Repository: shader-slang/slang (2026-04-02T19:58:19Z)

Issue:
[dynamic dispatch] Interface-typed variable with dynamic dispatching enabled passed to a generic function triggers internal compiler error
# Issue Description
An internal compiler error is triggered with the following conditions:
- Generic parameter type is interface-typed variable; AND
- The variable has dynamic dispatching enabled.


# Reproducer Code

```slang
uint functionCallTest<T>(T obj)
{
    return 0;
}

interface ITest
{
}

struct ABC : ITest
{
}

struct DEF : ITest
{
}

ITest createITest(bool type)
{
    if (type)
        return ABC();
    else
        return DEF(); // Note: if this would return ABC(), the compiler error goes away
}

RWStructuredBuffer<uint> outputBuffer;

[numthreads(1,1,1)]
void computeMain(uint3 id : SV_DispatchThreadID)
{
    ITest testVariant = createITest(id.x == 1);
    outputBuffer[0] = functionCallTest(testVariant);
}
```
```
slangc  -target hlsl -stage compute -entry computeMain repro.slang
```

# Expected Behavior
The above code should compile. If, for some reason, interface-typed variable type should not be accepted as a generic parameter type, then the compiler should diagnose this as an error.

# Actual Behavior
```
slangc  -target hlsl -stage compute -entry computeMain repro.slang
(0): error 99999: Slang compilation aborted due to an exception of N5Slang13InternalErrorE: unexpected: Unexpected operand type for type-flow specialization of Call inst
For assistance, file an issue on GitHub (https://github.com/shader-slang/slang/issues) or join the Slang Discord (https://khr.io/slangdiscord)
```

# Environment
 - Slang Version: commit 939b4266261af77c2cdb20e0702fe2be98a60018

# Additional context
It is claimed that a generic parameter must not be an interface: https://github.com/shader-slang/slang/pull/10165#discussion_r2855397978 . However, the following compiles, so clearly this is not enforced by slangc:

```slang
uint doSomething<T>()
{
    return sizeof(T);
}

struct TestType
{
    IArithmetic i;
}

interface ITest
{
}

RWStructuredBuffer<uint> outputBuffer;

[numthreads(1,1,1)]
void computeMain(uint3 id : SV_DispatchThreadID)
{
    outputBuffer[0] = doSomething<TestType>();
    outputBuffer[1] = doSomething<int64_t>();
    outputBuffer[2] = doSomething<IArithmetic>();
    outputBuffer[3] = doSomething<ITest>();
}
```


Discussion:
Possible related issue (not claiming duplicate): #9934 has the same ICE text ('Unexpected operand type for type-flow specialization of Call inst') in a dynamic-dispatch + unconstrained-generic path.\n\n#9934 reproduces via an associated-type value flowing into a generic call, while this issue passes an interface-typed existential directly. These may be distinct repro shapes that converge in the same specialization/lowering path.
Note: I might consider interface-typed variables as variants where every alternative type conforms to the interface. Hence, the variant itself conforms to the interface. Ideally, an interface-typed variable would be declared as `Variant<IFace> var` rather than `IFace var`. With an approach such as this, there shouldn't be fundamental issues why interface or variant types couldn't be accepted as generic types.


> Possible related issue (not claiming duplicate): [#9934](https://github.com/shader-slang/slang/issues/9934) has the same ICE text ('Unexpected operand type for type-flow specialization of Call inst') in a dynamic-dispatch + unconstrained-generic path.\n\n#9934 reproduces via an associated-type value flowing into a generic call, while this issue passes an interface-typed existential directly. These may be distinct repro shapes that converge in the same specialization/lowering path.

Looks like a related issue, possibly duplicate. In this case, the associated type ITransform.Output requires dynamic dispatch on use, which possibly triggers the ICE.
> Looks like a related issue, possibly duplicate. In this case, the associated type ITransform.Output requires dynamic dispatch on use, which possibly triggers the ICE.

A good question is that why we have this restriction. I can't readily see why interface-typed variables would be special in this regard.



Patch:
diff --git a/source/slang/slang-ir-typeflow-specialize.cpp b/source/slang/slang-ir-typeflow-specialize.cpp
index 5142a3f4dfa..24b1ff5f28f 100644
--- a/source/slang/slang-ir-typeflow-specialize.cpp
+++ b/source/slang/slang-ir-typeflow-specialize.cpp
@@ -3671,10 +3671,11 @@ struct TypeFlowSpecializationContext
                             return;
                         }
 
-                        specializedSet.add(builder.emitSpecializeInst(
+                        auto newSpec = builder.emitSpecializeInst(
                             typeOfSpecialization,
                             arg,
-                            specializationArgs));
+                            specializationArgs);
+                        specializedSet.add(newSpec);
                     });
             }
             else
@@ -3682,8 +3683,9 @@ struct TypeFlowSpecializationContext
                 // Concrete case..
                 IRBuilder builder(module);
                 builder.setInsertInto(module);
-                specializedSet.add(
-                    builder.emitSpecializeInst(typeOfSpecialization, operand, specializationArgs));
+                auto newSpec =
+                    builder.emitSpecializeInst(typeOfSpecialization, operand, specializationArgs);
+                specializedSet.add(newSpec);
             }
 
             IRBuilder builder(module);
@@ -5044,6 +5046,9 @@ struct TypeFlowSpecializationContext
         HashSet<IRInst*> processedSet;
         while (globalWorkList.hasItems())
         {
+            if (sink->getErrorCount() > 0)
+                break;
+
             auto globalInst = globalWorkList.dequeue();
 
             switch (globalInst->getOp())
@@ -5057,6 +5062,10 @@ struct TypeFlowSpecializationContext
                     hasChanges |= removeAnnotations(func);
                     hasChanges |= eliminateDeadCode(func);
                     hasChanges |= specializeFunc(func, globalWorkList);
+
+                    if (sink->getErrorCount() > 0)
+                        break;
+
                     hasChanges |= eliminateDeadCode(func);
                     hasChanges |= resolveTypesInFunc(func);
 
@@ -6339,12 +6348,18 @@ struct TypeFlowSpecializationContext
                     globalsWorkList.enqueue(callee);
                 }
             }
+            else if (auto specCallee = as<IRSpecialize>(callee))
+            {
+                // The callee is an IRSpecialize that escaped both the dispatch-action
+                // and set-specialized-generic resolution paths. This happens when an
+                // existential type flows into an unconstrained generic (no interface
+                // constraint), so the typeflow pass cannot generate dispatch code.
+                emitExistentialSpecializationDiagnostic(specCallee, inst->sourceLoc, inst);
+                module->getContainerPool().free(&callArgs);
+                return false;
+            }
             else
             {
-                // If we reach here, then something is wrong. Our callee is an inst of
-                // tag-type, but we could not resolve it through the dispatch action
-                // collection or set-specialized generic paths.
-                //
                 SLANG_UNEXPECTED(
                     "Unexpected operand type for type-flow specialization of Call inst");
             }
@@ -7614,7 +7629,8 @@ struct TypeFlowSpecializationContext
 
         if (sink->getErrorCount() > 0)
         {
-            // If there were errors during propagation, we bail out early.
+            // If there are any diagnostics after propagation, don't continue into
+            // the mutating lowering phase.
             return false;
         }
 


Test patch:
diff --git a/tests/language-feature/dynamic-dispatch/diagnose-specialize-unconstrained-generic-with-assoc-type.slang b/tests/language-feature/dynamic-dispatch/diagnose-specialize-unconstrained-generic-with-assoc-type.slang
index cf0e8302b46..92b387a09d1 100644
--- a/tests/language-feature/dynamic-dispatch/diagnose-specialize-unconstrained-generic-with-assoc-type.slang
+++ b/tests/language-feature/dynamic-dispatch/diagnose-specialize-unconstrained-generic-with-assoc-type.slang
@@ -1,13 +1,12 @@
 // When the return value of an interface method (whose type is an associated
 // type) is passed to an unconstrained generic function, the type parameter
-// cannot be statically resolved. The compiler should emit error 33180, but
-// currently produces an internal compiler error (99999) instead.
+// cannot be statically resolved. The compiler must emit error 33180 instead
+// of crashing.
 
-// 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
 
@@ -45,7 +44,8 @@ void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
 {
     ITransform obj = factory(dispatchThreadID.x);
     var result = obj.transform(1.0);
-    // CHECK: ([[# @LINE+1]]): error 33180
     let processed = process(result);
+//CHECK:                   ^ cannot specialize generic with existential type
+//CHECK:                   ^ specializing 'process' with an existential type is not allowed. All generic arguments must be statically resolvable at compile time.
     outputBuffer[0] = 0;
 }
diff --git a/tests/language-feature/dynamic-dispatch/diagnose-unconstrained-generic-func.slang b/tests/language-feature/dynamic-dispatch/diagnose-unconstrained-generic-func.slang
index add20ddf486..f62c34ed825 100644
--- a/tests/language-feature/dynamic-dispatch/diagnose-unconstrained-generic-func.slang
+++ b/tests/language-feature/dynamic-dispatch/diagnose-unconstrained-generic-func.slang
@@ -1,18 +1,13 @@
 // An unconstrained generic function cannot be specialized with an
 // existential type because the concrete type is not known at compile
 // time. When the generic type parameter T is inferred from an
-// interface-typed argument, the compiler must reject the call.
-//
-// Unlike the constrained case (T : IFoo), where the compiler
-// recognizes the pattern and emits error 33180, unconstrained generics
-// currently crash with an internal compiler error (99999) during
-// type-flow specialization instead of producing a diagnostic.
-
-// 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
+// interface-typed argument, the compiler must reject the call with
+// error 33180 instead of crashing.
+
+//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
 
@@ -51,10 +46,9 @@ void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
 {
     IFoo obj = makeInterface(dispatchThreadID.x);
 
-    // Inferring T from an interface-typed argument requires specializing
-    // the generic with the existential type, which is not allowed.
-    // CHECK: ([[# @LINE+1]]): error 33180
     IFoo result = identity(obj);
+//CHECK:                  ^ cannot specialize generic with existential type
+//CHECK:                  ^ specializing 'identity' with an existential type is not allowed. All generic arguments must be statically resolvable at compile time.
 
     outputBuffer[0] = result.calc(3.0);
 }