// Copyright (c) 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Compressed grammar tables. #include "source/table2.h" #include #include #include #include "source/extensions.h" #include "source/latest_version_spirv_header.h" #include "source/spirv_constant.h" #include "source/spirv_target_env.h" #include "spirv-tools/libspirv.hpp" namespace spvtools { namespace { // This is used in the source for the generated tables. constexpr inline IndexRange IR(uint32_t first, uint32_t count) { return IndexRange{first, count}; } struct NameIndex { // Location of the null-terminated name in the global string table kStrings. IndexRange name; // Index of this name's entry in the corresponding by-value table. uint32_t index; }; struct NameValue { // Location of the null-terminated name in the global string table kStrings. IndexRange name; // Enum value in the binary format. uint32_t value; }; // The generated include file contains variables: // // std::array kOperandNames: // Operand names and index, ordered by (operand kind, name) // The index part is the named entry's index in kOperandsByValue array. // Aliases are included as their own entries. // // std::array kOperandsByValue: // Operand descriptions, ordered by (operand kind, operand enum value). // // std::array kInstructionNames: // Instruction names and index, ordered by (name, value) // The index part is the named entry's index in kInstructionDesc array. // Aliases are included as their own entries. // // std::array kInstructionDesc // Instruction descriptions, ordered by opcode. // // const char kStrings[] // Array of characters, referenced by IndexRanges elsewhere. // Each IndexRange denotes a string. // // const IndexRange kAliasSpans[] // Array of IndexRanges, where each represents a string by referencing // the kStrings table. // This array contains all sequences of alias strings used in the grammar. // This table is referenced by an IndexRange elsewhere, i.e. by the // 'aliases' field of an instruction or operand description. // // const spv::Capability kCapabilitySpans[] // Array of capabilities, referenced by IndexRanges elsewhere. // Contains all sequences of capabilities used in the grammar. // // const spvtools::Extension kExtensionSpans[] = { // Array of extensions, referenced by IndexRanges elsewhere. // Contains all sequences of extensions used in the grammar. // // const spv_operand_type_t kOperandSpans[] = { // Array of operand types, referenced by IndexRanges elsewhere. // Contains all sequences of operand types used in the grammar. // Maps an operand kind to NameValue entries for that kind. // The result is an IndexRange into kOperandNames, and are sorted // by string name within that span. IndexRange OperandNameRangeForKind(spv_operand_type_t type); // Maps an operand kind to possible operands for that kind. // The result is an IndexRange into kOperandsByValue, and the operands // are sorted by value within that span. IndexRange OperandByValueRangeForKind(spv_operand_type_t type); // Maps an extended instruction set kind to NameValue entries for that kind. // The result is an IndexRange into kExtIntNames, and are sorted // by string name within that span. IndexRange ExtInstNameRangeForKind(spv_ext_inst_type_t type); // Maps an extended instruction set kind to possible operands for that kind. // The result is an IndexRange into kExtInstByValue, and the instructions // are sorted by opcode value within that span. IndexRange ExtInstByValueRangeForKind(spv_ext_inst_type_t type); // Returns the name of an extension, as an index into kStrings IndexRange ExtensionToIndexRange(Extension extension); #include "core_tables_body.inc" // Returns a pointer to the null-terminated C-style string in the global // strings table, as referenced by 'ir'. Assumes the given range is valid. const char* getChars(IndexRange ir) { assert(ir.first() < sizeof(kStrings)); return ir.apply(kStrings).data(); } } // anonymous namespace utils::Span OperandDesc::operands() const { return operands_range.apply(kOperandSpans); } utils::Span OperandDesc::name() const { return name_range.apply(kStrings); } utils::Span OperandDesc::aliases() const { return name_range.apply(kAliasSpans); } utils::Span OperandDesc::capabilities() const { return capabilities_range.apply(kCapabilitySpans); } utils::Span OperandDesc::extensions() const { return extensions_range.apply(kExtensionSpans); } utils::Span InstructionDesc::operands() const { return operands_range.apply(kOperandSpans); } utils::Span InstructionDesc::name() const { return name_range.apply(kStrings); } utils::Span InstructionDesc::aliases() const { return name_range.apply(kAliasSpans); } utils::Span InstructionDesc::capabilities() const { return capabilities_range.apply(kCapabilitySpans); } utils::Span InstructionDesc::extensions() const { return extensions_range.apply(kExtensionSpans); } utils::Span ExtInstDesc::operands() const { return operands_range.apply(kOperandSpans); } utils::Span ExtInstDesc::name() const { return name_range.apply(kStrings); } utils::Span ExtInstDesc::capabilities() const { return capabilities_range.apply(kCapabilitySpans); } spv_result_t LookupOpcode(spv::Op opcode, const InstructionDesc** desc) { // Metaphor: Look for the needle in the haystack. const InstructionDesc needle(opcode); auto where = std::lower_bound( kInstructionDesc.begin(), kInstructionDesc.end(), needle, [&](const InstructionDesc& lhs, const InstructionDesc& rhs) { return uint32_t(lhs.opcode) < uint32_t(rhs.opcode); }); if (where != kInstructionDesc.end() && where->opcode == opcode) { *desc = &*where; return SPV_SUCCESS; } return SPV_ERROR_INVALID_LOOKUP; } spv_result_t LookupOpcode(const char* name, const InstructionDesc** desc) { // The comparison function knows to use 'name' string to compare against // when the value is kSentinel. const auto kSentinel = uint32_t(-1); const NameIndex needle{{}, kSentinel}; auto less = [&](const NameIndex& lhs, const NameIndex& rhs) { const char* lhs_chars = lhs.index == kSentinel ? name : getChars(lhs.name); const char* rhs_chars = rhs.index == kSentinel ? name : getChars(rhs.name); return std::strcmp(lhs_chars, rhs_chars) < 0; }; auto where = std::lower_bound(kInstructionNames.begin(), kInstructionNames.end(), needle, less); if (where != kInstructionNames.end() && std::strcmp(getChars(where->name), name) == 0) { *desc = &kInstructionDesc[where->index]; return SPV_SUCCESS; } return SPV_ERROR_INVALID_LOOKUP; } namespace { template spv_result_t LookupOpcodeForEnvInternal(spv_target_env env, KEY_TYPE key, const InstructionDesc** desc) { const InstructionDesc* desc_proxy; auto status = LookupOpcode(key, &desc_proxy); if (status != SPV_SUCCESS) { return status; } const auto& entry = *desc_proxy; const auto version = spvVersionForTargetEnv(env); if ((version >= entry.minVersion && version <= entry.lastVersion) || entry.extensions_range.count() > 0 || entry.capabilities_range.count() > 0) { *desc = desc_proxy; return SPV_SUCCESS; } return SPV_ERROR_INVALID_LOOKUP; } } // namespace spv_result_t LookupOpcodeForEnv(spv_target_env env, const char* name, const InstructionDesc** desc) { return LookupOpcodeForEnvInternal(env, name, desc); } spv_result_t LookupOpcodeForEnv(spv_target_env env, spv::Op opcode, const InstructionDesc** desc) { return LookupOpcodeForEnvInternal(env, opcode, desc); } spv_result_t LookupOperand(spv_operand_type_t type, uint32_t value, const OperandDesc** desc) { auto ir = OperandByValueRangeForKind(type); if (ir.empty()) { return SPV_ERROR_INVALID_LOOKUP; } auto span = ir.apply(kOperandsByValue.data()); // Metaphor: Look for the needle in the haystack. // The operand value is the first member. const OperandDesc needle{value}; auto where = std::lower_bound(span.begin(), span.end(), needle, [&](const OperandDesc& lhs, const OperandDesc& rhs) { return lhs.value < rhs.value; }); if (where != span.end() && where->value == value) { *desc = &*where; return SPV_SUCCESS; } return SPV_ERROR_INVALID_LOOKUP; } spv_result_t LookupOperand(spv_operand_type_t type, const char* name, size_t name_len, const OperandDesc** desc) { auto ir = OperandNameRangeForKind(type); if (ir.empty()) { return SPV_ERROR_INVALID_LOOKUP; } auto span = ir.apply(kOperandNames.data()); // The comparison function knows to use (name, name_len) as the // string to compare against when the value is kSentinel. const auto kSentinel = uint32_t(-1); const NameIndex needle{{}, kSentinel}; // The strings in the global string table are null-terminated, and the count // reflects that. So always deduct 1 from its length. auto less = [&](const NameIndex& lhs, const NameIndex& rhs) { const char* lhs_chars = lhs.index == kSentinel ? name : getChars(lhs.name); const char* rhs_chars = rhs.index == kSentinel ? name : getChars(rhs.name); const auto content_cmp = std::strncmp(lhs_chars, rhs_chars, name_len); if (content_cmp != 0) { return content_cmp < 0; } const auto lhs_len = lhs.index == kSentinel ? name_len : lhs.name.count() - 1; const auto rhs_len = rhs.index == kSentinel ? name_len : rhs.name.count() - 1; return lhs_len < rhs_len; }; auto where = std::lower_bound(span.begin(), span.end(), needle, less); if (where != span.end() && where->name.count() - 1 == name_len && std::strncmp(getChars(where->name), name, name_len) == 0) { *desc = &kOperandsByValue[where->index]; return SPV_SUCCESS; } return SPV_ERROR_INVALID_LOOKUP; } spv_result_t LookupExtInst(spv_ext_inst_type_t type, const char* name, const ExtInstDesc** desc) { auto ir = ExtInstNameRangeForKind(type); if (ir.empty()) { return SPV_ERROR_INVALID_LOOKUP; } auto span = ir.apply(kExtInstNames.data()); // The comparison function knows to use 'name' string to compare against // when the value is kSentinel. const auto kSentinel = uint32_t(-1); const NameIndex needle{{}, kSentinel}; auto less = [&](const NameIndex& lhs, const NameIndex& rhs) { const char* lhs_chars = lhs.index == kSentinel ? name : getChars(lhs.name); const char* rhs_chars = rhs.index == kSentinel ? name : getChars(rhs.name); return std::strcmp(lhs_chars, rhs_chars) < 0; }; auto where = std::lower_bound(span.begin(), span.end(), needle, less); if (where != span.end() && std::strcmp(getChars(where->name), name) == 0) { *desc = &kExtInstByValue[where->index]; return SPV_SUCCESS; } return SPV_ERROR_INVALID_LOOKUP; } // Finds the extended instruction description by opcode value. // On success, returns SPV_SUCCESS and updates *desc. spv_result_t LookupExtInst(spv_ext_inst_type_t type, uint32_t value, const ExtInstDesc** desc) { auto ir = ExtInstByValueRangeForKind(type); if (ir.empty()) { return SPV_ERROR_INVALID_LOOKUP; } auto span = ir.apply(kExtInstByValue.data()); // Metaphor: Look for the needle in the haystack. // The operand value is the first member. const ExtInstDesc needle(value); auto where = std::lower_bound(span.begin(), span.end(), needle, [&](const ExtInstDesc& lhs, const ExtInstDesc& rhs) { return lhs.value < rhs.value; }); if (where != span.end() && where->value == value) { *desc = &*where; return SPV_SUCCESS; } return SPV_ERROR_INVALID_LOOKUP; } const char* ExtensionToString(Extension extension) { return getChars(ExtensionToIndexRange(extension)); } bool GetExtensionFromString(const char* name, Extension* extension) { // The comparison function knows to use 'name' string to compare against // when the value is kSentinel. const auto kSentinel = uint32_t(-1); const NameValue needle{{}, kSentinel}; auto less = [&](const NameValue& lhs, const NameValue& rhs) { const char* lhs_chars = lhs.value == kSentinel ? name : getChars(lhs.name); const char* rhs_chars = rhs.value == kSentinel ? name : getChars(rhs.name); return std::strcmp(lhs_chars, rhs_chars) < 0; }; auto where = std::lower_bound(kExtensionNames.begin(), kExtensionNames.end(), needle, less); if (where != kExtensionNames.end() && std::strcmp(getChars(where->name), name) == 0) { *extension = static_cast(where->value); return true; } return false; } } // namespace spvtools