Skip to content

Commit 0049015

Browse files
Support generic struct types during IR-based emit
Fixes shader-slang#318 Most of the required support was actually in place, so this is just a bunch of fixes: - Detect when we are in "full IR" mode, so that we can always emit `struct` declarations with their mangled named (which will produce different names for different specializations, since we emit decl-refs) - Carefully exclude builtin types from this for now. We'll need a more complete solution for mapping HLSL/Slang builtin types to their GLSL equivalents soon. - Skip emitting types referenced by generic IR functions, since they might not be usable. - Also fix things up so that we emit types used in the initializer for any global variables. - Fix bug in generic specialization where we specialize the same function more than once, with different type arguments. We were crashing on a `Dictionary::Add` call where the key already exists from a previous specialization attempt. - Fix name-mangling logic so that when outputting a possibly-specialized generic it looks for the outer-most `GenericSubstitution` rather than just the first one in the list. This is to handle the way that we insert other substitutions willy-nilly in places where they realistically don't belong. :( All of these changes together allow us to pass a slightly modified (more advanced) version of the test case posted to shader-slang#318.
1 parent 6f68127 commit 0049015

File tree

5 files changed

+149
-25
lines changed

5 files changed

+149
-25
lines changed

source/slang/emit.cpp

+81-23
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ struct SharedEmitContext
116116

117117
// Map used to tell AST lowering what decls are represented by IR.
118118
HashSet<Decl*>* irDeclSetForAST = nullptr;
119+
120+
// Are we doing IR-only emit, so that everything should get
121+
// its mangled name?
122+
bool isFullIRMode = false;
119123
};
120124

121125
struct EmitContext
@@ -2996,10 +3000,21 @@ struct EmitVisitor
29963000
}
29973001
}
29983002

3003+
bool isBuiltinDecl(Decl* decl)
3004+
{
3005+
for (auto dd = decl; dd; dd = dd->ParentDecl)
3006+
{
3007+
if (dd->FindModifier<FromStdLibModifier>())
3008+
return true;
3009+
}
3010+
return false;
3011+
}
3012+
29993013
void EmitDeclRef(DeclRef<Decl> declRef)
30003014
{
30013015
// Are we emitting an AST in a context where some declarations
30023016
// are actually stored as IR code?
3017+
30033018
if(auto irDeclSet = context->shared->irDeclSetForAST)
30043019
{
30053020
Decl* decl = declRef.getDecl();
@@ -3010,6 +3025,17 @@ struct EmitVisitor
30103025
}
30113026
}
30123027

3028+
if (context->shared->isFullIRMode)
3029+
{
3030+
// Don't apply this to builting declarations
3031+
if (!isBuiltinDecl(declRef.getDecl()))
3032+
{
3033+
emit(getIRName(declRef));
3034+
return;
3035+
}
3036+
}
3037+
3038+
30133039

30143040
// TODO: need to qualify a declaration name based on parent scopes/declarations
30153041

@@ -4453,26 +4479,34 @@ emitDeclImpl(decl, nullptr);
44534479

44544480
String getIRName(DeclRefBase const& declRef)
44554481
{
4456-
// It is a bit ugly, but we need a deterministic way
4457-
// to get a name for things when emitting from the IR
4458-
// that won't conflict with any keywords, builtins, etc.
4459-
// in the target language.
4482+
// In general, when referring to a declaration that has been lowered
4483+
// via the IR, we want to use its mangled name.
4484+
//
4485+
// There are two main exceptions to this:
4486+
//
4487+
// 1. For debugging, we accept the `-no-mangle` flag which basically
4488+
// instructs us to try to use the original name of all declarations,
4489+
// to make the output more like what is expected to come out of
4490+
// fxc pass-through. This case should get deprecated some day.
44604491
//
4461-
// Eventually we should accomplish this by using
4462-
// mangled names everywhere, but that complicates things
4463-
// when we are also using direct comparison to fxc/glslang
4464-
// output for some of our tests.
4492+
// 2. It is really annoying to have the fields of a `struct` type
4493+
// get ridiculously lengthy mangled names, and this also messes
4494+
// up stuff like specialization (since the mangled name of a field
4495+
// would then include the mangled name of the outer type).
44654496
//
44664497

44674498
String name;
44684499
if (context->shared->entryPoint->compileRequest->compileFlags & SLANG_COMPILE_FLAG_NO_MANGLING)
44694500
{
4501+
// Special case (1):
44704502
name.append(getText(declRef.GetName()));
4503+
return name;
44714504
}
4472-
else
4473-
{
4474-
name.append(getMangledName(declRef));
4475-
}
4505+
4506+
// Special case (2): not implemented yet.
4507+
4508+
// General case:
4509+
name.append(getMangledName(declRef));
44764510
return name;
44774511
}
44784512

@@ -7040,6 +7074,24 @@ emitDeclImpl(decl, nullptr);
70407074
{}
70417075
}
70427076

7077+
void emitIRUsedTypesForGlobalValueWithCode(
7078+
EmitContext* ctx,
7079+
IRGlobalValueWithCode* value)
7080+
{
7081+
for( auto bb = value->getFirstBlock(); bb; bb = bb->getNextBlock() )
7082+
{
7083+
for( auto pp = bb->getFirstParam(); pp; pp = pp->getNextParam() )
7084+
{
7085+
emitIRUsedTypesForValue(ctx, pp);
7086+
}
7087+
7088+
for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() )
7089+
{
7090+
emitIRUsedTypesForValue(ctx, ii);
7091+
}
7092+
}
7093+
}
7094+
70437095
void emitIRUsedTypesForValue(
70447096
EmitContext* ctx,
70457097
IRValue* value)
@@ -7050,19 +7102,23 @@ emitDeclImpl(decl, nullptr);
70507102
case kIROp_Func:
70517103
{
70527104
auto irFunc = (IRFunc*) value;
7105+
7106+
// Don't emit anything for a generic function,
7107+
// since we only care about the types used by
7108+
// the actual specializations.
7109+
if (irFunc->genericDecl)
7110+
return;
7111+
70537112
emitIRUsedType(ctx, irFunc->getResultType());
7054-
for( auto bb = irFunc->getFirstBlock(); bb; bb = bb->getNextBlock() )
7055-
{
7056-
for( auto pp = bb->getFirstParam(); pp; pp = pp->getNextParam() )
7057-
{
7058-
emitIRUsedTypesForValue(ctx, pp);
7059-
}
70607113

7061-
for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() )
7062-
{
7063-
emitIRUsedTypesForValue(ctx, ii);
7064-
}
7065-
}
7114+
emitIRUsedTypesForGlobalValueWithCode(ctx, irFunc);
7115+
}
7116+
break;
7117+
7118+
case kIROp_global_var:
7119+
{
7120+
auto irGlobal = (IRGlobalVar*) value;
7121+
emitIRUsedTypesForGlobalValueWithCode(ctx, irGlobal);
70667122
}
70677123
break;
70687124

@@ -7372,6 +7428,8 @@ String emitEntryPoint(
73727428
// compilation work. We thus start by cloning any code needed
73737429
// by the entry point over to our fresh IR module.
73747430

7431+
sharedContext.isFullIRMode = true;
7432+
73757433
specializeIRForEntryPoint(
73767434
irSpecializationState,
73777435
entryPoint);

source/slang/ir.cpp

+14-1
Original file line numberDiff line numberDiff line change
@@ -3079,7 +3079,20 @@ namespace Slang
30793079
{
30803080
if(!originalValue)
30813081
return;
3082-
context->getClonedValues().Add(originalValue, clonedValue);
3082+
3083+
// Note: setting the entry direclty here rather than
3084+
// using `Add` or `AddIfNotExists` because we can conceivably
3085+
// clone the same value (e.g., a basic block inside a generic
3086+
// function) multiple times, and that is okay, and we really
3087+
// just need to keep track of the most recent value.
3088+
3089+
// TODO: The same thing could potentially be handled more
3090+
// cleanly by having a notion of scoping for these cloned-value
3091+
// mappings, so that we register cloned values for things
3092+
// inside of a function to a temporary mapping that we
3093+
// throw away after the function is done.
3094+
3095+
context->getClonedValues()[originalValue] = clonedValue;
30833096
}
30843097

30853098
// Information on values to use when registering a cloned value

source/slang/mangle.cpp

+13-1
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,18 @@ namespace Slang
184184
}
185185
}
186186

187+
// TODO: this needs to be centralized
188+
RefPtr<GenericSubstitution> getOutermostGenericSubst(
189+
RefPtr<Substitutions> inSubst)
190+
{
191+
for (auto subst = inSubst; subst; subst = subst->outer)
192+
{
193+
if (auto genericSubst = subst.As<GenericSubstitution>())
194+
return genericSubst;
195+
}
196+
return nullptr;
197+
}
198+
187199
void emitQualifiedName(
188200
ManglingContext* context,
189201
DeclRef<Decl> declRef)
@@ -221,7 +233,7 @@ namespace Slang
221233
// There are two cases here: either we have specializations
222234
// in place for the parent generic declaration, or we don't.
223235

224-
auto subst = declRef.substitutions.As<GenericSubstitution>();
236+
auto subst = getOutermostGenericSubst(declRef.substitutions);
225237
if( subst && subst->genericDecl == parentGenericDeclRef.getDecl() )
226238
{
227239
// This is the case where we *do* have substitutions.

tests/compute/generic-struct.slang

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//TEST(compute):COMPARE_COMPUTE:-xslang -use-ir
2+
//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):dxbinding(0),glbinding(0),out
3+
4+
// Check that user code can declare and use a generic
5+
// `struct` type.
6+
7+
RWStructuredBuffer<int> outputBuffer;
8+
9+
__generic<T>
10+
struct GenStruct
11+
{
12+
T x;
13+
T y;
14+
};
15+
16+
__generic<T>
17+
T test(T val)
18+
{
19+
GenStruct<T> gs;
20+
gs.x = val;
21+
gs.y = val;
22+
return gs.x;
23+
}
24+
25+
26+
[numthreads(4, 1, 1)]
27+
void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
28+
{
29+
uint tid = dispatchThreadID.x;
30+
31+
float outVal = 0;
32+
33+
outVal += test<uint>(tid);
34+
outVal += test<float>(tid * 16.0f);
35+
36+
outputBuffer[tid] = int(outVal);
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
0
2+
11
3+
22
4+
33

0 commit comments

Comments
 (0)