metal2vulkan 0.1.0

Translate Metal AIR / LLVM IR to Vulkan SPIR-V with a native Rust emitter
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
//! The primary emit path plus its validation-error retry tiers. `translate_native_primary_validated`
//! emits once, then `emit_finish_primary`/`apply_*`/`primary_*_if_needed` try an ordered cascade of
//! structural rewrites, each re-validated, until one passes spirv-val (or all are exhausted). Byte
//! behavior is identical to when this lived in `lib.rs`; extracted for cohesion.

use super::*;
use crate::spirv_module::{load_bytes, Module};

fn module_bytes(module: &Module) -> Vec<u8> {
    module
        .assemble()
        .iter()
        .flat_map(|word| word.to_le_bytes())
        .collect()
}

fn prune_constant_branches_candidate(spv: &[u8]) -> Option<Module> {
    let mut module = load_bytes(spv).ok()?;
    native::prune_constant_branches_module(&mut module).ok()?;
    Some(module)
}

fn relooper_candidate(spv: &[u8]) -> Option<Vec<u8>> {
    let mut module = load_bytes(spv).ok()?;
    native::rewrite_to_relooper_module(&mut module).ok()?;
    Some(module_bytes(&module))
}

/// A raw logical-buffer write with a wide dynamic offset carries a local robust-write selection:
/// write only when the full i64 byte sum fits the u32 logical address model.  When that selection
/// occurs in a source loop header, its continuation owns the source terminator and can expose a
/// CFG-only validator error.  Reuse the exact ordered prefix of production's CFG cascade through
/// raw→relooper, but only for that emitted structural guard and only after each candidate validates.
/// This preserves the production candidate order (ordinary relooper, prune, high-cap, value lowering,
/// then raw→relooper) rather than bypassing a more portable earlier form.
pub(crate) fn primary_wide_raw_store_guard_cfg_chain_if_needed(
    validation_error: &str,
    spv: &[u8],
    module: &Module,
    retry: &retry::RetryCtx<'_>,
) -> Option<Vec<u8>> {
    if native::classify_validation_error(validation_error)
        != native::ValidationClass::CfgStructurization
        || !native::module_has_wide_raw_store_guard(module)
    {
        return None;
    }
    retry
        .census("val-cfg:relooper", retry.relooper_retry(spv))
        .or_else(|| {
            retry.census(
                "val-cfg:prune_then_relooper",
                retry.prune_then_relooper(spv),
            )
        })
        .or_else(|| retry.census("val-cfg:relooper_huge", retry.relooper_retry_huge(spv)))
        .or_else(|| {
            retry.census(
                "val-cfg:relooper_then_value_select",
                retry.relooper_then_value_select(spv),
            )
        })
        .or_else(|| retry.census("val-cfg:raw_then_relooper", retry.raw_then_relooper()))
}

/// Emit + `finish_module` the default (structured) way, then apply the in-process primary-emit
/// rewrites. This is the default path's output up to (but not including) `spirv_val_bytes` and the
/// tier cascade. Shared by the no-retry baseline path and its validation-gated primary-floor wrapper,
/// so both begin from the same structured bytes as the production default attempt. (The former
/// Keystone-2 `STRUCTURED_EXIT_GATE` demote-to-repair tail was removed with the W4 repair-roster
/// deletion — the MPS attention-kernel `structured-exit` family now ships via the retry cascade.)
pub(super) fn emit_finish_primary_module(
    san_ll: &str,
    stage: passes::Stage,
    frag: Option<&meta::FragMeta>,
    vert: Option<&meta::VertMeta>,
    kern: Option<&meta::KernMeta>,
    entry_name: Option<&str>,
    options: passes::TransformOptions,
) -> Result<FinishedModule, String> {
    let structured = tools::llc_vulkan_spirv_with_sidecar(
        san_ll,
        Path::new(""),
        kern,
        entry_name,
        stage_buffer_layouts(stage, frag, vert, kern),
    )
    .and_then(|b| {
        finish_module(
            b,
            stage,
            frag,
            vert,
            kern,
            entry_name,
            options,
            FinishRewrites::Primary,
        )
    })?;
    // Callers choose the byte-only BC boundary or retain the module for validation-gated rewrites.
    // The pointer-phi PSB form remains outside this helper, so a speculative physical rewrite cannot
    // perturb the subprocess-free baseline.
    Ok(structured)
}

/// PhysicalStorageBuffer64 lowering for a cross-binding POINTER PHI. A `select` can replay values at
/// its consumer in plain Logical storage, but a phi's chosen edge has no predicate at that consumer
/// and its dynamic access indices may be defined only after the merge. The PSB address table keeps
/// that pointer selection representable and leaves ordinary selects to
/// [`apply_primary_emit_rewrites_module`].
/// This pass is deliberately kept OUT of `finish_module`, so it touches only the primary candidate
/// and never a retry re-emit.
pub(crate) fn apply_xbind_phi_psb(module: &Module, spv: &[u8]) -> Vec<u8> {
    let mut candidate = module.clone();
    if native::rewrite_cross_binding_pointer_phis_module(&mut candidate).is_err() {
        return spv.to_vec();
    }
    candidate
        .assemble()
        .iter()
        .flat_map(|word| word.to_le_bytes())
        .collect()
}

/// Build the purely in-process primary candidate on the already-loaded, already-canonicalized
/// module. Keeping both rewrites in memory retains the typed sidecar roots that replaced legacy
/// debug-marker liveness. The ordering and second canonicalization after value lowering match the
/// former bytes adapters exactly. Retry re-emits use [`FinishRewrites::Plain`] and never call this.
pub(crate) fn apply_primary_emit_rewrites_module(
    module: &mut Module,
    retained_global_ids: &mut [spirv::Word],
) {
    if native::value_lower_cross_binding_pointer_merges_module(module) {
        passes::canonicalize_ids_and_remap(module, retained_global_ids);
    }
    apply_agx_fc_prune_module(module, retained_global_ids);
}

pub(crate) fn primary_xbind_phi_psb_for_validation_error(
    module: &Module,
    spv: &[u8],
    validation_error: &str,
    tmp: &Path,
) -> Option<Vec<u8>> {
    if native::classify_validation_error(validation_error)
        != native::ValidationClass::CrossBindingPointerMerge
    {
        return None;
    }
    if !native::has_cross_binding_pointer_phi_module(module) {
        return None;
    }
    let primary = apply_xbind_phi_psb(module, spv);
    (primary != spv && tools::spirv_val_bytes(&primary, tmp).is_ok()).then_some(primary)
}

/// Re-emit a primary only after its unseeded form proves a pointer-typing failure. The optional
/// parser mode requires an exact primitive metadata type, a matching post-phi GEP source type, and
/// the declared byte extent; the re-emission is still adopted only when it validates (or its
/// cross-binding pointer phi validates after the physical address lowering).
#[allow(clippy::too_many_arguments)]
pub(crate) fn primary_primitive_phi_metadata_if_needed(
    validation_error: &str,
    san_ll: &str,
    stage: passes::Stage,
    frag: Option<&meta::FragMeta>,
    vert: Option<&meta::VertMeta>,
    kern: Option<&meta::KernMeta>,
    entry_name: Option<&str>,
    tmp: &Path,
    options: passes::TransformOptions,
) -> Option<Vec<u8>> {
    if native::classify_validation_error(validation_error) != native::ValidationClass::PointerTyping
    {
        return None;
    }
    let emitted = native::emit_vulkan_spirv_with_primitive_phi_metadata_sidecar(
        san_ll,
        kern,
        entry_name,
        stage_buffer_layouts(stage, frag, vert, kern),
    )
    .ok()?;
    let finished = finish_module(
        emitted,
        stage,
        frag,
        vert,
        kern,
        entry_name,
        options,
        FinishRewrites::Primary,
    )
    .ok()?;
    let primary_module = finished.module;
    let primary = finished.bytes;
    match tools::spirv_val_bytes(&primary, tmp) {
        Ok(()) => Some(primary),
        Err(error) => {
            primary_xbind_phi_psb_for_validation_error(&primary_module, &primary, &error, tmp)
        }
    }
}

/// Promote the first production pointer-typing retry as a primary candidate. For the
/// `reached non-composite` Private-placeholder class, try the FC-buffer-promoted Logical projection
/// first: if an FC-wrapped buffer is actually used, binding it as the real descriptor preserves the
/// intended pointer better than the all-buffer-raw fallback's private zero scratch. Otherwise keep the
/// established all-buffer-raw first choice. Every candidate is retained only when it independently
/// validates.
pub(crate) fn primary_pointer_typing_raw_if_needed(
    validation_error: &str,
    retry: &retry::RetryCtx<'_>,
) -> Option<Vec<u8>> {
    if native::classify_validation_error(validation_error) != native::ValidationClass::PointerTyping
    {
        return None;
    }
    if validation_error.contains("reached non-composite") {
        if let Some(bytes) = retry.fc_promote_logical() {
            return Some(bytes);
        }
    }
    retry.raw_retry()
}

/// A pointer-typing primary can become a CFG-only failure after all buffers are modeled raw.  The
/// wide raw-write guard is the structural proof for that composition; replay the exact production
/// pointer branch between the ordinary raw retry and its raw→relooper/PSB tail so an earlier prune or
/// subword rewrite retains its established priority.  Every invoked retry independently validates.
pub(crate) fn primary_pointer_typing_wide_raw_guard_chain_if_needed(
    validation_error: &str,
    spv: &[u8],
    module: &Module,
    retry: &retry::RetryCtx<'_>,
) -> Option<Vec<u8>> {
    if native::classify_validation_error(validation_error) != native::ValidationClass::PointerTyping
        || !retry.raw_reemit_has_wide_raw_store_guard()
    {
        return None;
    }
    retry
        .census("val-ptr:prune", retry.prune_retry(spv))
        .or_else(|| retry.census("val-ptr:subword_pack", retry.subword_pack_retry(module)))
        .or_else(|| {
            retry.census(
                "val-ptr:prune_then_relooper",
                retry.prune_then_relooper(spv),
            )
        })
        .or_else(|| retry.raw_then_psb_chain("val-ptr:raw_then_relooper", "val-ptr:raw_psb"))
}

/// Promote the existing `val-other:prune` retry only for an `Other` primary validation error. This
/// covers the function-constant-dead table shape where an optional buffer's Private zero placeholder
/// is selected with a real StorageBuffer arm; static branch pruning removes the disabled arm. If that
/// semantics-preserving prune exposes only a structured-CFG rejection, apply the existing capped
/// prune-then-relooper sequence before adoption. The raw byte baseline remains unmodified, and every
/// candidate is independently validated. Other validation classes retain their established
/// higher-priority repair routing.
pub(crate) fn primary_other_prune_if_needed(
    validation_error: &str,
    spv: &[u8],
    tmp: &Path,
) -> Option<Vec<u8>> {
    if native::classify_validation_error(validation_error) != native::ValidationClass::Other {
        return None;
    }
    let mut pruned_module = prune_constant_branches_candidate(spv)?;
    let pruned = module_bytes(&pruned_module);
    if pruned == spv {
        return None;
    }
    match tools::spirv_val_bytes(&pruned, tmp) {
        Ok(()) => return Some(pruned),
        Err(error)
            if native::classify_validation_error(&error)
                != native::ValidationClass::CfgStructurization =>
        {
            return None;
        }
        Err(_) => {}
    }
    native::rewrite_to_relooper_module_capped(
        &mut pruned_module,
        native::PRUNE_THEN_RELOOPER_MAX_BLOCKS,
    )
    .ok()?;
    let primary = module_bytes(&pruned_module);
    (primary != spv && tools::spirv_val_bytes(&primary, tmp).is_ok()).then_some(primary)
}

/// Adopt the ordinary relooper rewrite when the primary fails a CFG structural rule and the relooper
/// produces a validator-clean module.
///
/// The full retry cascade already owns this form under the `val-cfg:relooper` census label
/// (`relooper_retry`). Historically `primary_cfg_prune_then_relooper_if_needed` *declined* when the
/// plain relooper worked so that cascade could keep the tier label — but
/// [`crate::translate_native_primary_validated`] has no later relooper step, so the banked compound-loop
/// residual (`09/de938adc`, shared merge block / `loop:MultipleExits+MultipleLatches[k=2,phi=1]`) stayed
/// PV-invalid even though production already ships the relooped bytes. This helper closes that gap:
/// validation-gated, structural (not name-keyed), and byte-identical for every primary that already
/// spirv-val-passes (it never fires on them).
pub(crate) fn primary_relooper_if_needed(
    validation_error: &str,
    spv: &[u8],
    tmp: &Path,
) -> Option<Vec<u8>> {
    if native::classify_validation_error(validation_error)
        != native::ValidationClass::CfgStructurization
    {
        return None;
    }
    let candidate = relooper_candidate(spv)?;
    (candidate != spv && tools::spirv_val_bytes(&candidate, tmp).is_ok()).then_some(candidate)
}

/// Promote the production `val-cfg:raw_then_relooper` (+ PSB) cascade into the primary-validated path
/// when the primary fails a CFG structurization rule that plain relooper / prune-then-relooper cannot
/// rewrite on the primary bytes. The frontier `selection:cross-arm-shared` residual
/// (`08/2d2d8c4f`, `10/1754ce19`) admits under `structured_plan` but still emits a validator-invalid
/// module (structured-exit / undefined-id SSA order); production ships via raw re-emit → relooper →
/// PSB. This helper reuses that exact retry composition, validation-gated and not name-keyed.
pub(crate) fn primary_cfg_raw_then_relooper_if_needed(
    validation_error: &str,
    spv: &[u8],
    retry: &retry::RetryCtx<'_>,
) -> Option<Vec<u8>> {
    if native::classify_validation_error(validation_error)
        != native::ValidationClass::CfgStructurization
    {
        return None;
    }
    retry
        .census(
            "val-cfg:relooper_then_value_select",
            retry.relooper_then_value_select(spv),
        )
        .or_else(|| retry.census("val-cfg:raw_then_relooper", retry.raw_then_relooper()))
}

/// Promote the existing `val-cfg:prune_then_relooper` retry only when the primary fails a CFG
/// structural rule and the ordinary relooper cannot validate it. Function-constant pruning can remove
/// dead branches that make a large reducer exceed the ordinary relooper's scope; the capped reloop of
/// the smaller, reachable CFG is already the production retry form. Checking the ordinary relooper
/// first preserves that retry's priority for every case it can already repair on the full cascade;
/// the primary-validated path adopts the plain relooper via [`primary_relooper_if_needed`] instead.
pub(crate) fn primary_cfg_prune_then_relooper_if_needed(
    validation_error: &str,
    spv: &[u8],
    tmp: &Path,
) -> Option<Vec<u8>> {
    if native::classify_validation_error(validation_error)
        != native::ValidationClass::CfgStructurization
    {
        return None;
    }
    if relooper_candidate(spv)
        .is_some_and(|candidate| tools::spirv_val_bytes(&candidate, tmp).is_ok())
    {
        return None;
    }
    let mut pruned_module = prune_constant_branches_candidate(spv)?;
    let pruned = module_bytes(&pruned_module);
    if pruned == spv {
        return None;
    }
    native::rewrite_to_relooper_module_capped(
        &mut pruned_module,
        native::PRUNE_THEN_RELOOPER_MAX_BLOCKS,
    )
    .ok()?;
    let primary = module_bytes(&pruned_module);
    (primary != spv && tools::spirv_val_bytes(&primary, tmp).is_ok()).then_some(primary)
}

/// Promote the narrow R2 construct-tree repair before broader CFG rewrites when the primary fails a
/// structured-CFG validation rule. The retry itself re-emits from source and independently validates
/// the finished module, so this helper only gates it to the relevant error class and preserves the
/// existing tier-census label.
pub(crate) fn primary_construct_tree_if_needed(
    validation_error: &str,
    retry: &retry::RetryCtx<'_>,
) -> Option<Vec<u8>> {
    if native::classify_validation_error(validation_error)
        != native::ValidationClass::CfgStructurization
    {
        return None;
    }
    retry.census("val-cfg:construct_tree", retry.construct_tree_retry())
}

/// Promote the narrow `val-other:inline_sroa_relooped` composition for a dynamic structure index only
/// after inlining/SROA changes its error class to CFG structurization. The source-level index remains
/// dynamic on the raw primary, so no pointer retype is guessed: the existing inline pass removes the
/// caller-owned local byte view, and the existing relooper restructures the resulting otherwise-valid
/// CFG. Any other first or second validation class declines.
#[allow(clippy::too_many_arguments)]
pub(crate) fn primary_dynamic_struct_index_inline_sroa_relooper_if_needed(
    validation_error: &str,
    san_ll: &str,
    stage: passes::Stage,
    frag: Option<&meta::FragMeta>,
    vert: Option<&meta::VertMeta>,
    kern: Option<&meta::KernMeta>,
    entry_name: Option<&str>,
    tmp: &Path,
    options: passes::TransformOptions,
) -> Option<Vec<u8>> {
    if native::classify_validation_error(validation_error) != native::ValidationClass::Other
        || !native::is_dynamic_struct_index_error(validation_error)
    {
        return None;
    }
    let inlined = tools::llc_vulkan_spirv_inline_sroa_with_sidecar(
        san_ll,
        tmp,
        kern,
        entry_name,
        stage_buffer_layouts(stage, frag, vert, kern),
    )
    .and_then(|spv| {
        finish_module(
            spv,
            stage,
            frag,
            vert,
            kern,
            entry_name,
            options,
            FinishRewrites::Plain,
        )
    })
    .map(|finished| finished.bytes)
    .ok()?;
    let inline_error = tools::spirv_val_bytes(&inlined, tmp).err()?;
    if native::classify_validation_error(&inline_error)
        != native::ValidationClass::CfgStructurization
    {
        return None;
    }
    let primary = relooper_candidate(&inlined)?;
    tools::spirv_val_bytes(&primary, tmp)
        .is_ok()
        .then_some(primary)
}

/// Promote the existing inline+SROA+raw retry only when the raw primary's exact validator rule is an
/// illegal Logical pointer operand. Inlining exposes the float-CAS helper's pointer at the caller and
/// raw addressing represents its `float`/`i32` reinterpret without `OpBitcast`; the candidate is
/// adopted only after its own validation. The no-retry byte baseline remains on the original emission.
#[allow(clippy::too_many_arguments)]
pub(crate) fn primary_logical_pointer_inline_sroa_raw_if_needed(
    validation_error: &str,
    san_ll: &str,
    stage: passes::Stage,
    frag: Option<&meta::FragMeta>,
    vert: Option<&meta::VertMeta>,
    kern: Option<&meta::KernMeta>,
    entry_name: Option<&str>,
    tmp: &Path,
    options: passes::TransformOptions,
) -> Option<Vec<u8>> {
    if native::classify_validation_error(validation_error) != native::ValidationClass::Other
        || !native::is_logical_pointer_operand_error(validation_error)
    {
        return None;
    }
    let primary = tools::llc_vulkan_spirv_inline_sroa_raw_with_sidecar(
        san_ll,
        tmp,
        kern,
        entry_name,
        stage_buffer_layouts(stage, frag, vert, kern),
    )
    .and_then(|spv| {
        finish_module(
            spv,
            stage,
            frag,
            vert,
            kern,
            entry_name,
            options,
            FinishRewrites::Plain,
        )
    })
    .map(|finished| finished.bytes)
    .ok()?;
    tools::spirv_val_bytes(&primary, tmp)
        .is_ok()
        .then_some(primary)
}

/// Move a stale opaque-image select into its pure explicit-LOD sampling consumers only after the raw
/// primary reports the broad `Other` select-type diagnostic. The post-emit pass requires an image-only
/// select tree and rejects every non-sampling opaque use, so it cannot turn a pointer repair into a
/// resource-policy guess. The result is retained only after independent spirv-val.
/// `finish_module` has already loaded, transformed, and canonicalized this module; clone that
/// retained carrier for the speculative rewrite instead of reparsing its just-assembled bytes.
pub(crate) fn primary_opaque_image_select_module_if_needed(
    validation_error: &str,
    module: &Module,
    spv: &[u8],
    tmp: &Path,
) -> Option<Vec<u8>> {
    if native::classify_validation_error(validation_error) != native::ValidationClass::Other {
        return None;
    }
    let mut primary_module = module.clone();
    native::rewrite_opaque_image_selects_module(&mut primary_module).ok()?;
    let primary = primary_module
        .assemble()
        .iter()
        .flat_map(|word| word.to_le_bytes())
        .collect::<Vec<_>>();
    (primary != spv && tools::spirv_val_bytes(&primary, tmp).is_ok()).then_some(primary)
}

/// PRIMARY-emit FC prune for modules that CALL a bodiless `llvm.agx*` hardware-intrinsic declaration.
/// Such a call is never executable on a Vulkan target (no
/// lowering exists, the declaration has no body) and is frequently spirv-val-INVALID — the
/// declaration's generic pointer param types mismatch the concrete call args. The affected kernels
/// are Metal function-constant specialized with the agx arm FC-dead (function constants are modeled
/// at their disabled default, matching how the goldens were captured), so the static constant-branch
/// prune — the SAME transform the `val-other:prune` retry applies post-validation-failure — removes
/// the arm, and the primary emit produces the bytes that retry ships today. Triggered structurally
/// by [`native::has_bodiless_agx_call_module`] (the `llvm.agx` ABI-symbol namespace, never a shader
/// name). The result is retained only when that exact call is gone after pruning, so an AGX call in
/// LIVE code cannot cause an unrelated constant branch to alter the primary candidate.
fn apply_agx_fc_prune_module(module: &mut Module, retained_global_ids: &[spirv::Word]) {
    if !native::has_bodiless_agx_call_module(module) {
        return;
    }
    let original = module.clone();
    let changed = native::prune_constant_branches_module_preserving(module, retained_global_ids);
    if !changed || native::has_bodiless_agx_call_module(module) {
        *module = original;
    } else {
        native::close_module_capabilities(module);
    }
}