diff options
| author | Ellie Hermaszewska <ellieh@nvidia.com> | 2023-08-29 06:05:26 +0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-08-28 15:05:26 -0700 |
| commit | 508dc3a95de50de4a4d07d0a72a18e40d55b0e2e (patch) | |
| tree | 7487232f5c0db0dd607e2a91b539f6a592789b06 /source/compiler-core | |
| parent | 06f7ef354cdde4cf8e8797d8853ed2d9c3208b5b (diff) | |
Allow bitwise or expressions and numeric literals in spirv_asm blocks (#3157)
* Add -spirv-core-grammar option to load alternate spirv defs
Also embed a version to use by default
* Use perfect hash for spv op lookup
* Neaten perfect hash embedding
* Refactor spirv grammar lookup in preperation for more kinds of lookups
* Load spirv capability list from spec
* Add all SPIR-V enums to lookup table
* regenerate vs projects
* appease msvc
* Use string slices for spir-v core grammar lookups
* wiggle
* comment
* Add OpInfo for spv ops
* regenerate vs projects
* Embed op names
* Add min/max operand counts and enum categories to spirv info
* neaten
* Operand kinds for spirv ops
* Store and embed all information relating to spirv enums and qualifiers
* Use SPIR-V spec to position instructions in spirv_asm blocks
* Neaten spir-v info embedding
* Neaten perfect hash embedding
* Add assignment syntax to spirv_asm snippets
* Better errors for spirv_asm parser
* Add warning for too many operands in spirv asm
* squash warnings
* neaten
* test wiggle
* Lookup enums for spirv
* Put OpCapability and OpExtension in the correct place for spirv_asm blocks
* Tests for OpCapability and OpExtension
* ci wiggle
* Add expected failure
* Allow raising immediate values to constant ids where necessary in spirv_asm blocks
* Allow bitwise or expressions and numeric literals in spirv_asm blocks
* test numeric literals
* Fix memory issues.
* fix.
---------
Co-authored-by: Yong He <yonghe@outlook.com>
Diffstat (limited to 'source/compiler-core')
| -rw-r--r-- | source/compiler-core/slang-json-value.h | 5 | ||||
| -rw-r--r-- | source/compiler-core/slang-misc-diagnostic-defs.h | 2 | ||||
| -rw-r--r-- | source/compiler-core/slang-perfect-hash.cpp | 214 | ||||
| -rw-r--r-- | source/compiler-core/slang-perfect-hash.h | 30 | ||||
| -rw-r--r-- | source/compiler-core/slang-spirv-core-grammar.cpp | 340 | ||||
| -rw-r--r-- | source/compiler-core/slang-spirv-core-grammar.h | 145 |
6 files changed, 736 insertions, 0 deletions
diff --git a/source/compiler-core/slang-json-value.h b/source/compiler-core/slang-json-value.h index 25e74cb36..0abe7c9a6 100644 --- a/source/compiler-core/slang-json-value.h +++ b/source/compiler-core/slang-json-value.h @@ -311,6 +311,11 @@ public: /// Returns true if all the keys are unique static bool areKeysUnique(const JSONKeyValue* keyValues, Index keyValueCount); + /// Access the internal set of strings, removing anything from this + /// will invalidate the container, so only do it immediately prior to + /// destruction. + StringSlicePool& getStringSlicePool() {return m_slicePool;}; + protected: struct Range { diff --git a/source/compiler-core/slang-misc-diagnostic-defs.h b/source/compiler-core/slang-misc-diagnostic-defs.h index aed83eb30..a2ccc4657 100644 --- a/source/compiler-core/slang-misc-diagnostic-defs.h +++ b/source/compiler-core/slang-misc-diagnostic-defs.h @@ -33,4 +33,6 @@ DIAGNOSTIC(100005, Error, invalidArgumentForOption, "invalid argument format for DIAGNOSTIC(99999, Note, noteLocationOfInternalError, "an internal error threw an exception while working on code near this location") +DIAGNOSTIC(29104, Error, spirvCoreGrammarJSONParseFailure, "unexpected JSON in spirv core grammar file: $0") + #undef DIAGNOSTIC diff --git a/source/compiler-core/slang-perfect-hash.cpp b/source/compiler-core/slang-perfect-hash.cpp new file mode 100644 index 000000000..bfe8d4876 --- /dev/null +++ b/source/compiler-core/slang-perfect-hash.cpp @@ -0,0 +1,214 @@ +#include "slang-perfect-hash.h" + +#include "../core/slang-string-util.h" +#include "../core/slang-writer.h" + +namespace Slang +{ + +// Implemented according to "Hash, displace, and compress" +// https://cmph.sourceforge.net/papers/esa09.pdf +HashFindResult minimalPerfectHash(const List<String>& ss, HashParams& hashParams) +{ + // Check for uniqueness + for (Index i = 0; i < ss.getCount(); ++i) + { + for (Index j = i + 1; j < ss.getCount(); ++j) + { + if (ss[i] == ss[j]) + { + return HashFindResult::NonUniqueKeys; + } + } + } + + SLANG_ASSERT(UIndex(ss.getCount()) < std::numeric_limits<UInt32>::max()); + const UInt32 nBuckets = UInt32(ss.getCount()); + List<List<String>> initialBuckets; + initialBuckets.setCount(nBuckets); + + const auto hash = [&](const String& s, const HashCode32 salt = 0) -> UInt32 + { + // + // The current getStableHashCode is susceptible to patterns of + // collisions causing the search to fail for the SPIR-V opnames; it + // performs poorly on short strings, taking over 300000 iterations to + // diverge on "Ceil" and "FMix" (and place them in already unoccupied + // slots)! + // + // Use FNV Hash here which seem perform much better on these short inputs + // https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + // + // If you change this, don't forget to also sync the version below in + // the printing code. + UInt32 h = salt; + for (const char c : s) + h = (h * 0x01000193) ^ c; + return h % nBuckets; + }; + + // Assign the inputs into their buckets according to the hash without salt. + // Sort the buckets according to size, so that later we can make these have + // unique destinations starting with the largest ones first as they are at + // most risk of collision. + for (const auto& s : ss) + { + initialBuckets[hash(s)].add(s); + } + initialBuckets.stableSort([](const List<String>& a, const List<String>& b) { return a.getCount() > b.getCount(); }); + + // These are our outputs, the salts are calculated such that for all input + // word, x, hash(x, salt[hash(x, 0)]) is unique + // + // We keep the final table as we need to detect when we've been given a + // word not in our language. + hashParams.saltTable.setCount(nBuckets); + for (auto& s : hashParams.saltTable) + s = 0; + hashParams.destTable.setCount(nBuckets); + for (auto& s : hashParams.destTable) + s.reduceLength(0); + + // This mask will, in each salt tryout, be used to prevent collisions + // within a single bucket. + List<bool> bucketDestinations = List<bool>::makeRepeated(false, nBuckets); + + for (const auto& b : initialBuckets) + { + // Break if we've reached the empty buckets + if (!b.getCount()) + { + break; + } + + // Try out all the salts until we get one which has no internal + // collisions for this bucket and also no collisions with the buckets + // we've processed so far. + UInt32 salt = 1; + while (true) + { + bool collision = false; + for (auto& d : bucketDestinations) + { + d = false; + } + + for (const auto& s : b) + { + const auto i = hash(s, salt); + if (hashParams.destTable[i].getLength() || bucketDestinations[i]) + { + collision = true; + break; + } + bucketDestinations[i] = true; + } + if (!collision) + { + break; + } + salt++; + + // If we fail to find a solution after some massive amount of tries + // it's almost certainly because of some property of the hash + // function and language causing an irresolvable collision. + if (salt > 10000 * nBuckets) + { + return HashFindResult::UnavoidableHashCollision; + } + } + for (const auto& s : b) + { + hashParams.saltTable[hash(s)] = salt; + hashParams.destTable[hash(s, salt)] = s; + } + } + return HashFindResult::Success; +} + +String perfectHashToEmbeddableCpp( + const HashParams& hashParams, + const UnownedStringSlice& valueType, + const UnownedStringSlice& funcName, + const List<String>& values) +{ + SLANG_ASSERT(hashParams.saltTable.getCount() == hashParams.destTable.getCount()); + SLANG_ASSERT(hashParams.saltTable.getCount() == values.getCount()); + + StringBuilder sb; + StringWriter writer(&sb, WriterFlags(0)); + WriterHelper w(&writer); + const auto line = [&](const char* l){ + w.put(l); + w.put("\n"); + }; + + w.print("bool %s(const UnownedStringSlice& str, %s& value)\n", String(funcName).getBuffer(), String(valueType).getBuffer()); + line("{"); + + w.print(" static const unsigned tableSalt[%ld] = {\n", hashParams.saltTable.getCount()); + w.print(" "); + for (Index i = 0; i < hashParams.saltTable.getCount(); ++i) + { + const auto salt = hashParams.saltTable[i]; + if (i != hashParams.saltTable.getCount() - 1) + { + w.print(" %d,", salt); + if (i % 16 == 15) + { + w.print("\n "); + } + } + else + { + w.print(" %d", salt); + } + } + line("\n };"); + line(""); + + w.print(" using KV = std::pair<const char*, %s>;\n", String(valueType).getBuffer()); + line(""); + + w.print(" static const KV words[%ld] =\n", hashParams.destTable.getCount()); + line(" {"); + for (Index i = 0; i < hashParams.destTable.getCount(); ++i) + { + const auto& s = hashParams.destTable[i]; + const auto& v = values[i]; + w.print( + " {\"%s\", %s},\n", + s.getBuffer(), + v.getBuffer() + ); + } + line(" };"); + line(""); + + // Make sure to update the hash function in the search function above if + // you change this. + line(" static const auto hash = [](const UnownedStringSlice& str, UInt32 salt){"); + line(" UInt32 h = salt;"); + line(" for (const char c : str)"); + line(" h = (h * 0x01000193) ^ c;"); + w.print(" return h %% %ld;\n", hashParams.saltTable.getCount()); + line(" };"); + line(""); + + line(" const auto i = hash(str, tableSalt[hash(str, 0)]);"); + line(" if(str == words[i].first)"); + line(" {"); + line(" value = words[i].second;"); + line(" return true;"); + line(" }"); + line(" else"); + line(" {"); + line(" return false;"); + line(" }"); + line("}"); + line(""); + + return sb; +} + +} diff --git a/source/compiler-core/slang-perfect-hash.h b/source/compiler-core/slang-perfect-hash.h new file mode 100644 index 000000000..1488c7a62 --- /dev/null +++ b/source/compiler-core/slang-perfect-hash.h @@ -0,0 +1,30 @@ +#pragma once + +#include "../core/slang-string.h" +#include "../core/slang-list.h" + +namespace Slang +{ + +struct HashParams +{ + List<UInt32> saltTable; + List<String> destTable; +}; + +enum class HashFindResult { + Success, + NonUniqueKeys, + UnavoidableHashCollision, +}; + +// Calculate a minimal perfect hash of a list of input strings +HashFindResult minimalPerfectHash(const List<String>& ss, HashParams& hashParams); + +String perfectHashToEmbeddableCpp( + const HashParams& hashParams, + const UnownedStringSlice& valueType, + const UnownedStringSlice& funcName, + const List<String>& values); + +} diff --git a/source/compiler-core/slang-spirv-core-grammar.cpp b/source/compiler-core/slang-spirv-core-grammar.cpp new file mode 100644 index 000000000..8140d002a --- /dev/null +++ b/source/compiler-core/slang-spirv-core-grammar.cpp @@ -0,0 +1,340 @@ +#include "slang-spirv-core-grammar.h" + +#include "../core/slang-rtti-util.h" +#include "../core/slang-string-util.h" +#include "slang-json-native.h" +#include "slang-core-diagnostics.h" +#include <limits> + +namespace Slang +{ +using SpvWord = uint32_t; + +// +// Structs which mirror the structure of spirv.core.grammar.json +// +// Commented members are those which currently don't use +struct InstructionPrintingClass +{ + UnownedStringSlice tag; + UnownedStringSlice heading; +}; +SLANG_MAKE_STRUCT_RTTI_INFO( + InstructionPrintingClass, + SLANG_RTTI_FIELD(tag), + SLANG_OPTIONAL_RTTI_FIELD(heading) +); + +struct Operand +{ + UnownedStringSlice kind; + UnownedStringSlice quantifier; + // UnownedStringSlice name; +}; +SLANG_MAKE_STRUCT_RTTI_INFO( + Operand, + SLANG_RTTI_FIELD(kind), + SLANG_OPTIONAL_RTTI_FIELD(quantifier) + //SLANG_RTTI_FIELD(name), +); + +struct Instruction +{ + UnownedStringSlice opname; + UnownedStringSlice class_; + SpvWord opcode; + List<UnownedStringSlice> capabilities; + List<Operand> operands; +}; +SLANG_MAKE_STRUCT_RTTI_INFO( + Instruction, + SLANG_RTTI_FIELD(opname), + SLANG_RTTI_FIELD_IMPL(class_, "class", 0), + SLANG_RTTI_FIELD(opcode), + SLANG_OPTIONAL_RTTI_FIELD(capabilities), + SLANG_OPTIONAL_RTTI_FIELD(operands) +); + +struct Enumerant +{ + UnownedStringSlice enumerant; + JSONValue value; + List<UnownedStringSlice> capabilities; + // List<Operand> parameters; + // UnownedStringSlice version; + // UnownedStringSlice lastVersion; + // List<UnownedStringSlice> extensions; +}; +SLANG_MAKE_STRUCT_RTTI_INFO( + Enumerant, + SLANG_RTTI_FIELD(enumerant), + SLANG_RTTI_FIELD(value), + SLANG_OPTIONAL_RTTI_FIELD(capabilities), + // SLANG_OPTIONAL_RTTI_FIELD(parameters), + // SLANG_OPTIONAL_RTTI_FIELD(version), + // SLANG_OPTIONAL_RTTI_FIELD(lastVersion), + // SLANG_OPTIONAL_RTTI_FIELD(extensions) +); + +struct OperandKind +{ + UnownedStringSlice category; + UnownedStringSlice kind; + List<Enumerant> enumerants; +}; +SLANG_MAKE_STRUCT_RTTI_INFO( + OperandKind, + SLANG_RTTI_FIELD(category), + SLANG_RTTI_FIELD(kind), + SLANG_OPTIONAL_RTTI_FIELD(enumerants) +); + +struct SPIRVSpec +{ + // List<UnownedStringSlice> copyright; + // UnownedStringSlice magic_number; + // UInt32 major_version; + // UInt32 minor_version; + // UInt32 revision; + List<InstructionPrintingClass> instruction_printing_class; + List<Instruction> instructions; + List<OperandKind> operand_kinds; +}; +SLANG_MAKE_STRUCT_RTTI_INFO( + SPIRVSpec, + // SLANG_RTTI_FIELD(copyright), + // SLANG_RTTI_FIELD(magic_number), + // SLANG_RTTI_FIELD(major_version) + // SLANG_RTTI_FIELD(minor_version) + // SLANG_RTTI_FIELD(revision) + SLANG_RTTI_FIELD(instruction_printing_class), + SLANG_RTTI_FIELD(instructions), + SLANG_RTTI_FIELD(operand_kinds) +); + +static Dictionary<UnownedStringSlice, SpvWord> operandKindToDict( + JSONContainer& container, + DiagnosticSink& sink, + const OperandKind& k) +{ + Dictionary<UnownedStringSlice, SpvWord> dict; + dict.reserve(k.enumerants.getCount()); + for(const auto& e : k.enumerants) + { + SpvWord valueInt = 0; + switch(e.value.getKind()) + { + case JSONValue::Kind::Integer: + { + // TODO: Range check here? + valueInt = SpvWord(container.asInteger(e.value)); + break; + } + case JSONValue::Kind::String: + { + Int i = 0; + const auto str = container.getString(e.value); + if(SLANG_FAILED(StringUtil::parseInt(str, i))) + sink.diagnose( + e.value.loc, + MiscDiagnostics::spirvCoreGrammarJSONParseFailure, + "Expected an integer value" + ); + // TODO: Range check here? + valueInt = SpvWord(i); + break; + } + default: + sink.diagnose( + e.value.loc, + MiscDiagnostics::spirvCoreGrammarJSONParseFailure, + "Expected an integer value (or a string with an integer inside)" + ); + } + dict.add(e.enumerant, valueInt); + } + return dict; +} + +// +// +// +RefPtr<SPIRVCoreGrammarInfo> SPIRVCoreGrammarInfo::loadFromJSON(SourceView& source, DiagnosticSink& sink) +{ + // + // Load the JSON + // + SLANG_ASSERT(source.getSourceManager() == sink.getSourceManager()); + JSONLexer lexer; + lexer.init(&source, &sink); + JSONParser parser; + JSONContainer container(sink.getSourceManager()); + JSONBuilder builder(&container); + RttiTypeFuncsMap typeMap; + typeMap = JSONNativeUtil::getTypeFuncsMap(); + SLANG_RETURN_NULL_ON_FAIL(parser.parse(&lexer, &source, &builder, &sink)); + JSONToNativeConverter converter(&container, &typeMap, &sink); + SPIRVSpec spec; + if(SLANG_FAILED(converter.convert(builder.getRootValue(), &spec))) + { + // TODO: not having a source loc here is not great... + sink.diagnoseWithoutSourceView( + SourceLoc{}, + MiscDiagnostics::spirvCoreGrammarJSONParseFailure, + "Failed to match SPIR-V grammar JSON to the expected schema" + ); + return nullptr; + } + + // + // Convert to the internal representation + // + RefPtr<SPIRVCoreGrammarInfo> res{new SPIRVCoreGrammarInfo}; + + res->operandKinds.dict.reserve(spec.operand_kinds.getCount()); + uint32_t operandKindIndex = 0; + for(const auto& c : spec.operand_kinds) + { + if(operandKindIndex > std::numeric_limits<decltype(OperandKind::index)>::max()) + { + sink.diagnoseWithoutSourceView( + SourceLoc{}, + MiscDiagnostics::spirvCoreGrammarJSONParseFailure, + "Too many enum categories, expected fewer than 256" + ); + } + res->operandKinds.dict.add(c.kind, {static_cast<decltype(OperandKind::index)>(operandKindIndex)}); + operandKindIndex++; + } + + // It's important we reserve the memory now, as we require the iterators to + // be stable, as references to them are maintained by the OpInfo structs. + Index totalNumOperands = 0; + for(const auto& i : spec.instructions) + totalNumOperands += i.operands.getCapacity(); + res->operandTypesStorage.reserve(totalNumOperands); + + res->opcodes.dict.reserve(spec.instructions.getCount()); + for(const auto& i : spec.instructions) + { + res->opcodes.dict.add(i.opname, SpvOp(i.opcode)); + + const auto class_ = + i.class_ == "Type-Declaration" ? OpInfo::TypeDeclaration + : i.class_ == "Constant-Creation" ? OpInfo::ConstantCreation + : i.class_ == "Debug" ? OpInfo::Debug + : OpInfo::Other; + + const auto resultTypeIndex + = i.operands.findFirstIndex([](const auto& o){return o.kind == "IdResultType";}); + const auto resultIdIndex + = i.operands.findFirstIndex([](const auto& o){return o.kind == "IdResult";}); + SLANG_ASSERT(resultTypeIndex >= -1 || resultTypeIndex <= 0); + SLANG_ASSERT(resultIdIndex >= -1 || resultTypeIndex <= 1); + + uint16_t minOperandCount = 0; + uint16_t maxOperandCount = 0; + uint16_t numOperandTypes = 0; + const OperandKind* operandTypes = res->operandTypesStorage.end(); + for(const auto& o : i.operands) + { + if(maxOperandCount == 0xffff) + { + // We are about to overflow maxWordCount, either someone has + // put 2^16 operands in the json, or we have a "*" quantified + // operand not in the last position and should implement + // support for that + sink.diagnoseWithoutSourceView( + SourceLoc{}, + MiscDiagnostics::spirvCoreGrammarJSONParseFailure, + "\"*\"-qualified operand wasn't the last operand" + ); + } + + const auto catIndex = res->operandKinds.lookup(o.kind); + if(!catIndex) + { + sink.diagnoseWithoutSourceView( + SourceLoc{}, + MiscDiagnostics::spirvCoreGrammarJSONParseFailure, + "Operand references a kind which doesn't exist" + ); + continue; + } + + numOperandTypes++; + res->operandTypesStorage.add(*catIndex); + + if(o.quantifier == "") + { + // This catches the case where an "?" or "*" qualified operand + // appears before any unqualified operands + if(minOperandCount != maxOperandCount) + sink.diagnoseWithoutSourceView( + SourceLoc{}, + MiscDiagnostics::spirvCoreGrammarJSONParseFailure, + "\"*\" or \"?\" operand appeared before an unqualified operand" + ); + minOperandCount++; + maxOperandCount++; + } + else if(o.quantifier == "?") + { + maxOperandCount++; + } + else if(o.quantifier == "*") + { + maxOperandCount = 0xffff; + } + } + + // There are duplicate opcodes in the json (for renamed instructions, + // or the same instruction with different capabilities), for now just + // keep the first one. + res->opInfos.dict.addIfNotExists(SpvOp(i.opcode), { + class_, + static_cast<int8_t>(resultTypeIndex), + static_cast<int8_t>(resultIdIndex), + minOperandCount, + maxOperandCount, + numOperandTypes, + operandTypes + }); + res->opNames.dict.addIfNotExists(SpvOp(i.opcode), i.opname); + } + + for(const auto& k : spec.operand_kinds) + { + const auto kindIndex = res->operandKinds.dict.getValue(k.kind); + const auto d = operandKindToDict(container, sink, k); + for(const auto& [n, v] : d) + { + // Add the string to this slice pool as we'll be taking ownership + // of it shortly but don't want to invalidate it in the meantime. + const auto s = container.getStringSlicePool().addAndGetSlice(String(k.kind) + n); + res->allEnumsWithTypePrefix.dict.add(s, v); + res->allEnums.dict.add({kindIndex, n}, v); + res->allEnumNames.dict.addIfNotExists({kindIndex, v}, n); + } + + res->operandKindNames.dict.add(kindIndex, k.kind); + + if(k.kind == "Capability") + for(const auto& [n, v] : d) + res->capabilities.dict.add(n, SpvCapability(v)); + + // If this starts with Id, and the suffix is also an operand kind, + // assume that this is an Id wrapper + if(k.kind.startsWith("Id")) + { + const UnownedStringSlice underneathIdKind{k.kind.begin()+2, k.kind.end()}; + OperandKind targetIndex; + if(res->operandKinds.dict.tryGetValue(underneathIdKind, targetIndex)) + res->operandKindUnderneathIds.dict.add(kindIndex, targetIndex); + } + } + // Steal the strings from the JSON container before it dies + res->strings.swapWith(container.getStringSlicePool()); + return res; +} +} diff --git a/source/compiler-core/slang-spirv-core-grammar.h b/source/compiler-core/slang-spirv-core-grammar.h new file mode 100644 index 000000000..7fe8f6cd7 --- /dev/null +++ b/source/compiler-core/slang-spirv-core-grammar.h @@ -0,0 +1,145 @@ +#pragma once + +#include "../core/slang-smart-pointer.h" +#include "../core/slang-string.h" +#include "../core/slang-string-slice-pool.h" +#include "../core/slang-dictionary.h" +#include "../../external/spirv-headers/include/spirv/unified1/spirv.h" +#include <optional> + +namespace Slang +{ + using SpvWord = uint32_t; + class DiagnosticSink; + class SourceView; + + struct SPIRVCoreGrammarInfo : public RefObject + { + static RefPtr<SPIRVCoreGrammarInfo> loadFromJSON(SourceView& source, DiagnosticSink& sink); + static RefPtr<SPIRVCoreGrammarInfo> getEmbeddedVersion(); + + template<typename K, typename T> + struct Lookup + { + std::optional<T> lookup(const K& name) const + { + T ret; + if(embedded ? embedded(name, ret) : dict.tryGetValue(name, ret)) + return ret; + else + return std::nullopt; + } + + bool (*embedded)(const K&, T&) = nullptr; + Dictionary<K, T> dict; + }; + + struct OperandKind + { + uint8_t index; + SLANG_COMPONENTWISE_HASHABLE_1; + SLANG_COMPONENTWISE_EQUALITY_1(OperandKind); + }; + + struct QualifiedEnumName + { + OperandKind kind; + UnownedStringSlice name; + SLANG_COMPONENTWISE_HASHABLE_2; + SLANG_COMPONENTWISE_EQUALITY_2(QualifiedEnumName); + }; + + struct QualifiedEnumValue + { + OperandKind kind; + SpvWord value; + SLANG_COMPONENTWISE_HASHABLE_2; + SLANG_COMPONENTWISE_EQUALITY_2(QualifiedEnumValue); + }; + + struct OpInfo + { + enum Class + { + // Unrecognized instructions go in here + Other, + + // Adding to this? Don't forget to update the embedding generator + Miscellaneous, + Debug, + Annotation, + Extension, + ModeSetting, + TypeDeclaration, + ConstantCreation, + Memory, + Function, + Image, + Conversion, + Composite, + Arithmetic, + Bit, + Relational_and_Logical, + Derivative, + ControlFlow, + Atomic, + Primitive, + Barrier, + Group, + DeviceSideEnqueue, + Pipe, + NonUniform, + Reserved, + }; + constexpr static int8_t kNoResultTypeId = -1; + constexpr static int8_t kNoResultId = -1; + + Class class_; + // -1 or 0 + int8_t resultTypeIndex = kNoResultTypeId; + // -1 or 0 or 1 + int8_t resultIdIndex = kNoResultId; + // The range of valid operand counts for this instruction, + // including any result type and id. Multi-word operands count as a + // single operand. + uint16_t minOperandCount; + uint16_t maxOperandCount; + // when looking up an operand type, clamp to this number-1 to + // account for variable length operands at the end + uint16_t numOperandTypes; + const OperandKind* operandTypes; + }; + + // + // Our tables: + // + + // Instruction name to opcode + Lookup<UnownedStringSlice, SpvOp> opcodes; + // Capability name to value + Lookup<UnownedStringSlice, SpvCapability> capabilities; + // String-qualified enum name (one with the type prefix) to value + Lookup<UnownedStringSlice, SpvWord> allEnumsWithTypePrefix; + // kind * enum name to value + Lookup<QualifiedEnumName, SpvWord> allEnums; + // kine * enum value to unqualified name + Lookup<QualifiedEnumValue, UnownedStringSlice> allEnumNames; + // Any other information on instructions + Lookup<SpvOp, OpInfo> opInfos; + // Opcode to instruction name + Lookup<SpvOp, UnownedStringSlice> opNames; + // Operand kind string to numeric id + Lookup<UnownedStringSlice, OperandKind> operandKinds; + // Operand kind id to string + Lookup<OperandKind, UnownedStringSlice> operandKindNames; + // Operand kind to the "un-id" version of itself, for example IdMemorySemantics to MemorySemantics + Lookup<OperandKind, OperandKind> operandKindUnderneathIds; + + private: + + // If this is loaded from JSON, we keep the strings around instead of + // copying them as dictionary keys + StringSlicePool strings = StringSlicePool(StringSlicePool::Style::Empty); + List<OperandKind> operandTypesStorage; + }; +} |
