Skip to content

Commit 871fbee

Browse files
author
Tim Foley
authored
Revise type legalization so it can handle constant buffers (shader-slang#282)
* Revise type legalization so it can handle constant buffers The existing legalization approach with "tuples" can handle scalarizing a `struct` type with resource-type fields in it, but it had several big gaps. The most notable is that given a type that mixes uniform and resource fields, we can't just blindly scalarize things: ``` struct P { float4 a; float4 b; Texture2D t; }; cbuffer C { P gParam[8]; }; ``` The existing code was completely ignoring the declaration of `gParam` inside `C`, but even if we fixed that issue, we'd get something like: ``` cbuffer C { float4 gParam_a[8]; float4 gParam_b[8]; }; Texture2D gParam_t[8]; ``` In this case we've completely changed the layout of the uniform buffer, by switching from AOS to SOA. Even if we could get the type layout logic and the IR to agree on this, it would be a surprise to users, and "principle of least surprise" should be a big deal on a project with as many moving parts as ours. The right thing to do is to have legalization create a "stripped" version of the original type `P` and use that: ``` struct P_stripped { float4 a; float4 b; }; cbuffer C { P_stripped gParam[8]; }; Texture2D gParam_t[8]; ``` Then at a call site, this: ``` foo(gParam); ``` becomes: ``` foo(gParam, gParam_t); ``` This is exactly how the current AST-to-AST legalization handles mixed uniform and resource types, but the way it does it involves some annoying kludges: - That pass has a notion of a "tuple" similar to our legalization, but every tuple has an optional "primary" entry for all the uniform data, plus tuple elements for the resources, and a given field may be represented on one side, the other, or both. It makes the code for handling tuples very messy. - That pass does the "stripping" of types by actually marking up the AST declarations (this is okay because it is constructing a new AST as it goes), so that when they get emitted certain fields don't actually show up. That is, we fix the problem with type `P` by actually *modifying* the user's declaration of `P`. That seems out of bounds for the IR. This change fixes the problem in our IR type legalization while trying to avoid the problems of the AST-to-AST pass by using two new ideas: 1. We add a new case for `LegalType` (and `LegalVal`) that is a "pair" type, where a pair consists of both an "ordinary" type (for uniform data) and a "special" type (for resource data). E.g., after legalization, the type for `C` (which can be over-simplified to `ConstantBuffer<P>` for our purposes), will be a `LegalType::pair` where the ordinary side is `ConstantBuffer<P_stripped>` and the special side is a tuple containing the `Texture2D` field. 2. We add a new (and annoyingly hacky) AST-level type called `FilteredTupleType` which is semantically a sort of tuple type (it holds a list of elements, and the elements have their own types), but which remembers an "original type" that it was created from, and for each element remembers the field of the original type that it corresponds to. This is used to construct a type like `P_stripped` as an actual AST-level structural type. The core logic for legalizing an aggregate type had to get more complicated just because of the new pair case, so there is now a `TupleTypeBuilder` that asists with taking an aggregate type, processing its fields, and then picking the right `LegalType` representation for the result. Other smaller changes: - Made the legalization logic actually legalize `PtrType<T>`. E.g., if `T` legalizes to a tuple, we need to construct a tuple of pointer types. The same exact thing needs to be applied to arrays, and any other generic type that should "distribute over" pairs/tuples. - Made the legalization logic actually legalize `ConstantBuffer<T>` and similar. The basic idea there is if `T` maps to a pair, we wrap `ConstantBuffer<...>` around the ordinary side, and `implicitDeref` around the special side. - Removed a bunch of `#ifdef`ed-out code from the end of `ir-legalize-types.cpp`. That was code from my first attempt at legalization that failed miserably (trying to do it via local changes and a work list instead of a global rewrite pass), but it had some code I wanted to reference when writing the version that actually got checked in (should have deleted the code earlier, though). - Added a bunch of cases for `LegalType::none` (and the `LegalVal` equivalent) that helped simplify the logic fo the `pair` case by allowing me to *always* dispatch to both the "ordinary" and "special" sides, even if they might not actually be present. - Renamed `TupleType` and `TupleVal` over to `TuplePseudoType` and `TuplePseudoval` to recognize the fact that we might actually need/want *real* tuples in the type system, to go along with these fake ones (that need to be optimized away). The biggest doubt I have about this change is the whole `FilteredTupleType` thing; it seems like an obviously contrived type to add to the front-end type system that really only solves IR-level problems. A cleaner approach might have been to just add a plain old `TupleType` to the front-end type system (and initially I started with that), and then have yet another `LegalType`/`LegalVal` case that handles mapping from the fields of the original type to the numbered tuple elements. I expect we'll actually want to make that change in the future (especially if we ever add true tuples to the front-end), but for right now I let myself be swayed by the desire to have these stripped/filtered types get names that explain their provenance ("where they came from") to make our output code more debuggable. The way I've done it is probably overkill, though, and we need a much more complete effort on the readability and debuggability of our output before anything like that is worth worrying about. * Fixup: typo * Fixup: fix output of "non-mangled" names for test cases - Make sure to attach high-level decls to variables created as part of type legalization - Also, try to share more of the code between the different cases of variables - Fix up `parameter-blocks` test case that was passing `-no-mangle` but expecting mangled names in the output - Fix up `multiple-parameter-blocks` to not rely on `-no-mangle` for now, because it would lead to two global variables with the same name (need to fix that underlying issue eventually). - Also fix name generation logic so that we only use "original" names (from high-level decls) specifically when the `-no-mangle` flag is on, and otherwise use IR-level names. * Fix: handle constant buffers better in render-test - Don't request both CB and SRV usage for buffers, since that is illegal - Also, don't try to create an SRV when user requested a CB (since the required usage flag won't be there) - Record the input buffer type on the `D3DBinding` for a buffer, and use that to tell us when to bind a CB instead of SRV/UAV - Fix expected output for `cbuffer-legalize` test now that we are actually feeding it correct cbuffer dta.
1 parent 1a0a7f6 commit 871fbee

13 files changed

+1255
-561
lines changed

source/slang/emit.cpp

+79-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "ir-insts.h"
55
#include "lower.h"
66
#include "lower-to-ir.h"
7+
#include "mangle.h"
78
#include "name.h"
89
#include "syntax.h"
910
#include "type-layout.h"
@@ -99,6 +100,8 @@ struct SharedEmitContext
99100
HashSet<Decl*> irDeclsVisited;
100101

101102
Dictionary<IRBlock*, IRBlock*> irMapContinueTargetToLoopHead;
103+
104+
HashSet<String> irTupleTypes;
102105
};
103106

104107
struct EmitContext
@@ -1230,6 +1233,13 @@ struct EmitVisitor
12301233
emitTypeImpl(type->valueType, arg.declarator);
12311234
}
12321235

1236+
void visitFilteredTupleType(FilteredTupleType* type, TypeEmitArg const& arg)
1237+
{
1238+
auto declarator = arg.declarator;
1239+
emit(getMangledTypeName(type));
1240+
EmitDeclarator(declarator);
1241+
}
1242+
12331243
void EmitType(
12341244
RefPtr<Type> type,
12351245
SourceLoc const& typeLoc,
@@ -4199,7 +4209,10 @@ emitDeclImpl(decl, nullptr);
41994209
return getText(reflectionNameMod->nameAndLoc.name);
42004210
}
42014211

4202-
return getIRName(decl);
4212+
if ((context->shared->entryPoint->compileRequest->compileFlags & SLANG_COMPILE_FLAG_NO_MANGLING))
4213+
{
4214+
return getIRName(decl);
4215+
}
42034216
}
42044217

42054218
switch (inst->op)
@@ -6271,6 +6284,26 @@ emitDeclImpl(decl, nullptr);
62716284
}
62726285
}
62736286
}
6287+
else if (auto filteredTupleType = elementType->As<FilteredTupleType>())
6288+
{
6289+
auto structTypeLayout = typeLayout.As<StructTypeLayout>();
6290+
assert(structTypeLayout);
6291+
6292+
for (auto ee : filteredTupleType->elements)
6293+
{
6294+
RefPtr<VarLayout> fieldLayout;
6295+
structTypeLayout->mapVarToLayout.TryGetValue(ee.fieldDeclRef, fieldLayout);
6296+
6297+
emitIRVarModifiers(ctx, fieldLayout);
6298+
6299+
auto fieldType = ee.type;
6300+
emitIRType(ctx, fieldType, getIRName(ee.fieldDeclRef));
6301+
6302+
emitHLSLParameterGroupFieldLayoutSemantics(layout, fieldLayout);
6303+
6304+
emit(";\n");
6305+
}
6306+
}
62746307
else
62756308
{
62766309
emit("/* unexpected */");
@@ -6586,6 +6619,43 @@ emitDeclImpl(decl, nullptr);
65866619
ensureStructDecl(ctx, structDeclRef);
65876620
}
65886621
}
6622+
else if (auto filteredTupleType = type->As<FilteredTupleType>())
6623+
{
6624+
// First, ensure that the element types are ready:
6625+
for (auto ee : filteredTupleType->elements)
6626+
{
6627+
if (ee.type)
6628+
{
6629+
emitIRUsedType(ctx, ee.type);
6630+
}
6631+
}
6632+
6633+
// Now, we want to ensure we've emitted a
6634+
// matching `struct` type declaration.
6635+
6636+
String mangledName = getMangledTypeName(filteredTupleType);
6637+
if (!ctx->shared->irTupleTypes.Contains(mangledName))
6638+
{
6639+
ctx->shared->irTupleTypes.Add(mangledName);
6640+
6641+
// Emit the damn `struct` decl...
6642+
6643+
Emit("struct ");
6644+
emit(mangledName);
6645+
Emit("\n{\n");
6646+
for( auto ee : filteredTupleType->elements )
6647+
{
6648+
if (!ee.type)
6649+
continue;
6650+
6651+
emitIRType(ctx, ee.type, getIRName(ee.fieldDeclRef));
6652+
6653+
emit(";\n");
6654+
}
6655+
Emit("};\n");
6656+
6657+
}
6658+
}
65896659
else
65906660
{}
65916661
}
@@ -6840,7 +6910,7 @@ String emitEntryPoint(
68406910

68416911
// Debugging code for IR transformations...
68426912
#if 0
6843-
fprintf(stderr, "###\n");
6913+
fprintf(stderr, "### SPECIALIZED:\n");
68446914
dumpIR(lowered);
68456915
fprintf(stderr, "###\n");
68466916
#endif
@@ -6852,6 +6922,13 @@ String emitEntryPoint(
68526922
//
68536923
legalizeTypes(lowered);
68546924

6925+
// Debugging output of legalization
6926+
#if 0
6927+
fprintf(stderr, "### LEGALIZED:\n");
6928+
dumpIR(lowered);
6929+
fprintf(stderr, "###\n");
6930+
#endif
6931+
68556932
// TODO: do we want to emit directly from IR, or translate the
68566933
// IR back into AST for emission?
68576934

0 commit comments

Comments
 (0)