Repository: shader-slang/slang (2026-03-30T13:06:56Z)

Issue:
[dynamic dispatch] Explicit generic specialization with interface type crashes with error 99999
## Issue Description

Explicitly specializing a constrained generic function with an interface type — `genericFunc<IFoo>(obj, 3.0)` — crashes the compiler with `error 99999: Unexpected context type for parameter info retrieval`.

This is a different crash from #10263 (which uses implicit inference). When the **same** function is called with **implicit** inference (`genericFunc(obj, 3.0)`), the compiler correctly emits `error 33180`. The explicit specialization path hits a separate unhandled code path.

## Reproducer Code

```slang
interface IFoo
{
    float calc(float x);
}

struct A : IFoo { float calc(float x) { return x * 2.0; } }
struct B : IFoo { float calc(float x) { return x + 10.0; } }

float genericFunc<T : IFoo>(T obj, float x)
{
    return obj.calc(x);
}

IFoo makeObj(int id)
{
    if (id == 0) return A();
    else return B();
}

RWStructuredBuffer<float> outputBuffer;

[numthreads(1, 1, 1)]
void computeMain(int id : SV_DispatchThreadID)
{
    IFoo obj = makeObj(id);
    float result = genericFunc<IFoo>(obj, 3.0);   // CRASH
    outputBuffer[0] = result;
}
```

Command line:
```bash
slangc repro.slang -target hlsl -stage compute -entry computeMain
```

## Expected Behavior

The compiler emits `error 33180` (consistent with the implicit inference case), since `genericFunc(obj, 3.0)` already produces that diagnostic.

## Actual Behavior

```
(0): error 99999: Slang compilation aborted due to an exception of N5Slang13InternalErrorE: unexpected: Unexpected context type for parameter info retrieval
```

The crash occurs on all targets (spirv, hlsl, glsl).

Note: Changing to implicit inference — `genericFunc(obj, 3.0)` instead of `genericFunc<IFoo>(obj, 3.0)` — correctly produces:
```
error 33180: specializing '...' with an existential type is not allowed. All generic arguments must be statically resolvable at compile time.
```

## Environment
- Slang Version: commit 6ffecd378 (master, 2026-02-26)
- OS: Linux 6.14.0-37-generic x86_64 (Ubuntu 24.04)

## Additional Context

Found during issue #10290 testing. Related test file: `tests/language-feature/dynamic-dispatch/diagnose-explicit-specialize-with-interface.slang`.

This could be filed as a standalone issue or added as a note on #10263 since both involve interface types flowing into generic parameters.


Discussion:
Issues https://github.com/shader-slang/slang/issues/10293 and https://github.com/shader-slang/slang/issues/10263 are NOT fixed by https://github.com/shader-slang/slang/pull/9808, but the typeflow-specialize defensive guards in this PR will conflict with https://github.com/shader-slang/slang/pull/9808's massive rewrite of that file. Per reviewer feedback, the typeflow changes are in the wrong place and remove sanity-check assertions.



Patch:
diff --git a/source/slang/slang-emit.cpp b/source/slang/slang-emit.cpp
index f68f249760e..08627bc20f8 100644
--- a/source/slang/slang-emit.cpp
+++ b/source/slang/slang-emit.cpp
@@ -1160,6 +1160,9 @@ Result linkAndOptimizeIR(
         SLANG_PASS(specializeModule, targetProgram, codeGenContext->getSink(), specOptions);
     }
 
+    if (sink->getErrorCount() != 0)
+        return SLANG_FAIL;
+
     if (requiredLoweringPassSet.higherOrderFunc)
     {
         SLANG_PASS(specializeHigherOrderParameters, codeGenContext);
diff --git a/source/slang/slang-ir-check-specialize-generic-with-existential.cpp b/source/slang/slang-ir-check-specialize-generic-with-existential.cpp
index 7f903600bb2..c08dc10259e 100644
--- a/source/slang/slang-ir-check-specialize-generic-with-existential.cpp
+++ b/source/slang/slang-ir-check-specialize-generic-with-existential.cpp
@@ -12,8 +12,11 @@ namespace Slang
 class DiagnosticSink;
 struct IRModule;
 
-// Recursively visit the entire module, and diagnose an error whenever an ExtractExistentialType is
-// being used as a specialization argument to a generic function or type.
+// Recursively visit the entire module and mark IRSpecialize instructions that use an existential
+// or interface type as a specialization argument. Recognized argument patterns include
+// ExtractExistentialType, ExtractExistentialWitnessTable, MakeExistential, InterfaceType, and
+// TypeEqualityWitness whose first operand is an InterfaceType.
+// The actual diagnostic is emitted later by the typeflow or specialize passes.
 void addDecorationsForGenericsSpecializedWithExistentialsRec(IRInst* parent, DiagnosticSink* sink)
 {
     if (auto code = as<IRGlobalValueWithCode>(parent))
@@ -35,12 +38,17 @@ void addDecorationsForGenericsSpecializedWithExistentialsRec(IRInst* parent, Dia
                     case kIROp_ExtractExistentialWitnessTable:
                     case kIROp_MakeExistential:
                         {
-                            IRInst* specializationBase = specialize->getBase();
-                            if (auto generic = as<IRGeneric>(specializationBase))
-                                specializationBase = findInnerMostGenericReturnVal(generic);
-                            if (auto lookupWitness =
-                                    as<IRLookupWitnessMethod>(specialize->getBase()))
-                                specializationBase = lookupWitness->getRequirementKey();
+                            IRBuilder builder(parent->getModule());
+                            builder.addDecoration(
+                                specialize,
+                                kIROp_DisallowSpecializationWithExistentialsDecoration);
+                            goto nextInst;
+                        }
+                    case kIROp_TypeEqualityWitness:
+                        {
+                            if (!as<IRInterfaceType>(specArg->getOperand(0)))
+                                continue;
+
                             IRBuilder builder(parent->getModule());
                             builder.addDecoration(
                                 specialize,
diff --git a/source/slang/slang-ir-specialize.cpp b/source/slang/slang-ir-specialize.cpp
index 41e467d4ce0..97abb647b99 100644
--- a/source/slang/slang-ir-specialize.cpp
+++ b/source/slang/slang-ir-specialize.cpp
@@ -672,9 +672,37 @@ struct SpecializationContext
         // version of the result of the generic (a specialized
         // type, function, or whatever).
         //
+        auto errorsBefore = sink ? sink->getErrorCount() : 0;
         auto specializedVal = specializeGeneric(genericVal, specInst);
         if (!specializedVal)
-            return false;
+        {
+            // When specialization fails due to an invalid existential specialization
+            // (e.g. genericFunc<IFoo>(...) with a missing witness table argument),
+            // emit a diagnostic and replace the specialize instruction with a poison
+            // value so that subsequent passes don't crash on the unresolved instruction.
+            // Only emit E33180 if no other diagnostic was already produced (e.g. by
+            // the specialization depth budget check).
+            if (sink && sink->getErrorCount() == errorsBefore)
+            {
+                String genericName;
+                if (auto inner = findGenericReturnVal(genericVal))
+                    if (auto nameHint = inner->findDecoration<IRNameHintDecoration>())
+                        genericName = nameHint->getName();
+                if (genericName.getLength() == 0)
+                    genericName = "<generic>";
+                sink->diagnose(Diagnostics::CannotSpecializeGenericWithExistential{
+                    .generic = genericName,
+                    .location = specInst->sourceLoc});
+            }
+            // Replace the specialize instruction with a poison value to prevent
+            // later passes from encountering an unresolvable specialize instruction.
+            IRBuilder builder(module);
+            builder.setInsertBefore(specInst);
+            auto poison = builder.emitPoison(specInst->getFullType());
+            specInst->replaceUsesWith(poison);
+            specInst->removeAndDeallocate();
+            return true;
+        }
 
         // Any uses of this `specialize(...)` instruction will
         // become uses of `specializeVal`, so we want to re-consider
@@ -1815,6 +1843,15 @@ struct SpecializationContext
                 //
                 while (workList.getCount() != 0)
                 {
+                    // If we've emitted errors (e.g. from diagnosing an invalid
+                    // existential specialization), bail out early to avoid
+                    // processing IR that may be in an inconsistent state.
+                    if (sink && sink->getErrorCount() != 0)
+                    {
+                        workList.clear();
+                        break;
+                    }
+
                     IRInst* inst = workList.getLast();
 
                     workList.removeLast();
@@ -1865,6 +1902,9 @@ struct SpecializationContext
                     break;
             }
 
+            if (sink && sink->getErrorCount() != 0)
+                break;
+
             if (iterChanged)
             {
                 this->changed = true;
@@ -1875,6 +1915,9 @@ struct SpecializationContext
                 unrollLoopsInModule(module, targetProgram, sink);
             }
 
+            if (sink && sink->getErrorCount() != 0)
+                break;
+
             // Once the work list has gone dry, we should have the invariant
             // that there are no `specialize` instructions inside of non-generic
             // functions that in turn reference a generic type/function unless the generic is
@@ -3879,6 +3922,18 @@ IRInst* specializeGenericImpl(
     // be initializing our environment to map `T -> a`, `U -> b`,
     // and `V -> c`.
     //
+    // When a generic is explicitly specialized with an interface type (e.g.
+    // genericFunc<IFoo>(...)), the specialize instruction may have fewer arguments
+    // than the generic has parameters (the witness-table argument is missing).
+    // Return nullptr; the caller handles diagnostics and cleanup.
+    {
+        UInt paramCount = 0;
+        for ([[maybe_unused]] auto param : genericVal->getParams())
+            paramCount++;
+        if (specializeInst->getArgCount() < paramCount)
+            return nullptr;
+    }
+
     UInt argCounter = 0;
     for (auto param : genericVal->getParams())
     {
diff --git a/source/slang/slang-ir-typeflow-specialize.cpp b/source/slang/slang-ir-typeflow-specialize.cpp
index a42d4a61cf1..5142a3f4dfa 100644
--- a/source/slang/slang-ir-typeflow-specialize.cpp
+++ b/source/slang/slang-ir-typeflow-specialize.cpp
@@ -191,6 +191,83 @@ bool isNoneCallee(IRInst* callee)
     return false;
 }
 
+IRInst* getInvalidExistentialSpecializationTarget(IRInst* specializedValue)
+{
+    IRInst* specializationBase = specializedValue;
+    if (auto specialize = as<IRSpecialize>(specializationBase))
+        specializationBase = specialize->getBase();
+
+    if (auto generic = as<IRGeneric>(specializationBase))
+        if (auto retVal = findInnerMostGenericReturnVal(generic))
+            specializationBase = retVal;
+
+    if (auto lookupWitness = as<IRLookupWitnessMethod>(specializationBase))
+        specializationBase = lookupWitness->getRequirementKey();
+
+    return specializationBase;
+}
+
+bool isInvalidExistentialSpecialization(IRInst* specializedValue)
+{
+    if (specializedValue->findDecoration<IRDisallowSpecializationWithExistentialsDecoration>())
+        return true;
+
+    // The decoration pass marks IRSpecialize instructions, but specializeModule() may
+    // resolve the specialization before the typeflow pass runs, consuming the decorated
+    // IRSpecialize and leaving behind a concrete function that contains the
+    // TypeEqualityWitness + LookupWitnessMethod pattern in its body. This body scan
+    // catches those post-resolution cases.
+    if (auto func = as<IRFunc>(specializedValue))
+    {
+        for (auto block = func->getFirstBlock(); block; block = block->getNextBlock())
+        {
+            for (auto inst : block->getOrdinaryInsts())
+            {
+                auto call = as<IRCall>(inst);
+                if (!call)
+                    continue;
+
+                auto lookupWitness = as<IRLookupWitnessMethod>(call->getCallee());
+                if (!lookupWitness)
+                    continue;
+
+                auto typeEqualityWitness = as<IRInst>(lookupWitness->getWitnessTable());
+                if (typeEqualityWitness &&
+                    typeEqualityWitness->getOp() == kIROp_TypeEqualityWitness &&
+                    as<IRInterfaceType>(typeEqualityWitness->getOperand(0)))
+                {
+                    return true;
+                }
+            }
+        }
+    }
+
+    auto specialize = as<IRSpecialize>(specializedValue);
+    if (!specialize)
+        return false;
+
+    for (UInt i = 0; i < specialize->getArgCount(); ++i)
+    {
+        auto arg = specialize->getArg(i);
+        switch (arg->getOp())
+        {
+        case kIROp_InterfaceType:
+        case kIROp_ExtractExistentialType:
+        case kIROp_ExtractExistentialWitnessTable:
+        case kIROp_MakeExistential:
+            return true;
+        case kIROp_TypeEqualityWitness:
+            if (as<IRInterfaceType>(arg->getOperand(0)))
+                return true;
+            break;
+        default:
+            break;
+        }
+    }
+
+    return false;
+}
+
 // Represents an interprocedural edge between call sites and functions
 struct InterproceduralEdge
 {
@@ -1434,11 +1511,14 @@ struct TypeFlowSpecializationContext
         // Global worklist for interprocedural analysis.
         WorkQueue<WorkItem> workQueue;
 
-        // Add all global functions to worklist.
+        // Add all global functions to worklist. Functions that are invalid existential
+        // specializations are skipped here; the diagnostic for them is emitted when
+        // discoverContext encounters the corresponding IRSpecialize context or when
+        // propagateToCallSite/specializeCall resolves the callee.
         //
         for (auto inst : module->getGlobalInsts())
             if (auto func = as<IRFunc>(inst))
-                if (isEntryPoint(func))
+                if (isEntryPoint(func) && !isInvalidExistentialSpecialization(func))
                     discoverContext(func, workQueue);
 
         // Process until fixed point.
@@ -3955,6 +4035,15 @@ struct TypeFlowSpecializationContext
             case kIROp_Specialize:
                 {
                     auto specialize = cast<IRSpecialize>(context);
+                    if (isInvalidExistentialSpecialization(specialize))
+                    {
+                        emitExistentialSpecializationDiagnostic(
+                            specialize,
+                            specialize->sourceLoc,
+                            specialize);
+                        return;
+                    }
+
                     auto generic = cast<IRGeneric>(specialize->getBase());
                     func = cast<IRFunc>(findGenericReturnVal(generic));
 
@@ -4066,6 +4155,12 @@ struct TypeFlowSpecializationContext
         HashSet<IRInst*> calleeSet;
         auto propagateToCallSite = [&](IRInst* callee)
         {
+            if (isInvalidExistentialSpecialization(callee))
+            {
+                emitExistentialSpecializationDiagnostic(callee, inst->sourceLoc, inst);
+                return;
+            }
+
             if (as<IRUnboundedFuncElement>(callee))
             {
                 // An unbounded element represents an unknown function,
@@ -5735,8 +5830,11 @@ struct TypeFlowSpecializationContext
         {
             if (isGlobalInst(specializeInst))
             {
+                auto specialized = specializeGeneric(specializeInst);
+                if (!specialized)
+                    return callee;
                 IRBuilder builder(module);
-                return builder.replaceOperand(&callee->typeUse, specializeGeneric(specializeInst));
+                return builder.replaceOperand(&callee->typeUse, specialized);
             }
         }
 
@@ -6117,6 +6215,16 @@ struct TypeFlowSpecializationContext
         if (isNoneCallee(callee))
             return false;
 
+        // Check for invalid existential specialization (e.g. specialize with
+        // interface type argument) before resolving the callee. These callees
+        // may have dangling operands after the specialize pass lowered their
+        // witness-lookup bases.
+        if (isInvalidExistentialSpecialization(callee))
+        {
+            emitExistentialSpecializationDiagnostic(callee, inst->sourceLoc, inst);
+            return false;
+        }
+
         if (isGlobalInst(callee))
             callee = translationContext.resolveInst(callee);
 
@@ -6209,24 +6317,14 @@ struct TypeFlowSpecializationContext
                 IRBuilder builder(module);
                 builder.setInsertInto(module);
 
-                // Check if the original callee inst has a dis-allow existential specialization
-                // decoration.
+                // Check both the specialize instruction and the selected callee for
+                // disallowed existential specialization.
                 //
-                if (inst->getCallee()
-                        ->findDecoration<IRDisallowSpecializationWithExistentialsDecoration>())
+                auto selectedCallee = setTag->getSet()->getElement(0);
+                if (isInvalidExistentialSpecialization(callee) ||
+                    isInvalidExistentialSpecialization(selectedCallee))
                 {
-                    // In Slang 2025 and later, specializing a generic with multiple types is
-                    // not allowed, so we'll throw a diagnostic message.
-                    //
-                    auto genericBase = as<IRSpecialize>(callee)->getBase();
-                    String genericName;
-                    if (auto nameHint = genericBase->findDecoration<IRNameHintDecoration>())
-                        genericName = nameHint->getName();
-                    else
-                        genericName = "<generic>";
-                    sink->diagnose(Diagnostics::CannotSpecializeGenericWithExistential{
-                        .generic = genericName,
-                        .location = inst->sourceLoc});
+                    emitExistentialSpecializationDiagnostic(callee, inst->sourceLoc, inst);
                     module->getContainerPool().free(&callArgs);
                     return false;
                 }
@@ -6236,7 +6334,7 @@ struct TypeFlowSpecializationContext
                     // Add in the arguments for the set specialization.
                     //
                     addArgsForSetSpecializedGeneric(cast<IRSpecialize>(callee), callArgs);
-                    callee = setTag->getSet()->getElement(0);
+                    callee = selectedCallee;
                     effectiveFuncType = getEffectiveFuncType(callee);
                     globalsWorkList.enqueue(callee);
                 }
@@ -7553,10 +7651,37 @@ struct TypeFlowSpecializationContext
     // to avoid emitting duplicate diagnostics per call edge.
     HashSet<IRInst*> diagnosedRefParams;
 
+    // Set of call sites/contexts already diagnosed for invalid existential specialization,
+    // to avoid duplicate diagnostics when instructions are revisited during propagation.
+    HashSet<IRInst*> diagnosedExistentialSpecializationSites;
+
     // Set of bit-cast instructions already diagnosed for unsupported
     // non-concrete result types during propagation.
     HashSet<IRInst*> diagnosedBitCasts;
 
+    // Emit error 33180 for an invalid existential specialization, deduplicating by `dedupKey`.
+    // Returns true if the diagnostic was emitted (first time for this key).
+    bool emitExistentialSpecializationDiagnostic(
+        IRInst* specializedValue,
+        SourceLoc location,
+        IRInst* dedupKey)
+    {
+        if (!diagnosedExistentialSpecializationSites.add(dedupKey))
+            return false;
+
+        String genericName;
+        auto diagnosticTarget = getInvalidExistentialSpecializationTarget(specializedValue);
+        if (auto nameHint = diagnosticTarget->findDecoration<IRNameHintDecoration>())
+            genericName = nameHint->getName();
+        if (genericName.getLength() == 0)
+            genericName = "<generic>";
+
+        sink->diagnose(Diagnostics::CannotSpecializeGenericWithExistential{
+            .generic = genericName,
+            .location = location});
+        return true;
+    }
+
     // Mapping from (context, inst) --> propagated info
     Dictionary<InstWithContext, IRWeakUse*> propagationMap;
 


Test patch:
diff --git a/tests/language-feature/dynamic-dispatch/diagnose-explicit-specialize-method-with-interface.slang b/tests/language-feature/dynamic-dispatch/diagnose-explicit-specialize-method-with-interface.slang
new file mode 100644
index 00000000000..d67c0527ae9
--- /dev/null
+++ b/tests/language-feature/dynamic-dispatch/diagnose-explicit-specialize-method-with-interface.slang
@@ -0,0 +1,51 @@
+// Explicitly specializing a generic interface method with an interface
+// type should be rejected with the same existential-specialization
+// diagnostic as the implicit form. Previously this crashed in typeflow.
+
+//DIAGNOSTIC_TEST:SIMPLE(diag=CHECK): -target spirv -conformance "B:IBar" -conformance "Foo:IFoo" -conformance "AnotherFoo:IFoo"
+//DIAGNOSTIC_TEST:SIMPLE(diag=CHECK): -target hlsl -stage compute -entry computeMain -conformance "B:IBar" -conformance "Foo:IFoo" -conformance "AnotherFoo:IFoo"
+//DIAGNOSTIC_TEST:SIMPLE(diag=CHECK): -target glsl -stage compute -entry computeMain -conformance "B:IBar" -conformance "Foo:IFoo" -conformance "AnotherFoo:IFoo"
+//DIAGNOSTIC_TEST:SIMPLE(diag=CHECK): -target metal -stage compute -entry computeMain -conformance "B:IBar" -conformance "Foo:IFoo" -conformance "AnotherFoo:IFoo"
+
+#lang slang 2025
+
+interface IFoo
+{
+    void doSomething();
+}
+
+struct Foo : IFoo
+{
+    void doSomething() {}
+}
+
+struct AnotherFoo : IFoo
+{
+    void doSomething() {}
+}
+
+interface IBar
+{
+    int eval<T : IFoo>(T t);
+}
+
+export struct B : IBar
+{
+    int eval<T : IFoo>(T t)
+    {
+        return 0;
+    }
+}
+
+RWStructuredBuffer<int> outputBuffer;
+
+[numthreads(1, 1, 1)]
+void computeMain(int id : SV_DispatchThreadID)
+{
+    IBar b = createDynamicObject<IBar>(id, 0);
+    IFoo df = createDynamicObject<IFoo>(id, 0);
+    int result = b.eval<IFoo>(df);
+//CHECK:                     ^ error E33180
+//CHECK:                     ^ specializing 'B.eval' with an existential type is not allowed.
+    outputBuffer[0] = result;
+}
diff --git a/tests/language-feature/dynamic-dispatch/diagnose-explicit-specialize-with-interface.slang b/tests/language-feature/dynamic-dispatch/diagnose-explicit-specialize-with-interface.slang
index c312f58be73..2cd68c883da 100644
--- a/tests/language-feature/dynamic-dispatch/diagnose-explicit-specialize-with-interface.slang
+++ b/tests/language-feature/dynamic-dispatch/diagnose-explicit-specialize-with-interface.slang
@@ -1,14 +1,13 @@
 // Explicitly specializing a constrained generic function with an
 // interface type should be rejected with the same diagnostic as
-// implicit inference (error 33180). Currently crashes with an
+// implicit inference (error 33180). Previously crashed with an
 // internal compiler error (99999) on a different code path than
 // the implicit case in diagnose-generic-func-with-interface.slang.
 
-// Disabled: ICE -- should emit error 33180 but crashes with error 99999
-//DISABLE_TEST:SIMPLE(filecheck=CHECK): -target spirv
-//DISABLE_TEST:SIMPLE(filecheck=CHECK): -target hlsl -stage compute -entry computeMain
-//DISABLE_TEST:SIMPLE(filecheck=CHECK): -target glsl -stage compute -entry computeMain
-//DISABLE_TEST:SIMPLE(filecheck=CHECK): -target metal -stage compute -entry computeMain
+//DIAGNOSTIC_TEST:SIMPLE(diag=CHECK): -target spirv
+//DIAGNOSTIC_TEST:SIMPLE(diag=CHECK): -target hlsl -stage compute -entry computeMain
+//DIAGNOSTIC_TEST:SIMPLE(diag=CHECK): -target glsl -stage compute -entry computeMain
+//DIAGNOSTIC_TEST:SIMPLE(diag=CHECK): -target metal -stage compute -entry computeMain
 
 #lang slang 2025
 
@@ -47,10 +46,9 @@ void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
 {
     IFoo obj = makeInterface(dispatchThreadID.x);
 
-    // Explicit specialization with an interface type should be rejected
-    // the same way implicit inference is (error 33180).
-    // CHECK: ([[# @LINE+1]]): error[E33180]
     float result = genericFunc<IFoo>(obj, 3.0);
+//CHECK:                            ^ error E33180
+//CHECK:                            ^ specializing 'genericFunc' with an existential type is not allowed.
 
     outputBuffer[0] = result;
 }
diff --git a/tests/language-feature/generics/diagnose-mock-tile-shape-pack-ops-specialize.slang b/tests/language-feature/generics/diagnose-mock-tile-shape-pack-ops-specialize.slang
index ef7b67c204f..4c482abadf1 100644
--- a/tests/language-feature/generics/diagnose-mock-tile-shape-pack-ops-specialize.slang
+++ b/tests/language-feature/generics/diagnose-mock-tile-shape-pack-ops-specialize.slang
@@ -1,12 +1,12 @@
-//DIAGNOSTIC_TEST:SIMPLE(diag=LATE_CONCAT_NON_AXIS,non-exhaustive): -target spirv -entry computeMain -stage compute -DLATE_CONCAT_NON_AXIS
-//DIAGNOSTIC_TEST:SIMPLE(diag=LATE_CONCAT_RANK,non-exhaustive): -target spirv -entry computeMain -stage compute -DLATE_CONCAT_RANK
-//DIAGNOSTIC_TEST:SIMPLE(diag=LATE_CONCAT_AXIS,non-exhaustive): -target spirv -entry computeMain -stage compute -DLATE_CONCAT_AXIS
-//DIAGNOSTIC_TEST:SIMPLE(diag=LATE_PERMUTE_LENGTH,non-exhaustive): -target spirv -entry computeMain -stage compute -DLATE_PERMUTE_LENGTH
-//DIAGNOSTIC_TEST:SIMPLE(diag=LATE_PERMUTE_DUP,non-exhaustive): -target spirv -entry computeMain -stage compute -DLATE_PERMUTE_DUP
-//DIAGNOSTIC_TEST:SIMPLE(diag=LATE_PERMUTE_RANGE,non-exhaustive): -target spirv -entry computeMain -stage compute -DLATE_PERMUTE_RANGE
-//DIAGNOSTIC_TEST:SIMPLE(diag=LATE_SWAP_AXIS,non-exhaustive): -target spirv -entry computeMain -stage compute -DLATE_SWAP_AXIS
-//DIAGNOSTIC_TEST:SIMPLE(diag=LATE_REDUCE_AXIS,non-exhaustive): -target spirv -entry computeMain -stage compute -DLATE_REDUCE_AXIS
-//DIAGNOSTIC_TEST:SIMPLE(diag=LATE_SWAP_SAME_AXIS,non-exhaustive): -target spirv -entry computeMain -stage compute -DLATE_SWAP_SAME_AXIS
+//DIAGNOSTIC_TEST:SIMPLE(diag=LATE_CONCAT_NON_AXIS): -target spirv -entry computeMain -stage compute -DLATE_CONCAT_NON_AXIS
+//DIAGNOSTIC_TEST:SIMPLE(diag=LATE_CONCAT_RANK): -target spirv -entry computeMain -stage compute -DLATE_CONCAT_RANK
+//DIAGNOSTIC_TEST:SIMPLE(diag=LATE_CONCAT_AXIS): -target spirv -entry computeMain -stage compute -DLATE_CONCAT_AXIS
+//DIAGNOSTIC_TEST:SIMPLE(diag=LATE_PERMUTE_LENGTH): -target spirv -entry computeMain -stage compute -DLATE_PERMUTE_LENGTH
+//DIAGNOSTIC_TEST:SIMPLE(diag=LATE_PERMUTE_DUP): -target spirv -entry computeMain -stage compute -DLATE_PERMUTE_DUP
+//DIAGNOSTIC_TEST:SIMPLE(diag=LATE_PERMUTE_RANGE): -target spirv -entry computeMain -stage compute -DLATE_PERMUTE_RANGE
+//DIAGNOSTIC_TEST:SIMPLE(diag=LATE_SWAP_AXIS): -target spirv -entry computeMain -stage compute -DLATE_SWAP_AXIS
+//DIAGNOSTIC_TEST:SIMPLE(diag=LATE_REDUCE_AXIS): -target spirv -entry computeMain -stage compute -DLATE_REDUCE_AXIS
+//DIAGNOSTIC_TEST:SIMPLE(diag=LATE_SWAP_SAME_AXIS): -target spirv -entry computeMain -stage compute -DLATE_SWAP_SAME_AXIS
 #lang slang 2026
 
 struct ShapeInfo<let each D : int>