---
generated: true
model: claude-opus-4-7
generated_at: 2026-05-21T00:00:00+00:00
source_commit: 1655c2bf8d3567fa220a5226769ef5e3917d55e8
watched_paths_digest: 8749b5a60327ef9aea96c0b02a10d643c2d39d04195e7cbd40904b69dabc7f6e
source_doc: docs/generated/design/pipeline/05-ir-passes.md
source_doc_digest: 0c1c128f131512193c797738ba0f341f117afd9505bb5ddddf47481f9f70dc7b
warning: "Auto-generated. May drift from source. Do not edit by hand."
---

# Tests for pipeline/05-ir-passes

## Intent
Tests verify the IR-pass catalog described in
[`docs/generated/design/pipeline/05-ir-passes.md`](../../../design/pipeline/05-ir-passes.md):
the categorized inventory of ~325 `slang-ir-*.cpp` passes that run
between AST → IR lowering and code emission, orchestrated by
`linkAndOptimizeIR` in `slang-emit.cpp`. The doc groups the passes
into nine categories (Linking and validation, SSA construction /
basic cleanup, Specialization and generics, Differentiation,
Type and value legalization, Inlining and call-graph, Entry-point
and parameter handling, Layout and binding, Loop transformations,
Target-specific lowering, Instrumentation). Each test anchors on a
named pass in the doc's per-category tables and confirms an
observable consequence — either a `### BEFORE/AFTER <pass>:` IR-dump
diff, a target-text rewrite that the pass causes, or a diagnostic
the validation pass emits.

The bundle exercises every category. Three modes of observation are
used:

- **`-dump-ir-before/-dump-ir-after <pass>`** with FileCheck against
  the IR dump for `### BEFORE <pass>:` / `### AFTER <pass>:` headers,
  used for `specializeModule`, `simplifyIR`, `eliminatePhis`,
  `eliminateDeadCode` (and as the IR-side mode for `dll-export`).
- **Multi-target SIMPLE** with one `//TEST:SIMPLE(filecheck=<NAME>)`
  per text-emit target where the pass is observable; per-target
  CHECK prefixes in the source comments. Used for the dominant
  legalization / emit-shape consequences (DCE, inline, optional
  lowering, collect-global-uniforms, entry-point decorations,
  byte-address legalize, vector-types legalize, etc.).
- **`DIAGNOSTIC_TEST:SIMPLE(diag=CHECK,non-exhaustive)`** for the
  validation passes that emit diagnostics: `check-recursion`,
  `missing-return`, `check-optional-none-usage`, `late-require-
  capability` (warning form), `check-differentiability`,
  `operator-shift-overflow`.

This is the largest bundle in the suite by design: `size_cap_files`
is 100; the bundle has 109 tests covering most categories. The
initial 45 functional + negative tests were extended with 26
boundary / negative / stress probes that pressure the same claims
along documented axes (0 / 1 / many / large-N iterations for
loop-unroll; 0 / 1 / many uniforms for layout-and-binding; narrow
scalar types — uint8, half, bool — through type-legalization;
alternative input shapes for the diagnostic passes). A subsequent
expansion pass added 38 more tests anchored on under-represented
doc claims: byte-address-legalize store and vector / non-zero-
offset shapes; matrix and many-dim vector legalization;
empty-array elision; three-element tuple decomposition;
multi-case enum lowering; alternative payload types for the
optional / float / int8 narrow probes; `__stage_switch` vertex-vs-
fragment branches; specialize-resources for resource-typed args;
nested and many-arg specialization stress; pixel-/vertex-stage
varying-param shapes; SV_GroupID / SV_GroupThreadID built-ins;
multi-buffer Metal / WGSL legalize; non-default SPIR-V localsize;
`__ldg(...)` on struct-field uniforms; multi-out-parameter ABI;
fan-out / depth stress on inlining; nested loop-unroll;
DCE on unused global uniforms; missing-return on if-only flow;
3-function recursion cycle.
Differentiation tests are limited to one observable diagnostic
(`check-differentiability`) — the rest of the autodiff family
requires test infrastructure beyond what `slangc` exposes on a
no-GPU runner and is recorded under `## Untested claims`.


## Functional coverage
| Claim | Intent | Anchor | Tests |
| --- | --- | --- | --- |
| The check-differentiability pass diagnoses calling a non-`[Differentiable]` function from a forward-derivative context. | negative | [#differentiation-autodiff](../../../design/pipeline/05-ir-passes.md#differentiation-autodiff) | [`check-differentiability-rejects-non-differentiable.slang`](check-differentiability-rejects-non-differentiable.slang) |
| Boundary: `[numthreads(8, 4, 2)]` (mixed non-trivial dimensions) is surfaced verbatim as `OpExecutionMode %main LocalSize 8 4 2` on SPIR-V by the entry-point-decorations pass. | boundary | [#entry-point-and-parameter-handling](../../../design/pipeline/05-ir-passes.md#entry-point-and-parameter-handling) | [`entry-point-decorations-spirv-localsize.slang`](entry-point-decorations-spirv-localsize.slang) |
| Boundary: entry-point-decorations surfaces a large `[numthreads(64, 1, 1)]` value on HLSL — alternative numthreads axis to the (1,1,1) and small default cases. | expansion | [#entry-point-and-parameter-handling](../../../design/pipeline/05-ir-passes.md#entry-point-and-parameter-handling) | [`entry-point-decorations-large-numthreads.slang`](entry-point-decorations-large-numthreads.slang) |
| Expansion: lower-out-parameters preserves the `out` keyword on multiple parameters (two `out` parameters in one helper) on HLSL — many-args axis to the existing single-out case. | expansion | [#entry-point-and-parameter-handling](../../../design/pipeline/05-ir-passes.md#entry-point-and-parameter-handling) | [`lower-out-parameters-on-multiple-out-params.slang`](lower-out-parameters-on-multiple-out-params.slang) |
| The entry-point-decorations pass attaches `[numthreads(...)]` to the entry-point IR function and the marker surfaces in every text target's emit. | functional | [#entry-point-and-parameter-handling](../../../design/pipeline/05-ir-passes.md#entry-point-and-parameter-handling) | [`entry-point-decorations-emit-numthreads-marker.slang`](entry-point-decorations-emit-numthreads-marker.slang) |
| Two `uniform` globals declared at module scope are collected into the same `GlobalParams` struct by the entry-point uniforms / collect-global-uniforms passes; both fields appear in the HLSL cbuffer body. | functional | [#entry-point-and-parameter-handling](../../../design/pipeline/05-ir-passes.md#entry-point-and-parameter-handling) | [`entry-point-uniforms-packs-multiple-uniforms-into-one-cbuffer.slang`](entry-point-uniforms-packs-multiple-uniforms-into-one-cbuffer.slang) |
| Stress: the eliminateDeadCode pass runs at least three times during the SPIR-V pipeline — three or more `### AFTER eliminateDeadCode:` headers appear in the dump. | stress | [#how-the-passes-are-ordered](../../../design/pipeline/05-ir-passes.md#how-the-passes-are-ordered) | [`dce-runs-multiple-times-stress.slang`](dce-runs-multiple-times-stress.slang) |
| The DCE / cleanup category runs multiple times during `linkAndOptimizeIR`; multiple `### AFTER eliminateDeadCode:` headers appear in the dump. | functional | [#how-the-passes-are-ordered](../../../design/pipeline/05-ir-passes.md#how-the-passes-are-ordered) | [`eliminate-dead-code-runs-multiple-stages.slang`](eliminate-dead-code-runs-multiple-stages.slang) |
| Boundary: a tiny helper WITHOUT `[ForceInline]` is NOT inlined by the default inline pass — the callee name survives in the HLSL emit. The doc claim is that `[ForceInline]` drives inlining, not size; this is the contrasting baseline. | boundary | [#inlining-and-call-graph](../../../design/pipeline/05-ir-passes.md#inlining-and-call-graph) | [`inline-tiny-helper-survives-without-attribute.slang`](inline-tiny-helper-survives-without-attribute.slang) |
| Stress: a `[ForceInline]` helper called 16 times is inlined at every call site — the callee name disappears from every observable target. | stress | [#inlining-and-call-graph](../../../design/pipeline/05-ir-passes.md#inlining-and-call-graph) | [`inline-many-call-sites-stress.slang`](inline-many-call-sites-stress.slang) |
| Stress: a five-deep `[ForceInline]` call chain (h0 -> h1 -> h2 -> h3 -> h4) inlines fully — no helper name appears in the HLSL emit. | stress | [#inlining-and-call-graph](../../../design/pipeline/05-ir-passes.md#inlining-and-call-graph) | [`inline-deeply-nested-callees-stress.slang`](inline-deeply-nested-callees-stress.slang) |
| Stress: eight different `[ForceInline]` callees (each called once) all inline into a single entry point — no callee name appears in the HLSL emit. | stress | [#inlining-and-call-graph](../../../design/pipeline/05-ir-passes.md#inlining-and-call-graph) | [`inline-many-different-callees-stress.slang`](inline-many-different-callees-stress.slang) |
| The dll-export pass marks exported entry-point functions with an `export` decoration visible in the IR dump. | functional | [#inlining-and-call-graph](../../../design/pipeline/05-ir-passes.md#inlining-and-call-graph) | [`dll-export-marks-entry-point-with-export-decoration.slang`](dll-export-marks-entry-point-with-export-decoration.slang) |
| The inline pass inlines a `[ForceInline]` callee so its body appears at the call site and the callee no longer appears in the emit. | functional | [#inlining-and-call-graph](../../../design/pipeline/05-ir-passes.md#inlining-and-call-graph) | [`inline-removes-forceinline-callee-from-emit.slang`](inline-removes-forceinline-callee-from-emit.slang) |
| Boundary: `getStringHash("")` of the empty string and a non-empty string both reduce to distinct integer literals in HLSL — the string-hash pass folds them deterministically. | boundary | [#layout-and-binding](../../../design/pipeline/05-ir-passes.md#layout-and-binding) | [`string-hash-pass-deterministic-across-runs.slang`](string-hash-pass-deterministic-across-runs.slang) |
| Boundary: a shader with zero module-scope uniforms does not produce a `GlobalParams` aggregate in the HLSL emit — the collect-global-uniforms pass has nothing to pack. | boundary | [#layout-and-binding](../../../design/pipeline/05-ir-passes.md#layout-and-binding) | [`layout-zero-uniforms-no-globalparams.slang`](layout-zero-uniforms-no-globalparams.slang) |
| Expansion (boundary): the string-hash pass produces a numeric constant for `getStringHash("")` (empty input) — the empty string literal does not survive in the HLSL emit. | expansion | [#layout-and-binding](../../../design/pipeline/05-ir-passes.md#layout-and-binding) | [`string-hash-pass-empty-string.slang`](string-hash-pass-empty-string.slang) |
| Expansion: translate-global-varying-var routes `SV_GroupID` and `SV_GroupThreadID` into per-target stage-input markers on HLSL and GLSL (alternative semantic shapes to SV_DispatchThreadID). | expansion | [#layout-and-binding](../../../design/pipeline/05-ir-passes.md#layout-and-binding) | [`translate-global-varying-var-sv-groupid.slang`](translate-global-varying-var-sv-groupid.slang) |
| Stress: eight module-scope uniforms are packed into a single `GlobalParams` aggregate by collect-global-uniforms; every named field survives in the HLSL emit. | stress | [#layout-and-binding](../../../design/pipeline/05-ir-passes.md#layout-and-binding) | [`layout-many-uniforms-stress.slang`](layout-many-uniforms-stress.slang) |
| The collect-global-uniforms pass packages free-floating `uniform` globals into a `GlobalParams` aggregate visible in the emit on HLSL, GLSL, and CUDA. | functional | [#layout-and-binding](../../../design/pipeline/05-ir-passes.md#layout-and-binding) | [`collect-global-uniforms-builds-global-params-struct.slang`](collect-global-uniforms-builds-global-params-struct.slang) |
| The explicit-global-context pass threads the global parameter context through the C++ emit; `GlobalParams_0` is passed by pointer rather than read from a literal global. | functional | [#layout-and-binding](../../../design/pipeline/05-ir-passes.md#layout-and-binding) | [`explicit-global-context-passes-globals-to-cpu.slang`](explicit-global-context-passes-globals-to-cpu.slang) |
| The late-require-capability pass propagates a capability requirement from `printf` to the GLSL emit's `#extension` line. | functional | [#layout-and-binding](../../../design/pipeline/05-ir-passes.md#layout-and-binding) | [`late-require-capability-emits-extension-for-printf.slang`](late-require-capability-emits-extension-for-printf.slang) |
| The layout pass adds resource binding decorations; the SPIR-V emit carries explicit `Binding N` / `DescriptorSet 0` decorations for each declared resource. | functional | [#layout-and-binding](../../../design/pipeline/05-ir-passes.md#layout-and-binding) | [`layout-pass-emits-binding-and-descriptorset-decorations.slang`](layout-pass-emits-binding-and-descriptorset-decorations.slang) |
| The string-hash pass replaces `getStringHash("...")` with a stable integer hash at IR level. | functional | [#layout-and-binding](../../../design/pipeline/05-ir-passes.md#layout-and-binding) | [`string-hash-pass-emits-numeric-hash.slang`](string-hash-pass-emits-numeric-hash.slang) |
| The translate-global-varying-var pass routes `SV_DispatchThreadID` into a target-shaped stage-input on every text target. | functional | [#layout-and-binding](../../../design/pipeline/05-ir-passes.md#layout-and-binding) | [`translate-global-varying-var-emits-input-as-parameter.slang`](translate-global-varying-var-emits-input-as-parameter.slang) |
| Expansion (negative): check-optional-none-usage rejects `.value` on an Optional<float> with float payload — payload-type axis to the existing int / explicit-none shapes. | expansion | [#linking-and-validation](../../../design/pipeline/05-ir-passes.md#linking-and-validation) | [`optional-none-usage-on-float-payload.slang`](optional-none-usage-on-float-payload.slang) |
| Expansion (negative): the check-recursion pass diagnoses a 3-function cycle (A->B->C->A) — alternative input shape to direct or 2-cycle mutual recursion. | expansion | [#linking-and-validation](../../../design/pipeline/05-ir-passes.md#linking-and-validation) | [`recursion-check-rejects-three-cycle.slang`](recursion-check-rejects-three-cycle.slang) |
| Negative: the check-optional-none-usage pass rejects `.value` access on an `Optional<int>` declared with an explicit `= none` initializer (alternative shape to the default-constructed case). | negative | [#linking-and-validation](../../../design/pipeline/05-ir-passes.md#linking-and-validation) | [`optional-none-usage-on-explicit-none-literal.slang`](optional-none-usage-on-explicit-none-literal.slang) |
| Negative: the check-recursion validation pass diagnoses mutual recursion (`fa` → `fb` → `fa`) just as it does direct self-recursion. | negative | [#linking-and-validation](../../../design/pipeline/05-ir-passes.md#linking-and-validation) | [`recursion-check-rejects-mutual-recursion.slang`](recursion-check-rejects-mutual-recursion.slang) |
| Targeting a profile the entry-point requires features beyond is rejected by capability validation passes. | negative | [#linking-and-validation](../../../design/pipeline/05-ir-passes.md#linking-and-validation) | [`check-unsupported-inst-rejects-non-target-feature.slang`](check-unsupported-inst-rejects-non-target-feature.slang) |
| The check-optional-none-usage validation pass rejects an Optional.value read when the value is always `none`. | negative | [#linking-and-validation](../../../design/pipeline/05-ir-passes.md#linking-and-validation) | [`optional-none-usage-check-rejects-always-none.slang`](optional-none-usage-check-rejects-always-none.slang) |
| The check-recursion validation pass diagnoses unsupported recursion in the IR. | negative | [#linking-and-validation](../../../design/pipeline/05-ir-passes.md#linking-and-validation) | [`recursion-check-rejects-self-recursion.slang`](recursion-check-rejects-self-recursion.slang) |
| Boundary: a `[ForceUnroll]` loop with exactly one static iteration is unrolled — no `for` keyword appears in the GLSL emit. | boundary | [#loop-transformations](../../../design/pipeline/05-ir-passes.md#loop-transformations) | [`loop-unroll-single-iteration.slang`](loop-unroll-single-iteration.slang) |
| Boundary: a `[ForceUnroll]` loop with zero static iterations is removed entirely by the loop-unroll pass and emits a documented `E30514` warning. | boundary | [#loop-transformations](../../../design/pipeline/05-ir-passes.md#loop-transformations) | [`loop-unroll-zero-iterations.slang`](loop-unroll-zero-iterations.slang) |
| Stress: a `[ForceUnroll]` loop with 32 static iterations is fully unrolled — no `for` keyword survives in the GLSL emit. | stress | [#loop-transformations](../../../design/pipeline/05-ir-passes.md#loop-transformations) | [`loop-unroll-large-iteration-stress.slang`](loop-unroll-large-iteration-stress.slang) |
| Stress: two nested `[ForceUnroll]` loops (4x4 = 16 iterations) both unroll fully — neither outer nor inner `for` keyword survives in the GLSL emit. | stress | [#loop-transformations](../../../design/pipeline/05-ir-passes.md#loop-transformations) | [`loop-unroll-nested-stress.slang`](loop-unroll-nested-stress.slang) |
| The loop-unroll pass unrolls a static `[ForceUnroll]` loop with a compile-time bound — no `for` loop remains in the emit on GLSL. | functional | [#loop-transformations](../../../design/pipeline/05-ir-passes.md#loop-transformations) | [`loop-unroll-removes-static-loop-on-glsl.slang`](loop-unroll-removes-static-loop-on-glsl.slang) |
| Boundary: a generic function with zero call-site instantiations is dead — neither the generic body nor any specialization appears in the emit. | boundary | [#specialization-and-generics](../../../design/pipeline/05-ir-passes.md#specialization-and-generics) | [`specialize-zero-instances-of-generic.slang`](specialize-zero-instances-of-generic.slang) |
| Boundary: a generic with exactly one call-site instantiation produces exactly one concrete specialization — the `### AFTER specializeModule` dump shows a `Func(Float, Float, Float)` signature. | boundary | [#specialization-and-generics](../../../design/pipeline/05-ir-passes.md#specialization-and-generics) | [`specialize-one-instance-emits-concrete-type.slang`](specialize-one-instance-emits-concrete-type.slang) |
| Expansion: `__stage_switch` distinguishes vertex vs fragment branches — compiling for vertex stage observes the vertex value (10), fragment stage observes fragment value (20). | expansion | [#specialization-and-generics](../../../design/pipeline/05-ir-passes.md#specialization-and-generics) | [`specialize-stage-switch-multiple-stages.slang`](specialize-stage-switch-multiple-stages.slang) |
| Expansion: a `__target_switch` whose only matching branch is `default` resolves to the default value after specialize-target-switch — no other case leaks through. | expansion | [#specialization-and-generics](../../../design/pipeline/05-ir-passes.md#specialization-and-generics) | [`specialize-target-switch-default-branch.slang`](specialize-target-switch-default-branch.slang) |
| Expansion: nested generic instantiation — `outer<T>` calls `inner<T>` — specializes to a concrete chain at depth 3, neither generic shape survives in the emit. | expansion | [#specialization-and-generics](../../../design/pipeline/05-ir-passes.md#specialization-and-generics) | [`specialize-nested-generic-instantiation.slang`](specialize-nested-generic-instantiation.slang) |
| Expansion: the specialize-arrays pass produces independent specializations for two different `let N` array sizes (N=2 and N=8) — both inline cleanly and no generic shape survives. | expansion | [#specialization-and-generics](../../../design/pipeline/05-ir-passes.md#specialization-and-generics) | [`specialize-arrays-different-fixed-sizes.slang`](specialize-arrays-different-fixed-sizes.slang) |
| Expansion: the specialize-resources pass routes a resource-typed argument (RWStructuredBuffer<int>) into a concrete specialization — the helper inlines and the buffer access appears in the entry-point body. | expansion | [#specialization-and-generics](../../../design/pipeline/05-ir-passes.md#specialization-and-generics) | [`specialize-resources-routes-typed-resource-arg.slang`](specialize-resources-routes-typed-resource-arg.slang) |
| Lowering of dynamic dispatch resolves a known interface-typed value (concrete implementer) to a direct call in the emit. | functional | [#specialization-and-generics](../../../design/pipeline/05-ir-passes.md#specialization-and-generics) | [`lower-com-methods-emit-binds-virtual-call.slang`](lower-com-methods-emit-binds-virtual-call.slang) |
| Stress: a generic with four type parameters is specialized cleanly — the four-arg generic shape is replaced by a concrete specialization, no surviving generic indirection. | stress | [#specialization-and-generics](../../../design/pipeline/05-ir-passes.md#specialization-and-generics) | [`specialize-many-generic-type-args-stress.slang`](specialize-many-generic-type-args-stress.slang) |
| Stress: the same generic called with three distinct type arguments (int / float / uint) is each specialized independently — the three concrete operand types `int ai_0`, `float af_0`, `uint au_0` survive in the HLSL GlobalParams struct. | stress | [#specialization-and-generics](../../../design/pipeline/05-ir-passes.md#specialization-and-generics) | [`specialize-many-different-arg-instances.slang`](specialize-many-different-arg-instances.slang) |
| The specialize pass substitutes generic parameters with concrete types — before the pass the generic function is generic-shaped, after the pass it carries a concrete signature. | functional | [#specialization-and-generics](../../../design/pipeline/05-ir-passes.md#specialization-and-generics) | [`specialize-module-substitutes-generic-with-concrete-type.slang`](specialize-module-substitutes-generic-with-concrete-type.slang) |
| The specialize-arrays pass resolves a fixed-size-array generic parameter so the emit reflects the concrete N. | functional | [#specialization-and-generics](../../../design/pipeline/05-ir-passes.md#specialization-and-generics) | [`specialize-arrays-allows-generic-array-parameter.slang`](specialize-arrays-allows-generic-array-parameter.slang) |
| The specialize-stage-switch pass selects the branch matching the active shader stage — a `__stage_switch` returns the chosen branch's value at the call site. | functional | [#specialization-and-generics](../../../design/pipeline/05-ir-passes.md#specialization-and-generics) | [`specialize-stage-switch-resolves-stage-conditional.slang`](specialize-stage-switch-resolves-stage-conditional.slang) |
| The specialize-target-switch pass selects the branch matching the active target — different targets see different emit text. | functional | [#specialization-and-generics](../../../design/pipeline/05-ir-passes.md#specialization-and-generics) | [`specialize-target-switch-resolves-target-conditional.slang`](specialize-target-switch-resolves-target-conditional.slang) |
| Boundary: a completely dead two-function call chain (`deadOuter` calls `deadInner`, neither reachable) is fully removed by DCE — neither name appears in emit. | boundary | [#ssa-construction-and-basic-cleanup](../../../design/pipeline/05-ir-passes.md#ssa-construction-and-basic-cleanup) | [`dce-removes-transitively-dead-call-chain.slang`](dce-removes-transitively-dead-call-chain.slang) |
| Boundary: a local struct whose `.dead` field is written but never read has the dead store collapsed by the SPIR-V cleanup pipeline — the literal initializer 4242 does not survive into SPIR-V. | boundary | [#ssa-construction-and-basic-cleanup](../../../design/pipeline/05-ir-passes.md#ssa-construction-and-basic-cleanup) | [`dce-keeps-partially-used-struct-field.slang`](dce-keeps-partially-used-struct-field.slang) |
| Boundary: after const-folding, an `if (false)` arm is removed by DCE — the dead literal 9999 does not survive to the emit. | boundary | [#ssa-construction-and-basic-cleanup](../../../design/pipeline/05-ir-passes.md#ssa-construction-and-basic-cleanup) | [`dce-removes-dead-branch-after-const-fold.slang`](dce-removes-dead-branch-after-const-fold.slang) |
| Boundary: the init-local-var pass tolerates an uninitialized struct local — the function compiles successfully and the live field is read back correctly on HLSL. | boundary | [#ssa-construction-and-basic-cleanup](../../../design/pipeline/05-ir-passes.md#ssa-construction-and-basic-cleanup) | [`init-local-var-on-trivial-empty-struct.slang`](init-local-var-on-trivial-empty-struct.slang) |
| Expansion (negative): missing-return diagnoses an `if`-only function where the false branch implicitly falls through — alternative control-flow shape to the empty-body case. | expansion | [#ssa-construction-and-basic-cleanup](../../../design/pipeline/05-ir-passes.md#ssa-construction-and-basic-cleanup) | [`missing-return-on-if-only-branch.slang`](missing-return-on-if-only-branch.slang) |
| Expansion (negative): operator-shift-overflow warns when shifting an `int8_t` by a count exceeding its 8-bit width — narrow-type axis to the existing int / uint cases. | expansion | [#ssa-construction-and-basic-cleanup](../../../design/pipeline/05-ir-passes.md#ssa-construction-and-basic-cleanup) | [`shift-overflow-on-int8-shift.slang`](shift-overflow-on-int8-shift.slang) |
| Expansion: DCE removes an entirely-unread module-scope uniform (`leftover`) before emit — the name does not appear in the HLSL `GlobalParams` struct. | expansion | [#ssa-construction-and-basic-cleanup](../../../design/pipeline/05-ir-passes.md#ssa-construction-and-basic-cleanup) | [`dce-removes-unused-global-uniform.slang`](dce-removes-unused-global-uniform.slang) |
| Negative boundary: `uint << 32` shifts by exactly the operand bit-width — the operator-shift-overflow pass emits `E41030` `left shift overflow`. | negative | [#ssa-construction-and-basic-cleanup](../../../design/pipeline/05-ir-passes.md#ssa-construction-and-basic-cleanup) | [`shift-overflow-on-uint-32-shift.slang`](shift-overflow-on-uint-32-shift.slang) |
| Negative: the missing-return pass fires on an entirely empty non-void function body — the empty-body shape is a distinct input from the partial-branch shape. | negative | [#ssa-construction-and-basic-cleanup](../../../design/pipeline/05-ir-passes.md#ssa-construction-and-basic-cleanup) | [`missing-return-on-empty-non-void-body.slang`](missing-return-on-empty-non-void-body.slang) |
| Repeated runs of simplifyIR after the cleanup category leave only the final-form IR for emit; the `func %main` line remains as the user-visible entry point. | functional | [#ssa-construction-and-basic-cleanup](../../../design/pipeline/05-ir-passes.md#ssa-construction-and-basic-cleanup) | [`simplify-ir-preserves-entry-point.slang`](simplify-ir-preserves-entry-point.slang) |
| The DCE pass removes a top-level helper function that is never called from any reachable code. | functional | [#ssa-construction-and-basic-cleanup](../../../design/pipeline/05-ir-passes.md#ssa-construction-and-basic-cleanup) | [`dce-removes-unused-helper-from-emit.slang`](dce-removes-unused-helper-from-emit.slang) |
| The eliminate-phis pass converts the SSA form into a non-SSA form ahead of emit; the final IR-dump stage `### AFTER eliminatePhis:` exists and contains `func %main`. | functional | [#ssa-construction-and-basic-cleanup](../../../design/pipeline/05-ir-passes.md#ssa-construction-and-basic-cleanup) | [`eliminate-phis-converts-out-of-ssa-form.slang`](eliminate-phis-converts-out-of-ssa-form.slang) |
| The init-local-var pass inserts default initialization for locals so the SPIR-V emit does not need explicit zero-init for the typical local in a Slang shader. | functional | [#ssa-construction-and-basic-cleanup](../../../design/pipeline/05-ir-passes.md#ssa-construction-and-basic-cleanup) | [`init-local-var-allows-empty-initializer.slang`](init-local-var-allows-empty-initializer.slang) |
| The missing-return pass diagnoses paths missing a return in a non-void function. | negative | [#ssa-construction-and-basic-cleanup](../../../design/pipeline/05-ir-passes.md#ssa-construction-and-basic-cleanup) | [`missing-return-warns-on-non-void.slang`](missing-return-warns-on-non-void.slang) |
| The operator-shift-overflow pass diagnoses a literal shift count that exceeds the operand bit width. | negative | [#ssa-construction-and-basic-cleanup](../../../design/pipeline/05-ir-passes.md#ssa-construction-and-basic-cleanup) | [`operator-shift-overflow-warns-on-large-shift.slang`](operator-shift-overflow-warns-on-large-shift.slang) |
| The strip-debug-info pass removes debug-only instructions when debug info is not requested, so the SPIR-V emit only carries minimal OpSource debug info. | functional | [#ssa-construction-and-basic-cleanup](../../../design/pipeline/05-ir-passes.md#ssa-construction-and-basic-cleanup) | [`strip-debug-info-keeps-zero-debug-instructions.slang`](strip-debug-info-keeps-zero-debug-instructions.slang) |
| Boundary (cross-target): the cuda-immutable-load pass fires only when emitting CUDA — HLSL emit of the same uniform read contains no `__ldg(...)` call. | boundary | [#target-specific-lowering](../../../design/pipeline/05-ir-passes.md#target-specific-lowering) | [`cuda-immutable-load-only-on-cuda-not-hlsl.slang`](cuda-immutable-load-only-on-cuda-not-hlsl.slang) |
| Expansion: cuda-immutable-load wraps reads of a struct-field uniform — `Settings.factor` is read via `__ldg(&...)` on CUDA. Struct-field axis alongside the scalar uniform case. | expansion | [#target-specific-lowering](../../../design/pipeline/05-ir-passes.md#target-specific-lowering) | [`cuda-immutable-load-on-struct-field.slang`](cuda-immutable-load-on-struct-field.slang) |
| Expansion: metal-legalize attaches a positional `buffer(N)` marker to each resource in a multi-buffer entry point; the kernel qualifier also appears. | expansion | [#target-specific-lowering](../../../design/pipeline/05-ir-passes.md#target-specific-lowering) | [`metal-legalize-multiple-positional-buffers.slang`](metal-legalize-multiple-positional-buffers.slang) |
| Expansion: spirv-legalize emits an `OpEntryPoint` and `OpExecutionMode LocalSize` for an alternative numthreads shape (8,4,1) — non-default localsize axis. | expansion | [#target-specific-lowering](../../../design/pipeline/05-ir-passes.md#target-specific-lowering) | [`spirv-legalize-non-default-localsize.slang`](spirv-legalize-non-default-localsize.slang) |
| Expansion: wgsl-legalize emits a `@binding(N) @group(N)` pair for every resource in a multi-buffer entry point — two buffers each get their own annotation. | expansion | [#target-specific-lowering](../../../design/pipeline/05-ir-passes.md#target-specific-lowering) | [`wgsl-legalize-multiple-binding-groups.slang`](wgsl-legalize-multiple-binding-groups.slang) |
| The CUDA immutable-load pass wraps reads of uniform globals in `__ldg(&...)` on the CUDA target. | functional | [#target-specific-lowering](../../../design/pipeline/05-ir-passes.md#target-specific-lowering) | [`cuda-immutable-load-wraps-uniform-read-in-ldg.slang`](cuda-immutable-load-wraps-uniform-read-in-ldg.slang) |
| The GLSL-legalize pass rewrites an `RWStructuredBuffer<T>` into a `layout(std430, binding=N) buffer { T _data[]; }` SSBO shape. | functional | [#target-specific-lowering](../../../design/pipeline/05-ir-passes.md#target-specific-lowering) | [`glsl-legalize-emits-std430-ssbo-shape.slang`](glsl-legalize-emits-std430-ssbo-shape.slang) |
| The Metal-legalize pass attaches positional `[[buffer(N)]]` annotations and `kernel` markers to the entry point. | functional | [#target-specific-lowering](../../../design/pipeline/05-ir-passes.md#target-specific-lowering) | [`metal-legalize-emits-positional-buffer-marker.slang`](metal-legalize-emits-positional-buffer-marker.slang) |
| The SPIR-V legalize pass ensures the emitted SPIR-V module carries `OpCapability Shader`, `OpEntryPoint`, and an `OpExecutionMode LocalSize` decoration. | functional | [#target-specific-lowering](../../../design/pipeline/05-ir-passes.md#target-specific-lowering) | [`spirv-legalize-emits-opcapability-shader.slang`](spirv-legalize-emits-opcapability-shader.slang) |
| The WGSL-legalize pass attaches `@binding(N) @group(N)` to resources and `@compute @workgroup_size(...)` to the entry point. | functional | [#target-specific-lowering](../../../design/pipeline/05-ir-passes.md#target-specific-lowering) | [`wgsl-legalize-emits-binding-group-and-workgroup-size.slang`](wgsl-legalize-emits-binding-group-and-workgroup-size.slang) |
| The vk-invert-y pass inserts a Y-flip on vertex-shader output position when targeting Vulkan / SPIR-V; the SPIR-V emit shows OpFNegate of the Y component. | functional | [#target-specific-lowering](../../../design/pipeline/05-ir-passes.md#target-specific-lowering) | [`vk-invert-y-flips-position-y-on-spirv.slang`](vk-invert-y-flips-position-y-on-spirv.slang) |
| An explicit constructor-style cast on an integer literal is converted to a target-specific cast intrinsic by the type-legalization passes. | functional | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`lower-l-value-cast-routes-through-pointer.slang`](lower-l-value-cast-routes-through-pointer.slang) |
| Boundary: a 16-bit `half` value routed through type-legalization emits `OpCapability Float16` on SPIR-V and preserves the `half` spelling on HLSL. | boundary | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`legalize-types-half-16bit-through-pipeline.slang`](legalize-types-half-16bit-through-pipeline.slang) |
| Boundary: a `bool` field on a local struct routed through type-legalization emits a comparison-shaped result on HLSL — bool packing does not corrupt the live read. | boundary | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`legalize-types-bool-in-struct.slang`](legalize-types-bool-in-struct.slang) |
| Boundary: a `uint8_t` (narrow 8-bit) value routed through the type-legalization pipeline emits `OpCapability Int8` on SPIR-V. | boundary | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`legalize-types-uint8-narrow-through-pipeline.slang`](legalize-types-uint8-narrow-through-pipeline.slang) |
| Boundary: a length-1 vector — the smallest documented vector dimension — is legalized to a plain scalar `int` in the HLSL emit. | boundary | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`legalize-vector-types-length-one-vector.slang`](legalize-vector-types-length-one-vector.slang) |
| Expansion (boundary): a `double` value routed through the type-legalization pipeline emits `OpCapability Float64` on SPIR-V — wide-type axis to the 16-bit / 8-bit cases. | expansion | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`legalize-types-double-through-pipeline.slang`](legalize-types-double-through-pipeline.slang) |
| Expansion: byte-address-legalize handles the store side of `RWByteAddressBuffer.Store<int>(addr, v)` — the store lowers to a typed indexed write into `_data[]` on GLSL. | expansion | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`byte-address-legalize-store-on-glsl.slang`](byte-address-legalize-store-on-glsl.slang) |
| Expansion: byte-address-legalize handles vector-typed loads — `ByteAddressBuffer.Load<float4>(0)` lowers to a vector-shaped read on GLSL with the `babuf` SSBO surviving. | expansion | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`byte-address-legalize-vector-load.slang`](byte-address-legalize-vector-load.slang) |
| Expansion: byte-address-legalize rewrites a typed `ByteAddressBuffer.Load<int>(N)` at a non-zero offset into a typed indexed SSBO access on GLSL — the offset is divided by element size as part of the rewrite. | expansion | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`byte-address-legalize-load-at-nonzero-offset.slang`](byte-address-legalize-load-at-nonzero-offset.slang) |
| Expansion: legalize-binary-operator on WGSL handles a uint-typed shift count (`uint << uint`) — alternative operand-type axis to the int-shift case. | expansion | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`legalize-binary-operator-uint-shift-amount.slang`](legalize-binary-operator-uint-shift-amount.slang) |
| Expansion: legalize-empty-array elides a zero-length fixed-size array struct field — the field name does not survive in the HLSL emit because the legalizer dropped the empty array. | expansion | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`legalize-empty-array-zero-length.slang`](legalize-empty-array-zero-length.slang) |
| Expansion: legalize-matrix-types lowers a Slang `matrix<float, 3, 4>` into a target-shaped matrix on HLSL (`float3x4`) and GLSL (`mat3x4` / `mat4x3`-style spelling). | expansion | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`legalize-matrix-types-float3x4-emit.slang`](legalize-matrix-types-float3x4-emit.slang) |
| Expansion: legalize-varying-params on a pixel-shader entry point routes user-defined TEXCOORD inputs into per-target shapes and `SV_Target` into the per-target output marker on HLSL and GLSL. | expansion | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`legalize-varying-params-pixel-shader.slang`](legalize-varying-params-pixel-shader.slang) |
| Expansion: legalize-varying-params routes a vertex-shader output `SV_Position` into the per-target stage-output marker on HLSL and GLSL — alternative pipeline-stage shape. | expansion | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`legalize-varying-params-vertex-shader.slang`](legalize-varying-params-vertex-shader.slang) |
| Expansion: lower-bit-cast routes `bit_cast<uint>(float)` to a SPIR-V `OpBitcast` instruction — alternative target axis to the HLSL/GLSL spellings. | expansion | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`lower-bit-cast-float-to-uint-on-spirv.slang`](lower-bit-cast-float-to-uint-on-spirv.slang) |
| Expansion: lower-buffer-element-type handles a struct-typed buffer element — `RWStructuredBuffer<Pixel>` produces a typed SSBO whose field names survive on GLSL. | expansion | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`lower-buffer-element-type-struct-on-glsl.slang`](lower-buffer-element-type-struct-on-glsl.slang) |
| Expansion: lower-buffer-element-type renders an `RWStructuredBuffer<float>` element as `float _data[]` on GLSL — alternative element-type axis to the int case. | expansion | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`lower-buffer-element-type-float-on-glsl.slang`](lower-buffer-element-type-float-on-glsl.slang) |
| Expansion: lower-enum-type renders all enum cases (four cases here) as plain integer constants in the HLSL emit — the enum type name does not survive in the entry-point body. | expansion | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`lower-enum-type-on-multiple-cases.slang`](lower-enum-type-on-multiple-cases.slang) |
| Expansion: lower-optional-type renders `Optional<float>` into a struct with `value` and `hasValue` fields — alternative payload type axis to the existing `int` case. | expansion | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`lower-optional-type-on-float-payload.slang`](lower-optional-type-on-float-payload.slang) |
| Expansion: lower-tuple-types decomposes a 3-element `Tuple<int, float, uint>` so no `Tuple` type spelling survives on HLSL — the heterogeneous element types are flattened. | expansion | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`lower-tuple-types-three-element-tuple.slang`](lower-tuple-types-three-element-tuple.slang) |
| Stress: legalize-vector-types renders all three of `vector<int, 2>`, `vector<int, 3>`, `vector<int, 4>` simultaneously by pinning them into typed RW buffers — the three native spellings (`int2`, `int3`, `int4`) all appear in the HLSL emit. | stress | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`legalize-vector-types-many-dimensions-stress.slang`](legalize-vector-types-many-dimensions-stress.slang) |
| The byte-address-legalize pass rewrites a typed `ByteAddressBuffer.Load<int>(0)` into a typed indexed access on GLSL. | functional | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`byte-address-legalize-lowers-load-on-glsl.slang`](byte-address-legalize-lowers-load-on-glsl.slang) |
| The legalize-binary-operator pass adjusts a shift's RHS so it matches the target's signedness rules — on WGSL the shift count is a `u32`. | functional | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`legalize-binary-operator-routes-int-shift-on-wgsl.slang`](legalize-binary-operator-routes-int-shift-on-wgsl.slang) |
| The legalize-vector-types pass routes Slang `vector<T, N>` into target-specific vector spellings (`int3` on HLSL, `ivec3` on GLSL). | functional | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`legalize-vector-types-emits-target-vector.slang`](legalize-vector-types-emits-target-vector.slang) |
| The lower-bit-cast pass turns a Slang `bit_cast<int>(f)` into target-specific reinterpret intrinsics on HLSL and GLSL. | functional | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`lower-bit-cast-emits-target-specific-reinterpret.slang`](lower-bit-cast-emits-target-specific-reinterpret.slang) |
| The lower-buffer-element-type pass renders the element type of an `RWStructuredBuffer<int>` as `int` in the GLSL SSBO and `int` in the HLSL register declaration. | functional | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`lower-buffer-element-type-renders-int-on-glsl.slang`](lower-buffer-element-type-renders-int-on-glsl.slang) |
| The lower-defer pass routes a `defer` statement's body to run at scope exit; the deferred write appears in the emit. | functional | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`lower-defer-emits-cleanup-on-scope-exit.slang`](lower-defer-emits-cleanup-on-scope-exit.slang) |
| The lower-enum-type pass renders a Slang enum constant as a plain integer in the HLSL emit; no enum type name remains. | functional | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`lower-enum-type-emits-integer-on-hlsl.slang`](lower-enum-type-emits-integer-on-hlsl.slang) |
| The lower-optional-type pass turns `Optional<T>` into a plain struct with `value` and `hasValue` fields visible on every text target. | functional | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`lower-optional-type-emits-struct-with-has-value.slang`](lower-optional-type-emits-struct-with-has-value.slang) |
| The lower-out-parameters pass routes an `out` parameter through a pointer-like address on every text-emit target. | functional | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`lower-out-parameters-preserves-out-keyword.slang`](lower-out-parameters-preserves-out-keyword.slang) |
| The lower-tuple-types pass decomposes Slang tuples into scalar values; no `Tuple` type appears in the HLSL emit. | functional | [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | [`lower-tuple-types-flattens-into-anonymous-values.slang`](lower-tuple-types-flattens-into-anonymous-values.slang) |


## Untested claims
| Claim | Reason | Anchor | Why untested |
| --- | --- | --- | --- |
| The complete sequence of `### AFTER <pass>:` headers — these vary by target (HLSL has ~64 stages, SPIR-V has ~86) and would be a snapshot test, not a claim test. | (unclassified) | (unspecified) | Reason and explanation to be refined by the next regeneration. |
| Coverage instrumentation (`-trace-coverage-binding`, `-trace-coverage-reserved-space`) — these are command-line surface; the agentic bundle does not exercise them. | (unclassified) | (unspecified) | Reason and explanation to be refined by the next regeneration. |
| IR pass utilities (`Clone`, `Dominators`, `Util`, `Insts info`, `Insts stable names`) — they are not transformations and have no observable effect of their own. | (unclassified) | [#clone](../../../design/pipeline/05-ir-passes.md#clone) | Reason and explanation to be refined by the next regeneration. |
| The "Pass utilities" section — `Clone`, `Dominators`, `Util`, `Insts info`, `Insts stable names` — these are not transformations and have no observable behavior on their own. | (unclassified) | [#clone](../../../design/pipeline/05-ir-passes.md#clone) | Reason and explanation to be refined by the next regeneration. |
| The `defunctionalization` pass and `lower-expand-type` pass — the source-level constructs (first-class function values, variadic packs) are not core enough to anchor a `.slang` test without inviting a wider language-feature test. | (unclassified) | [#defunctionalization](../../../design/pipeline/05-ir-passes.md#defunctionalization) | Reason and explanation to be refined by the next regeneration. |
| The `insert-debug-value-store` and `liveness` passes — gated on debug-info preservation. We exercised the inverse (`strip-debug-info` without `-g`); the positive `-g` path would require a debug emitter we do not target. | (unclassified) | [#insert-debug-value-store](../../../design/pipeline/05-ir-passes.md#insert-debug-value-store) | Reason and explanation to be refined by the next regeneration. |
| Hoistable / global flag bits — internal to `IRInst`'s op encoding; not in the `-dump-ir` output. | (unclassified) | [#irinst](../../../design/pipeline/05-ir-passes.md#irinst) | Reason and explanation to be refined by the next regeneration. |
| The exact order in which passes run for a given target. The doc explicitly defers ordering to `linkAndOptimizeIR` and the per-target pipelines under `target-pipelines/`. A textual ordering would be brittle and is not a doc claim here; only the "multiple invocations" claim is preserved. | (unclassified) | [#linkandoptimizeir](../../../design/pipeline/05-ir-passes.md#linkandoptimizeir) | Reason and explanation to be refined by the next regeneration. |
| The `obfuscate-loc` pass — gated on `-obfuscate` and intended for distributed modules; not exercised here. | (unclassified) | [#obfuscate-loc](../../../design/pipeline/05-ir-passes.md#obfuscate-loc) | Reason and explanation to be refined by the next regeneration. |
| The "Other passes" section's `spirv-snippet` — internal helper used by SPIR-V emit; not surface-visible. | (unclassified) | [#spirv-snippet](../../../design/pipeline/05-ir-passes.md#spirv-snippet) | Reason and explanation to be refined by the next regeneration. |
| Per-pass C++ helper functions and class structures (IRBuilder usage inside each pass). Internal API. | needs-unit-test | (unspecified) | No slangc CLI surface reaches this. A C++ unit test in `tools/slang-unit-test/` could exercise the relevant compiler internals directly. |
| `extract-value-from-type`, `bind-existentials`, `any-value-inference`, `any-value-marshalling`, `synthesize-active-mask`, `propagate-func-properties` — internal analysis or marshalling passes with no user-observable footprint that the doc names. | implementation-detail | [#extract-value-from-type](../../../design/pipeline/05-ir-passes.md#extract-value-from-type) | Internal compiler choice (pass ordering, hoistability decisions, deduplication) with no test-directive that reveals it. |
| The pre-link region documented in `04b-pre-link-passes.md`. If a claim is about an explicitly pre-link pass, it belongs in that bundle's prompt; tests here are anchored at the post-link orchestrator only. | link-stage-only | (unspecified) | Synthesized at a later IR pass than this bundle's `pipeline_stage` observes; the test belongs in the bundle whose pipeline stage matches. |
| The "Adding a new pass" workflow — developer guide, not a user-observable behavior. | process-doc | [#adding-a-new-pass](../../../design/pipeline/05-ir-passes.md#adding-a-new-pass) | Contributor walkthrough / process documentation, not a compiler behavior. |
| Differentiation passes (`autodiff-fwd`, `-rev`, `-transpose`, `-unzip`, `-cfg-norm`, `-loop-analysis`, `-pairs`, `-primal-hoist`, `-region`). The doc names the pass family but does not specify a per-pass user-observable consequence at a level this bundle can anchor cleanly without writing a wider autodiff test fixture. Recorded as a doc gap above. | requires-external-tool | [#autodiff-fwd](../../../design/pipeline/05-ir-passes.md#autodiff-fwd) | Verification would need a non-Slang tool the runner does not include (e.g., `unzip`, hex dumper, decompiler). |


## Doc gaps observed
| Anchor | Kind | Gap | Suggested addition |
| --- | --- | --- | --- |
| [#beforeafter](../../../design/pipeline/05-ir-passes.md#beforeafter) | undocumented-behavior | The "Specialization and generics" category lists `slang-ir-typeflow-specialize.cpp` and `slang-ir-typeflow-set.cpp` as "Type-flow set construction" / "Specialization based on type flow" but does not name a user-observable footprint of either pass — neither pass shows up in a `### BEFORE/AFTER` dump uniquely, and neither has a distinguishable emit-text consequence that a `.slang` test could anchor on. | A short example of what type-flow-based specialization changes (e.g. dynamic-dispatch call resolved via type-flow, observable as a removed `lookup_witness` in the dump) would unlock tests here. |
| [#type-and-value-legalization](../../../design/pipeline/05-ir-passes.md#type-and-value-legalization) | undocumented-behavior | The "Type and value legalization" category lists `slang-ir-any-value-marshalling.cpp` and `slang-ir-any-value-inference.cpp` as "Pack / unpack values into `AnyValue`" / "Determines `AnyValue` size for existentials". Both passes run only when `AnyValue` types appear in IR; the doc does not name a Slang-source-level construct that surfaces these in the emit. A pointer to a user-visible `AnyValue` spelling (or the surface of dynamic-dispatch through interfaces that triggers it) would unlock a per-pass test. |  |
| [#loop-transformations](../../../design/pipeline/05-ir-passes.md#loop-transformations) | undocumented-behavior | The "Loop transformations" category lists `slang-ir-uniformity.cpp` ("Uniformity / divergence analysis") as a pass but does not name a user-observable consequence. We could not anchor a test on uniformity output without fabricating one. | A short example of what changes — e.g. "a varying conditional triggers a `convergent` requirement in the SPIR-V emit" — would be testable. |
| [#loop-transformations](../../../design/pipeline/05-ir-passes.md#loop-transformations) | undocumented-behavior | The "Loop transformations" category also lists `slang-ir-synthesize-active-mask.cpp` with no user-visible footprint enumerated. Same gap as uniformity. |  |
| [#target-specific-lowering](../../../design/pipeline/05-ir-passes.md#target-specific-lowering) | undocumented-behavior | The "Target-specific lowering" category lists `slang-ir-lower-cuda-builtin-types.cpp` (CUDA only) but does not enumerate which Slang built-in types are transformed nor what the CUDA emit text looks like before vs after. | A short example (e.g. "Slang `RaytracingAccelerationStructure` becomes CUDA `OptixTraversableHandle`") would let us anchor a CUDA builtin-type lowering test. |
| [#specialization-and-generics](../../../design/pipeline/05-ir-passes.md#specialization-and-generics) | undocumented-behavior | The "Specialization and generics" category includes `slang-ir-specialize-resources.cpp` but does not name the observable post-condition. We anchored a smoke test on "resource argument inlines cleanly", but a stronger doc statement (e.g. "the resource is no longer passed as a parameter; it is referenced directly at the call site") would enable a sharper test. |  |
| [#differentiation-autodiff](../../../design/pipeline/05-ir-passes.md#differentiation-autodiff) | undocumented-behavior | The "Differentiation (autodiff)" category lists nine passes (`slang-ir-autodiff*.cpp`) but does not enumerate user-observable consequences per pass. Only `check-differentiability` has an observable surface here (a diagnostic). The other autodiff passes (`fwd`, `rev`, `transpose`, `unzip`, `cfg-norm`, `loop-analysis`, `pairs`, `primal-hoist`, `region`) would need per-pass "what the user sees" rows to be testable from a `.slang` file. A pointer to the `[Differentiable]` user surface alongside each row would unlock per-pass tests. |  |
| [#inlining-and-call-graph](../../../design/pipeline/05-ir-passes.md#inlining-and-call-graph) | undocumented-behavior | The "Inlining and call-graph" category lists `call-graph`, `reachability`, `propagate-func-properties`, `marshal-native-call`, `defer-buffer-load` — none of which name a user-observable consequence. They are analysis or marshalling helpers that this bundle could not anchor cleanly. A "footprint in emit text" row per pass would enable tests. |  |
| [#layout-and-binding](../../../design/pipeline/05-ir-passes.md#layout-and-binding) | undocumented-behavior | The "Layout and binding" category includes `user-type-hint`, `metadata`, `explicit-global-init` — the doc states the pass exists and what it does internally but not how to spot its output. `explicit-global-context` was the only one we could anchor cleanly (CUDA / C++ pointer threading). |  |
| [#loop-transformations](../../../design/pipeline/05-ir-passes.md#loop-transformations) | undocumented-behavior | The "Loop transformations" category lists six passes (`loop-unroll`, `loop-inversion`, `fuse-satcoop`, `restructure`, `restructure-scoping`, `synthesize-active-mask`, `uniformity`). Only `loop-unroll` is observably testable; the rest would need per-pass "what to look for in the dump or emit" prose. |  |
| [#out-of-scope](../../../design/pipeline/05-ir-passes.md#out-of-scope) | undocumented-behavior | The "Instrumentation" section names `coverage-instrument` / `finalize-coverage-metadata` as gated on `-trace-coverage-binding` and `-trace-coverage-reserved-space` — those flags' surface is not exercised here (recorded under `## Untested claims`). A default-on instrumentation pass without flag dependence would be testable; the doc could add such an example. |  |
| [#linking-and-validation](../../../design/pipeline/05-ir-passes.md#linking-and-validation) | undocumented-behavior | The "Linking and validation" category lists `slang-ir-validate.cpp` as "structural sanity checks". The doc names no user-observable symptom of validation failing; we could not produce a clean negative test for the validator itself. | A short example of a malformed IR construct that validation rejects would help. |
| [#the-pipeline-is-not-a-fixed-list](../../../design/pipeline/05-ir-passes.md#the-pipeline-is-not-a-fixed-list) | undocumented-behavior | The doc cites the `linkAndOptimizeIR` orchestration but says "the pipeline is **not** a fixed list" — the only observable ordering claim we could anchor was "DCE runs multiple times" (a weak claim that the orchestrator interleaves cleanup). Any pass-ordering invariant that the doc affirms (e.g. "specialize always runs before inline" or "validate runs first and last") would unlock ordering-based tests, but no such invariant is currently stated. |  |
| [#target-specific-lowering](../../../design/pipeline/05-ir-passes.md#target-specific-lowering) | undocumented-behavior | "Target-specific lowering" has `slang-ir-translate.cpp` listed as "Generic translation step used by some targets" — too vague to anchor a test. | Naming the specific surface (e.g. "Translate routes `printf` to OptiX-shaped trace calls") would help. |
