Problems around associating a value with a type
Take the following example (tests/experiments/generic/type-to-value-4.slang)
RWStructuredBuffer<int> outputBuffer;
enum class Enum
{
A, B
};
interface IGetE
{
static Enum getE();
};
public struct A : IGetE
{
static Enum getE() { return Enum::A; }
};
public struct B : IGetE
{
static Enum getE() { return Enum::B; }
};
[numthreads(4, 1, 1)]
void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
{
int index = dispatchThreadID.x;
B b;
IGetE g = b;
let e = g.getE();
outputBuffer[dispatchThreadID.x] = int(e);
}
This doesn't work without at least 'public' on struct B. This is necessary such that those implementations of IGetE are visible for dynamic dispatch.
Otherwise the error
tests/experiments/generic/type-to-value-4.slang(21): error 50100: No type conformances are found for interface 'IGetE'. Code generation for current target requires at least one implementation type present in the linkage. interface IGetE ^~~~~
This is somewhat 'surprising' because the code explicitly instantiates B, so why is it necessary to make it public?
If we make both struct A and struct B public, the generated code uses dynamic dispatch in order to implement e.getE(). When it does this it introduces
struct Tuple_0
{
uint2 value0_0;
uint2 value1_0;
AnyValue16 value2_0;
};
Tuple_0 _S1;
[numthreads(4, 1, 1)]
void computeMain(uint3 dispatchThreadID_0 : SV_DISPATCHTHREADID)
{
int _S7 = U_S3tu05IGetE4getEp0p3tu04Enum_0(_S1.value1_0);
// ...
}
But _S1 is never initialized (in HLSL it becomes a Global in a constant buffer). Why does creating a local variable produce something in a global which now needs binding/initialization?
Moreover it is perhaps surprising that dynamic dispatch is used in the example, and in doing so is less efficient/more complex than it needs to be.
There's really a few issues here (and we might need to split this so we can track them individually).
A Dynamic-Dispatch Lowering Bug?
@csyonghe I think the most important thing to deal with on this issue is to resolve whether there is indeed a codegen bug relate to dynamic dispatch in that last example @jsmall-nvidia posted. My assumption is that the IGetE g = b part should have generated an existential with the data of b and the ID for the conformance B : IGetE. The fact that a global variable (or shader parameter?) of tuple type is getting introduced doesn't seem right.
A Specialization Optimization Bug?
The second issue that seems clear to me is that this code should have been able to yield fully statically-specialized output. There is no question at the IR level of what the concrete type inside the existential g is, and we should have thus been able to specialize the callee to the type and witness table in the existential value.
We definitely want to keep working on our optimizations for the existential case, since users seem inclined to lean on it more and more.
A Design Question
The final issue is the high-level question of why public is needed to get the code to compile, because it sure seems like it should Just Work.
This is slightly tangled up with the previous item, since my strong opinion is:
-
For any code where our IR optimization passes can fully specialize it such that dynamic dispatch is no longer needed, we should happily compile it without
publicever being needed. Since that should have been the case for this example, this high-level problem shouldn't have even come up for this code. -
For any cases where we find that IR-level specialization does not do all the work, and dynamic dispatch appears to be needed, we probably need some kind of opt-in from the user to indicate their intention to use dynamic dispatch. Currently the API user can explicitly opt in to dynamic dispatch for specific interfaces by manually including lists of the conformances they want to be accounted for. In cases where the user doesn't provide those lists (or can't because they are using command-line
slangc) we seemingly need a way to say "just enable dynamic dispatch for everything that the compiler thinks it needs"... a flag like-enable-dynamic-dispatch.
There's a more nuanced thing here, though, where it seems that at the very least we ought to be able to detect cases where the code we are generating creates an existential value based on the conformance B : IGetE, and determine that we probably need to account for that conformance when generating dynamic-dispatch logic for IGetE.
Summary
I'd like to address all these separate pieces, in approximately the order outlined above. I've assigned this issue to Yong so he can ideally get the first item resolved quickly.
This issue is fixed as I can no longer reproduce the error on top of tree. The test has been enabled.