yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Jay KwakHandle SPIR-V aliases (#8704)b173149f4

master
11.9 KiB356 linesraw
1#include "slang-spirv-core-grammar.h"
2
3#include "../core/slang-rtti-util.h"
4#include "../core/slang-string-util.h"
5#include "slang-core-diagnostics.h"
6#include "slang-json-native.h"
7
8#include <limits>
9
10namespace Slang
11{
12using SpvWord = uint32_t;
13
14//
15// Structs which mirror the structure of spirv.core.grammar.json
16//
17// Commented members are those which currently don't use
18struct InstructionPrintingClass
19{
20    UnownedStringSlice tag;
21    UnownedStringSlice heading;
22};
23SLANG_MAKE_STRUCT_RTTI_INFO(
24    InstructionPrintingClass,
25    SLANG_RTTI_FIELD(tag),
26    SLANG_OPTIONAL_RTTI_FIELD(heading));
27
28struct Operand
29{
30    UnownedStringSlice kind;
31    UnownedStringSlice quantifier;
32    // UnownedStringSlice name;
33};
34SLANG_MAKE_STRUCT_RTTI_INFO(
35    Operand,
36    SLANG_RTTI_FIELD(kind),
37    SLANG_OPTIONAL_RTTI_FIELD(quantifier)
38    // SLANG_RTTI_FIELD(name),
39);
40
41struct Instruction
42{
43    UnownedStringSlice opname;
44    UnownedStringSlice class_;
45    SpvWord opcode;
46    List<UnownedStringSlice> capabilities;
47    List<UnownedStringSlice> aliases;
48    List<Operand> operands;
49};
50SLANG_MAKE_STRUCT_RTTI_INFO(
51    Instruction,
52    SLANG_RTTI_FIELD(opname),
53    SLANG_RTTI_FIELD_IMPL(class_, "class", 0),
54    SLANG_RTTI_FIELD(opcode),
55    SLANG_OPTIONAL_RTTI_FIELD(capabilities),
56    SLANG_OPTIONAL_RTTI_FIELD(aliases),
57    SLANG_OPTIONAL_RTTI_FIELD(operands));
58
59struct Enumerant
60{
61    UnownedStringSlice enumerant;
62    JSONValue value;
63    List<UnownedStringSlice> capabilities;
64    List<UnownedStringSlice> aliases;
65    // List<Operand> parameters;
66    // UnownedStringSlice version;
67    // UnownedStringSlice lastVersion;
68    // List<UnownedStringSlice> extensions;
69};
70SLANG_MAKE_STRUCT_RTTI_INFO(
71    Enumerant,
72    SLANG_RTTI_FIELD(enumerant),
73    SLANG_RTTI_FIELD(value),
74    SLANG_OPTIONAL_RTTI_FIELD(capabilities),
75    SLANG_OPTIONAL_RTTI_FIELD(aliases),
76    // SLANG_OPTIONAL_RTTI_FIELD(parameters),
77    // SLANG_OPTIONAL_RTTI_FIELD(version),
78    // SLANG_OPTIONAL_RTTI_FIELD(lastVersion),
79    // SLANG_OPTIONAL_RTTI_FIELD(extensions)
80);
81
82struct OperandKind
83{
84    UnownedStringSlice category;
85    UnownedStringSlice kind;
86    List<Enumerant> enumerants;
87};
88SLANG_MAKE_STRUCT_RTTI_INFO(
89    OperandKind,
90    SLANG_RTTI_FIELD(category),
91    SLANG_RTTI_FIELD(kind),
92    SLANG_OPTIONAL_RTTI_FIELD(enumerants));
93
94struct SPIRVSpec
95{
96    // List<UnownedStringSlice> copyright;
97    // UnownedStringSlice magic_number;
98    // UInt32 major_version;
99    // UInt32 minor_version;
100    // UInt32 revision;
101    List<InstructionPrintingClass> instruction_printing_class;
102    List<Instruction> instructions;
103    List<OperandKind> operand_kinds;
104};
105SLANG_MAKE_STRUCT_RTTI_INFO(
106    SPIRVSpec,
107    // SLANG_RTTI_FIELD(copyright),
108    // SLANG_RTTI_FIELD(magic_number),
109    // SLANG_RTTI_FIELD(major_version)
110    // SLANG_RTTI_FIELD(minor_version)
111    // SLANG_RTTI_FIELD(revision)
112    SLANG_RTTI_FIELD(instruction_printing_class),
113    SLANG_RTTI_FIELD(instructions),
114    SLANG_RTTI_FIELD(operand_kinds));
115
116static Dictionary<UnownedStringSlice, SpvWord> operandKindToDict(
117    JSONContainer& container,
118    DiagnosticSink& sink,
119    const OperandKind& k)
120{
121    Dictionary<UnownedStringSlice, SpvWord> dict;
122    dict.reserve(k.enumerants.getCount());
123    for (const auto& e : k.enumerants)
124    {
125        SpvWord valueInt = 0;
126        switch (e.value.getKind())
127        {
128        case JSONValue::Kind::Integer:
129            {
130                // TODO: Range check here?
131                valueInt = SpvWord(container.asInteger(e.value));
132                break;
133            }
134        case JSONValue::Kind::String:
135            {
136                Int i = 0;
137                const auto str = container.getString(e.value);
138                if (SLANG_FAILED(StringUtil::parseInt(str, i)))
139                    sink.diagnose(
140                        e.value.loc,
141                        MiscDiagnostics::spirvCoreGrammarJSONParseFailure,
142                        "Expected an integer value");
143                // TODO: Range check here?
144                valueInt = SpvWord(i);
145                break;
146            }
147        default:
148            sink.diagnose(
149                e.value.loc,
150                MiscDiagnostics::spirvCoreGrammarJSONParseFailure,
151                "Expected an integer value (or a string with an integer inside)");
152        }
153        dict.add(e.enumerant, valueInt);
154
155        for (auto alias : e.aliases)
156        {
157            dict.add(alias, valueInt);
158        }
159    }
160    return dict;
161}
162
163//
164//
165//
166RefPtr<SPIRVCoreGrammarInfo> SPIRVCoreGrammarInfo::loadFromJSON(
167    SourceView& source,
168    DiagnosticSink& sink)
169{
170    //
171    // Load the JSON
172    //
173    SLANG_ASSERT(source.getSourceManager() == sink.getSourceManager());
174    JSONLexer lexer;
175    lexer.init(&source, &sink);
176    JSONParser parser;
177    JSONContainer container(sink.getSourceManager());
178    JSONBuilder builder(&container);
179    RttiTypeFuncsMap typeMap;
180    typeMap = JSONNativeUtil::getTypeFuncsMap();
181    SLANG_RETURN_NULL_ON_FAIL(parser.parse(&lexer, &source, &builder, &sink));
182    JSONToNativeConverter converter(&container, &typeMap, &sink);
183    SPIRVSpec spec;
184    if (SLANG_FAILED(converter.convert(builder.getRootValue(), &spec)))
185    {
186        // TODO: not having a source loc here is not great...
187        sink.diagnoseWithoutSourceView(
188            SourceLoc{},
189            MiscDiagnostics::spirvCoreGrammarJSONParseFailure,
190            "Failed to match SPIR-V grammar JSON to the expected schema");
191        return nullptr;
192    }
193
194    //
195    // Convert to the internal representation
196    //
197    RefPtr<SPIRVCoreGrammarInfo> res{new SPIRVCoreGrammarInfo};
198
199    res->operandKinds.dict.reserve(spec.operand_kinds.getCount());
200    uint32_t operandKindIndex = 0;
201    for (const auto& c : spec.operand_kinds)
202    {
203        if (operandKindIndex > std::numeric_limits<decltype(OperandKind::index)>::max())
204        {
205            sink.diagnoseWithoutSourceView(
206                SourceLoc{},
207                MiscDiagnostics::spirvCoreGrammarJSONParseFailure,
208                "Too many enum categories, expected fewer than 256");
209        }
210        res->operandKinds.dict.add(
211            c.kind,
212            {static_cast<decltype(OperandKind::index)>(operandKindIndex)});
213        operandKindIndex++;
214    }
215
216    // It's important we reserve the memory now, as we require the iterators to
217    // be stable, as references to them are maintained by the OpInfo structs.
218    Index totalNumOperands = 0;
219    for (const auto& i : spec.instructions)
220        totalNumOperands += i.operands.getCapacity();
221    res->operandTypesStorage.reserve(totalNumOperands);
222
223    res->opcodes.dict.reserve(spec.instructions.getCount());
224    for (const auto& i : spec.instructions)
225    {
226        res->opcodes.dict.add(i.opname, SpvOp(i.opcode));
227
228        for (auto alias : i.aliases)
229        {
230            res->opcodes.dict.add(alias, SpvOp(i.opcode));
231        }
232
233        const auto class_ = i.class_ == "Type-Declaration"    ? OpInfo::TypeDeclaration
234                            : i.class_ == "Constant-Creation" ? OpInfo::ConstantCreation
235                            : i.class_ == "Debug"             ? OpInfo::Debug
236                                                              : OpInfo::Other;
237
238        const auto resultTypeIndex =
239            i.operands.findFirstIndex([](const auto& o) { return o.kind == "IdResultType"; });
240        const auto resultIdIndex =
241            i.operands.findFirstIndex([](const auto& o) { return o.kind == "IdResult"; });
242        SLANG_ASSERT(resultTypeIndex >= -1 || resultTypeIndex <= 0);
243        SLANG_ASSERT(resultIdIndex >= -1 || resultTypeIndex <= 1);
244
245        uint16_t minOperandCount = 0;
246        uint16_t maxOperandCount = 0;
247        uint16_t numOperandTypes = 0;
248        const OperandKind* operandTypes = res->operandTypesStorage.end();
249        for (const auto& o : i.operands)
250        {
251            if (maxOperandCount == 0xffff)
252            {
253                // We are about to overflow maxWordCount, either someone has
254                // put 2^16 operands in the json, or we have a "*" quantified
255                // operand not in the last position and should implement
256                // support for that
257                sink.diagnoseWithoutSourceView(
258                    SourceLoc{},
259                    MiscDiagnostics::spirvCoreGrammarJSONParseFailure,
260                    "\"*\"-qualified operand wasn't the last operand");
261            }
262
263            const auto catIndex = res->operandKinds.lookup(o.kind);
264            if (!catIndex)
265            {
266                sink.diagnoseWithoutSourceView(
267                    SourceLoc{},
268                    MiscDiagnostics::spirvCoreGrammarJSONParseFailure,
269                    "Operand references a kind which doesn't exist");
270                continue;
271            }
272
273            numOperandTypes++;
274            res->operandTypesStorage.add(*catIndex);
275
276            // The number of "ImageOperands" is dependent on the bitmask
277            // operand, for our purposes treat them as unbounded
278            if (o.quantifier == "*" || o.kind == "ImageOperands")
279            {
280                maxOperandCount = 0xffff;
281            }
282            else if (o.quantifier == "?")
283            {
284                maxOperandCount++;
285            }
286            else if (o.quantifier == "")
287            {
288                // This catches the case where an "?" or "*" qualified operand
289                // appears before any unqualified operands
290                if (minOperandCount != maxOperandCount)
291                    sink.diagnoseWithoutSourceView(
292                        SourceLoc{},
293                        MiscDiagnostics::spirvCoreGrammarJSONParseFailure,
294                        "\"*\" or \"?\" operand appeared before an unqualified operand");
295                minOperandCount++;
296                maxOperandCount++;
297            }
298            else
299            {
300                sink.diagnose(
301                    SourceLoc{},
302                    MiscDiagnostics::spirvCoreGrammarJSONParseFailure,
303                    "quantifier wasn't empty, * or ?");
304            }
305        }
306
307        // There are duplicate opcodes in the json (for renamed instructions,
308        // or the same instruction with different capabilities), for now just
309        // keep the first one.
310        res->opInfos.dict.addIfNotExists(
311            SpvOp(i.opcode),
312            {class_,
313             static_cast<int8_t>(resultTypeIndex),
314             static_cast<int8_t>(resultIdIndex),
315             minOperandCount,
316             maxOperandCount,
317             numOperandTypes,
318             operandTypes});
319        res->opNames.dict.addIfNotExists(SpvOp(i.opcode), i.opname);
320    }
321
322    for (const auto& k : spec.operand_kinds)
323    {
324        const auto kindIndex = res->operandKinds.dict.getValue(k.kind);
325        const auto d = operandKindToDict(container, sink, k);
326        for (const auto& [n, v] : d)
327        {
328            // Add the string to this slice pool as we'll be taking ownership
329            // of it shortly but don't want to invalidate it in the meantime.
330            const auto s = container.getStringSlicePool().addAndGetSlice(String(k.kind) + n);
331            res->allEnumsWithTypePrefix.dict.add(s, v);
332            res->allEnums.dict.add({kindIndex, n}, v);
333            res->allEnumNames.dict.addIfNotExists({kindIndex, v}, n);
334        }
335
336        res->operandKindNames.dict.add(kindIndex, k.kind);
337
338        if (k.kind == "Capability")
339            for (const auto& [n, v] : d)
340                res->capabilities.dict.add(n, SpvCapability(v));
341
342        // If this starts with Id, and the suffix is also an operand kind,
343        // assume that this is an Id wrapper
344        if (k.kind.startsWith("Id"))
345        {
346            const UnownedStringSlice underneathIdKind{k.kind.begin() + 2, k.kind.end()};
347            OperandKind targetIndex;
348            if (res->operandKinds.dict.tryGetValue(underneathIdKind, targetIndex))
349                res->operandKindUnderneathIds.dict.add(kindIndex, targetIndex);
350        }
351    }
352    // Steal the strings from the JSON container before it dies
353    res->strings.swapWith(container.getStringSlicePool());
354    return res;
355}
356} // namespace Slang