diff --git a/Android.mk b/Android.mk index ac280b9e7f..86c0b830db 100644 --- a/Android.mk +++ b/Android.mk @@ -25,6 +25,7 @@ SPVTOOLS_SRC_FILES := \ source/spirv_target_env.cpp \ source/spirv_validator_options.cpp \ source/table.cpp \ + source/table2.cpp \ source/text.cpp \ source/text_handler.cpp \ source/to_string.cpp \ @@ -206,24 +207,20 @@ SPV_CLDEBUGINFO100_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/extin SPV_VKDEBUGINFO100_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/extinst.nonsemantic.shader.debuginfo.100.grammar.json define gen_spvtools_grammar_tables -$(call generate-file-dir,$(1)/core.insts-unified1.inc) -$(1)/core.insts-unified1.inc $(1)/operand.kinds-unified1.inc \ +$(call generate-file-dir,$(1)/core_tables.inc) +$(1)/core_tables.inc \ : \ $(LOCAL_PATH)/utils/generate_grammar_tables.py \ $(SPV_COREUNIFIED1_GRAMMAR) \ - $(SPV_GLSL_GRAMMAR) \ - $(SPV_OpenCL_GRAMMAR) \ $(SPV_DEBUGINFO_GRAMMAR) \ $(SPV_CLDEBUGINFO100_GRAMMAR) - @$(HOST_PYTHON) $(LOCAL_PATH)/utils/generate_grammar_tables.py \ + @$(HOST_PYTHON) $(LOCAL_PATH)/utils/ggt.py \ --spirv-core-grammar=$(SPV_COREUNIFIED1_GRAMMAR) \ --extinst-debuginfo-grammar=$(SPV_DEBUGINFO_GRAMMAR) \ --extinst-cldebuginfo100-grammar=$(SPV_CLDEBUGINFO100_GRAMMAR) \ - --core-insts-output=$(1)/core.insts-unified1.inc \ - --operand-kinds-output=$(1)/operand.kinds-unified1.inc + --core-tables-output=$(1)/core_tables.inc @echo "[$(TARGET_ARCH_ABI)] Grammar (from unified1) : instructions & operands <= grammar JSON files" -$(LOCAL_PATH)/source/opcode.cpp: $(1)/core.insts-unified1.inc -$(LOCAL_PATH)/source/operand.cpp: $(1)/operand.kinds-unified1.inc +$(LOCAL_PATH)/source/table2.cpp: $(1)/core_tables.inc $(LOCAL_PATH)/source/ext_inst.cpp: \ $(1)/glsl.std.450.insts.inc \ $(1)/opencl.std.100.insts.inc \ diff --git a/BUILD.bazel b/BUILD.bazel index da9935c577..b460693cf6 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -5,7 +5,7 @@ load( "DEBUGINFO_GRAMMAR_JSON_FILE", "SHDEBUGINFO100_GRAMMAR_JSON_FILE", "TEST_COPTS", - "generate_core_tables", + "generate_compressed_tables", "generate_enum_string_mapping", "generate_extinst_lang_headers", "generate_vendor_tables", @@ -27,16 +27,35 @@ exports_files([ ]) py_binary( + # Generates tables for extensions and extended instruction sets. + # TODO(crbug.com/266223071) Replace this with ggt.py name = "generate_grammar_tables", srcs = ["utils/generate_grammar_tables.py"], ) +py_binary( + # The script that generates compressed grammar tables for + # instructions and operands. + # TODO(crbug.com/266223071) Eventually this will fully replace + # utils/generate_grammar_tables.py. + name = "ggt", + main = "utils/ggt.py", # The file found by $(location :ggt) + srcs = [ + "utils/ggt.py", + "utils/Table/__init__.py", + "utils/Table/Context.py", + "utils/Table/IndexRange.py", + "utils/Table/Operand.py", + "utils/Table/StringList.py", + ], +) + py_binary( name = "generate_language_headers", srcs = ["utils/generate_language_headers.py"], ) -generate_core_tables(version = "unified1") +generate_compressed_tables() generate_enum_string_mapping(version = "unified1") @@ -142,7 +161,7 @@ cc_library( "source/val/*.cpp", ]) + [ ":build_version_inc", - ":gen_core_tables_unified1", + ":gen_compressed_tables", ":gen_enum_string_mapping", ":gen_extinst_lang_headers_DebugInfo", ":gen_extinst_lang_headers_NonSemanticShaderDebugInfo100", diff --git a/BUILD.gn b/BUILD.gn index 5f7a1bb7c5..4de49e3faa 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -34,41 +34,35 @@ spirv_headers = spirv_tools_spirv_headers_dir spirv_is_winuwp = is_win && target_os == "winuwp" template("spvtools_core_tables") { - assert(defined(invoker.version), "Need version in $target_name generation.") - + # Generate a compressed table describing SPIR-V core spec instructions and operands. action("spvtools_core_tables_" + target_name) { - script = "utils/generate_grammar_tables.py" - - version = invoker.version + script = "utils/ggt.py" core_json_file = - "${spirv_headers}/include/spirv/$version/spirv.core.grammar.json" - core_insts_file = "${target_gen_dir}/core.insts-$version.inc" - operand_kinds_file = "${target_gen_dir}/operand.kinds-$version.inc" + "${spirv_headers}/include/spirv/unified1/spirv.core.grammar.json" debuginfo_insts_file = "${spirv_headers}/include/spirv/unified1/extinst.debuginfo.grammar.json" cldebuginfo100_insts_file = "${spirv_headers}/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json" + core_tables_file = "${target_gen_dir}/core_tables.inc" + sources = [ cldebuginfo100_insts_file, core_json_file, debuginfo_insts_file, ] outputs = [ - core_insts_file, - operand_kinds_file, + core_tables_file, ] args = [ "--spirv-core-grammar", rebase_path(core_json_file, root_build_dir), - "--core-insts-output", - rebase_path(core_insts_file, root_build_dir), "--extinst-debuginfo-grammar", rebase_path(debuginfo_insts_file, root_build_dir), "--extinst-cldebuginfo100-grammar", rebase_path(cldebuginfo100_insts_file, root_build_dir), - "--operand-kinds-output", - rebase_path(operand_kinds_file, root_build_dir) + "--core-tables-output", + rebase_path(core_tables_file, root_build_dir) ] } } @@ -267,7 +261,6 @@ action("spvtools_build_version") { } spvtools_core_tables("unified1") { - version = "unified1" } spvtools_core_enums("unified1") { version = "unified1" diff --git a/CMakeLists.txt b/CMakeLists.txt index abb5450587..8c6fe41e1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -240,6 +240,20 @@ endif() # Tests require Python3 find_host_package(Python3 REQUIRED) +# Check type annotations in Perl code. Assumes mypy. +add_custom_target(spirv-tools-check-python-types + COMMAND mypy Table + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/utils +) +set_target_properties(spirv-tools-check-python-types + PROPERTIES EXCLUDE_FROM_ALL ON) + +# Run Python unit tests +add_custom_target(spirv-tools-check-python-tests + COMMAND Python3::Interpreter -m unittest discover -v -p "*test.py" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/utils +) + # Check for symbol exports on Linux. # At the moment, this check will fail on the OSX build machines for the Android NDK. # It appears they don't have objdump. diff --git a/build_defs.bzl b/build_defs.bzl index ebb29b25af..e09d3f1faa 100644 --- a/build_defs.bzl +++ b/build_defs.bzl @@ -47,6 +47,7 @@ def incompatible_with(incompatible_constraints): for constraint in incompatible_constraints }])) +SPIRV_CORE_GRAMMAR_JSON_FILE = "@spirv_headers//:spirv_core_grammar_unified1" DEBUGINFO_GRAMMAR_JSON_FILE = "@spirv_headers//:spirv_ext_inst_debuginfo_grammar_unified1" CLDEBUGINFO100_GRAMMAR_JSON_FILE = "@spirv_headers//:spirv_ext_inst_opencl_debuginfo_100_grammar_unified1" SHDEBUGINFO100_GRAMMAR_JSON_FILE = "@spirv_headers//:spirv_ext_inst_nonsemantic_shader_debuginfo_100_grammar_unified1" @@ -57,6 +58,7 @@ def _merge_dicts(dicts): merged.update(d) return merged +# TODO(b/413743565): Remove after legacy grammars removed. def generate_core_tables(version): if not version: fail("Must specify version", "version") @@ -91,6 +93,35 @@ def generate_core_tables(version): visibility = ["//visibility:private"], ) +def generate_compressed_tables(): + grammars = dict( + core_grammar = SPIRV_CORE_GRAMMAR_JSON_FILE, + debuginfo_grammar = DEBUGINFO_GRAMMAR_JSON_FILE, + cldebuginfo_grammar = CLDEBUGINFO100_GRAMMAR_JSON_FILE, + ) + + outs = dict( + core_tables_output = "core_tables.inc", + ) + + cmd = ( + "$(location :ggt)" + + " --spirv-core-grammar=$(location {core_grammar})" + + " --extinst-debuginfo-grammar=$(location {debuginfo_grammar})" + + " --extinst-cldebuginfo100-grammar=$(location {cldebuginfo_grammar})" + + " --core-tables-output=$(location {core_tables_output})" + ).format(**_merge_dicts([grammars, outs])) + + native.genrule( + name = "gen_compressed_tables", + srcs = grammars.values(), + outs = outs.values(), + cmd = cmd, + cmd_bat = cmd, + tools = [":ggt"], + visibility = ["//visibility:private"], + ) + def generate_enum_string_mapping(version): if not version: fail("Must specify version", "version") diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index ee44f27a86..7837918afb 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -16,35 +16,27 @@ set(GRAMMAR_PROCESSING_SCRIPT "${spirv-tools_SOURCE_DIR}/utils/generate_grammar_ set(VIMSYNTAX_PROCESSING_SCRIPT "${spirv-tools_SOURCE_DIR}/utils/vim/generate_syntax.py") set(XML_REGISTRY_PROCESSING_SCRIPT "${spirv-tools_SOURCE_DIR}/utils/generate_registry_tables.py") set(LANG_HEADER_PROCESSING_SCRIPT "${spirv-tools_SOURCE_DIR}/utils/generate_language_headers.py") +set(GGT_SCRIPT "${spirv-tools_SOURCE_DIR}/utils/ggt.py") # Pull in grammar files that have migrated to SPIRV-Headers +set(SPIRV_CORE_GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/unified1/spirv.core.grammar.json") set(DEBUGINFO_GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/unified1/extinst.debuginfo.grammar.json") set(CLDEBUGINFO100_GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json") set(VKDEBUGINFO100_GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/unified1/extinst.nonsemantic.shader.debuginfo.100.grammar.json") -# macro() definitions are used in the following because we need to append .inc -# file paths into some global lists (*_CPP_DEPENDS). And those global lists are -# later used by set_source_files_properties() calls. -# function() definitions are not suitable because they create new scopes. -macro(spvtools_core_tables CONFIG_VERSION) - set(GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/${CONFIG_VERSION}/spirv.core.grammar.json") - set(GRAMMAR_INSTS_INC_FILE "${spirv-tools_BINARY_DIR}/core.insts-${CONFIG_VERSION}.inc") - set(GRAMMAR_KINDS_INC_FILE "${spirv-tools_BINARY_DIR}/operand.kinds-${CONFIG_VERSION}.inc") - add_custom_command(OUTPUT ${GRAMMAR_INSTS_INC_FILE} ${GRAMMAR_KINDS_INC_FILE} - COMMAND Python3::Interpreter ${GRAMMAR_PROCESSING_SCRIPT} - --spirv-core-grammar=${GRAMMAR_JSON_FILE} +set(CORE_TABLES_INC_FILE ${spirv-tools_BINARY_DIR}/core_tables.inc) +add_custom_command(OUTPUT ${CORE_TABLES_INC_FILE} + COMMAND Python3::Interpreter ${GGT_SCRIPT} + --spirv-core-grammar=${SPIRV_CORE_GRAMMAR_JSON_FILE} --extinst-debuginfo-grammar=${DEBUGINFO_GRAMMAR_JSON_FILE} --extinst-cldebuginfo100-grammar=${CLDEBUGINFO100_GRAMMAR_JSON_FILE} - --core-insts-output=${GRAMMAR_INSTS_INC_FILE} - --operand-kinds-output=${GRAMMAR_KINDS_INC_FILE} - DEPENDS ${GRAMMAR_PROCESSING_SCRIPT} - ${GRAMMAR_JSON_FILE} + --core-tables-output=${CORE_TABLES_INC_FILE} + DEPENDS ${GGT_SCRIPT} + ${SPIRV_CORE_GRAMMAR_JSON_FILE} ${DEBUGINFO_GRAMMAR_JSON_FILE} ${CLDEBUGINFO100_GRAMMAR_JSON_FILE} - COMMENT "Generate info tables for SPIR-V ${CONFIG_VERSION} core instructions and operands.") - list(APPEND OPCODE_CPP_DEPENDS ${GRAMMAR_INSTS_INC_FILE}) - list(APPEND OPERAND_CPP_DEPENDS ${GRAMMAR_KINDS_INC_FILE}) -endmacro(spvtools_core_tables) + COMMENT "Generate core tables") +add_custom_target(spirv-tools-tables DEPENDS ${CORE_TABLES_INC_FILE}) macro(spvtools_enum_string_mapping CONFIG_VERSION) set(GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/${CONFIG_VERSION}/spirv.core.grammar.json") @@ -115,7 +107,6 @@ macro(spvtools_extinst_lang_headers NAME GRAMMAR_FILE) list(APPEND EXTINST_CPP_DEPENDS spirv-tools-header-${NAME}) endmacro(spvtools_extinst_lang_headers) -spvtools_core_tables("unified1") spvtools_enum_string_mapping("unified1") spvtools_vendor_tables("glsl.std.450" "glsl" "") spvtools_vendor_tables("opencl.std.100" "opencl" "") @@ -153,7 +144,7 @@ list(APPEND OPCODE_CPP_DEPENDS ${GENERATOR_INC_FILE}) # We need to wrap the .inc files with a custom target to avoid problems when # multiple targets depend on the same custom command. add_custom_target(core_tables - DEPENDS ${OPCODE_CPP_DEPENDS} ${OPERAND_CPP_DEPENDS}) + DEPENDS ${OPCODE_CPP_DEPENDS} ${OPERAND_CPP_DEPENDS} ${CORE_TABLES_INC_FILE}) add_custom_target(enum_string_mapping DEPENDS ${EXTENSION_H_DEPENDS} ${ENUM_STRING_MAPPING_CPP_DEPENDS}) add_custom_target(extinst_tables @@ -235,6 +226,7 @@ set(SPIRV_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/spirv_target_env.h ${CMAKE_CURRENT_SOURCE_DIR}/spirv_validator_options.h ${CMAKE_CURRENT_SOURCE_DIR}/table.h + ${CMAKE_CURRENT_SOURCE_DIR}/table2.h ${CMAKE_CURRENT_SOURCE_DIR}/text.h ${CMAKE_CURRENT_SOURCE_DIR}/text_handler.h ${CMAKE_CURRENT_SOURCE_DIR}/to_string.h @@ -264,6 +256,7 @@ set(SPIRV_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/spirv_target_env.cpp ${CMAKE_CURRENT_SOURCE_DIR}/spirv_validator_options.cpp ${CMAKE_CURRENT_SOURCE_DIR}/table.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/table2.cpp ${CMAKE_CURRENT_SOURCE_DIR}/text.cpp ${CMAKE_CURRENT_SOURCE_DIR}/text_handler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/to_string.cpp diff --git a/source/assembly_grammar.cpp b/source/assembly_grammar.cpp index 0092d01a50..fefbaea35f 100644 --- a/source/assembly_grammar.cpp +++ b/source/assembly_grammar.cpp @@ -23,6 +23,7 @@ #include "source/operand.h" #include "source/spirv_target_env.h" #include "source/table.h" +#include "source/table2.h" namespace spvtools { namespace { @@ -35,15 +36,12 @@ namespace { /// /// On success, the value is written to pValue. /// -/// @param[in] operandTable operand lookup table /// @param[in] type of the operand /// @param[in] textValue word of text to be parsed /// @param[out] pValue where the resulting value is written /// /// @return result code -spv_result_t spvTextParseMaskOperand(spv_target_env env, - const spv_operand_table operandTable, - const spv_operand_type_t type, +spv_result_t spvTextParseMaskOperand(const spv_operand_type_t type, const char* textValue, uint32_t* pValue) { if (textValue == nullptr) return SPV_ERROR_INVALID_TEXT; size_t text_length = strlen(textValue); @@ -62,9 +60,9 @@ spv_result_t spvTextParseMaskOperand(spv_target_env env, do { end = std::find(begin, text_end, separator); - spv_operand_desc entry = nullptr; - if (auto error = spvOperandTableNameLookup(env, operandTable, type, begin, - end - begin, &entry)) { + spvtools::OperandDesc* entry = nullptr; + if (auto error = + spvtools::LookupOperand(type, begin, end - begin, &entry)) { return error; } value |= entry->value; @@ -170,23 +168,22 @@ const size_t kNumOpSpecConstantOpcodes = } // namespace -bool AssemblyGrammar::isValid() const { - return operandTable_ && opcodeTable_ && extInstTable_; -} +bool AssemblyGrammar::isValid() const { return extInstTable_; } CapabilitySet AssemblyGrammar::filterCapsAgainstTargetEnv( const spv::Capability* cap_array, uint32_t count) const { CapabilitySet cap_set; const auto version = spvVersionForTargetEnv(target_env_); for (uint32_t i = 0; i < count; ++i) { - spv_operand_desc entry = {}; - if (SPV_SUCCESS == lookupOperand(SPV_OPERAND_TYPE_CAPABILITY, - static_cast(cap_array[i]), - &entry)) { + spvtools::OperandDesc* entry = nullptr; + if (SPV_SUCCESS == + spvtools::LookupOperand(SPV_OPERAND_TYPE_CAPABILITY, + static_cast(cap_array[i]), &entry)) { // This token is visible in this environment if it's in an appropriate // core version, or it is enabled by a capability or an extension. if ((version >= entry->minVersion && version <= entry->lastVersion) || - entry->numExtensions > 0u || entry->numCapabilities > 0u) { + entry->extensions_range.count() > 0u || + entry->capabilities_range.count() > 0u) { cap_set.insert(cap_array[i]); } } @@ -194,28 +191,13 @@ CapabilitySet AssemblyGrammar::filterCapsAgainstTargetEnv( return cap_set; } -spv_result_t AssemblyGrammar::lookupOpcode(const char* name, - spv_opcode_desc* desc) const { - return spvOpcodeTableNameLookup(target_env_, opcodeTable_, name, desc); -} - -spv_result_t AssemblyGrammar::lookupOpcode(spv::Op opcode, - spv_opcode_desc* desc) const { - return spvOpcodeTableValueLookup(target_env_, opcodeTable_, opcode, desc); -} - -spv_result_t AssemblyGrammar::lookupOperand(spv_operand_type_t type, - const char* name, size_t name_len, - spv_operand_desc* desc) const { - return spvOperandTableNameLookup(target_env_, operandTable_, type, name, - name_len, desc); -} - -spv_result_t AssemblyGrammar::lookupOperand(spv_operand_type_t type, - uint32_t operand, - spv_operand_desc* desc) const { - return spvOperandTableValueLookup(target_env_, operandTable_, type, operand, - desc); +const char* AssemblyGrammar::lookupOperandName(spv_operand_type_t type, + uint32_t operand) const { + spvtools::OperandDesc* desc = nullptr; + if (spvtools::LookupOperand(type, operand, &desc) != SPV_SUCCESS || !desc) { + return "Unknown"; + } + return desc->name().data(); } spv_result_t AssemblyGrammar::lookupSpecConstantOpcode(const char* name, @@ -245,8 +227,7 @@ spv_result_t AssemblyGrammar::lookupSpecConstantOpcode(spv::Op opcode) const { spv_result_t AssemblyGrammar::parseMaskOperand(const spv_operand_type_t type, const char* textValue, uint32_t* pValue) const { - return spvTextParseMaskOperand(target_env_, operandTable_, type, textValue, - pValue); + return spvTextParseMaskOperand(type, textValue, pValue); } spv_result_t AssemblyGrammar::lookupExtInst(spv_ext_inst_type_t type, const char* textValue, @@ -263,7 +244,7 @@ spv_result_t AssemblyGrammar::lookupExtInst(spv_ext_inst_type_t type, void AssemblyGrammar::pushOperandTypesForMask( const spv_operand_type_t type, const uint32_t mask, spv_operand_pattern_t* pattern) const { - spvPushOperandTypesForMask(target_env_, operandTable_, type, mask, pattern); + spvPushOperandTypesForMask(type, mask, pattern); } } // namespace spvtools diff --git a/source/assembly_grammar.h b/source/assembly_grammar.h index 36fdd08a6d..cf875501bf 100644 --- a/source/assembly_grammar.h +++ b/source/assembly_grammar.h @@ -19,6 +19,7 @@ #include "source/latest_version_spirv_header.h" #include "source/operand.h" #include "source/table.h" +#include "source/util/span.h" #include "spirv-tools/libspirv.h" namespace spvtools { @@ -29,8 +30,6 @@ class AssemblyGrammar { public: explicit AssemblyGrammar(const spv_const_context context) : target_env_(context->target_env), - operandTable_(context->operand_table), - opcodeTable_(context->opcode_table), extInstTable_(context->ext_inst_table) {} // Returns true if the internal tables have been initialized with valid data. @@ -41,41 +40,21 @@ class AssemblyGrammar { // Removes capabilities not available in the current target environment and // returns the rest. + // TODO(crbug.com/266223071) Remove this. CapabilitySet filterCapsAgainstTargetEnv(const spv::Capability* cap_array, uint32_t count) const; - - // Fills in the desc parameter with the information about the opcode - // of the given name. Returns SPV_SUCCESS if the opcode was found, and - // SPV_ERROR_INVALID_LOOKUP if the opcode does not exist. - spv_result_t lookupOpcode(const char* name, spv_opcode_desc* desc) const; - - // Fills in the desc parameter with the information about the opcode - // of the valid. Returns SPV_SUCCESS if the opcode was found, and - // SPV_ERROR_INVALID_LOOKUP if the opcode does not exist. - spv_result_t lookupOpcode(spv::Op opcode, spv_opcode_desc* desc) const; - - // Fills in the desc parameter with the information about the given - // operand. Returns SPV_SUCCESS if the operand was found, and - // SPV_ERROR_INVALID_LOOKUP otherwise. - spv_result_t lookupOperand(spv_operand_type_t type, const char* name, - size_t name_len, spv_operand_desc* desc) const; - - // Fills in the desc parameter with the information about the given - // operand. Returns SPV_SUCCESS if the operand was found, and - // SPV_ERROR_INVALID_LOOKUP otherwise. - spv_result_t lookupOperand(spv_operand_type_t type, uint32_t operand, - spv_operand_desc* desc) const; + // Removes capabilities not available in the current target environment and + // returns the rest. + CapabilitySet filterCapsAgainstTargetEnv( + const spvtools::utils::Span& caps) const { + return filterCapsAgainstTargetEnv(caps.begin(), + static_cast(caps.size())); + } // Finds operand entry in the grammar table and returns its name. // Returns "Unknown" if not found. const char* lookupOperandName(spv_operand_type_t type, - uint32_t operand) const { - spv_operand_desc desc = nullptr; - if (lookupOperand(type, operand, &desc) != SPV_SUCCESS || !desc) { - return "Unknown"; - } - return desc->name; - } + uint32_t operand) const; // Finds the opcode for the given OpSpecConstantOp opcode name. The name // should not have the "Op" prefix. For example, "IAdd" corresponds to @@ -129,8 +108,6 @@ class AssemblyGrammar { private: const spv_target_env target_env_; - const spv_operand_table operandTable_; - const spv_opcode_table opcodeTable_; const spv_ext_inst_table extInstTable_; }; diff --git a/source/binary.cpp b/source/binary.cpp index 3807441dfe..0a9997f815 100644 --- a/source/binary.cpp +++ b/source/binary.cpp @@ -33,6 +33,7 @@ #include "source/operand.h" #include "source/spirv_constant.h" #include "source/spirv_endian.h" +#include "source/table2.h" #include "source/util/string_utils.h" spv_result_t spvBinaryHeaderGet(const spv_const_binary binary, @@ -317,8 +318,8 @@ spv_result_t Parser::parseInstruction() { return diagnostic() << "Invalid instruction word count: " << inst_word_count; } - spv_opcode_desc opcode_desc; - if (grammar_.lookupOpcode(static_cast(inst.opcode), &opcode_desc)) + spvtools::InstructionDesc* opcode_desc = nullptr; + if (spvtools::LookupOpcode(static_cast(inst.opcode), &opcode_desc)) return diagnostic() << "Invalid opcode: " << inst.opcode; // Advance past the opcode word. But remember the of the start @@ -334,16 +335,15 @@ spv_result_t Parser::parseInstruction() { // ExecutionMode), or for extended instructions that may have their // own operands depending on the selected extended instruction. _.expected_operands.clear(); - for (auto i = 0; i < opcode_desc->numTypes; i++) - _.expected_operands.push_back( - opcode_desc->operandTypes[opcode_desc->numTypes - i - 1]); + + spvPushOperandTypes(opcode_desc->operands(), &_.expected_operands); while (_.word_index < inst_offset + inst_word_count) { const uint16_t inst_word_index = uint16_t(_.word_index - inst_offset); if (_.expected_operands.empty()) { - return diagnostic() << "Invalid instruction Op" << opcode_desc->name - << " starting at word " << inst_offset - << ": expected no more operands after " + return diagnostic() << "Invalid instruction Op" + << opcode_desc->name().data() << " starting at word " + << inst_offset << ": expected no more operands after " << inst_word_index << " words, but stated word count is " << inst_word_count << "."; @@ -362,15 +362,15 @@ spv_result_t Parser::parseInstruction() { if (!_.expected_operands.empty() && !spvOperandIsOptional(_.expected_operands.back())) { return diagnostic() << "End of input reached while decoding Op" - << opcode_desc->name << " starting at word " + << opcode_desc->name().data() << " starting at word " << inst_offset << ": expected more operands after " << inst_word_count << " words."; } if ((inst_offset + inst_word_count) != _.word_index) { - return diagnostic() << "Invalid word count: Op" << opcode_desc->name - << " starting at word " << inst_offset - << " says it has " << inst_word_count + return diagnostic() << "Invalid word count: Op" + << opcode_desc->name().data() << " starting at word " + << inst_offset << " says it has " << inst_word_count << " words, but found " << _.word_index - inst_offset << " words instead."; } @@ -522,8 +522,8 @@ spv_result_t Parser::parseOperand(size_t inst_offset, return diagnostic() << "Invalid " << spvOperandTypeStr(type) << ": " << word; } - spv_opcode_desc opcode_entry = nullptr; - if (grammar_.lookupOpcode(spv::Op(word), &opcode_entry)) { + spvtools::InstructionDesc* opcode_entry = nullptr; + if (spvtools::LookupOpcode(spv::Op(word), &opcode_entry)) { return diagnostic(SPV_ERROR_INTERNAL) << "OpSpecConstant opcode table out of sync"; } @@ -532,8 +532,9 @@ spv_result_t Parser::parseOperand(size_t inst_offset, // operants for the opcode. assert(opcode_entry->hasType); assert(opcode_entry->hasResult); - assert(opcode_entry->numTypes >= 2); - spvPushOperandTypes(opcode_entry->operandTypes + 2, expected_operands); + assert(opcode_entry->operands().size() >= 2); + spvPushOperandTypes(opcode_entry->operands().subspan(2), + expected_operands); } break; case SPV_OPERAND_TYPE_LITERAL_INTEGER: @@ -687,19 +688,19 @@ spv_result_t Parser::parseOperand(size_t inst_offset, if (type == SPV_OPERAND_TYPE_OPTIONAL_FPENCODING) parsed_operand.type = SPV_OPERAND_TYPE_FPENCODING; - spv_operand_desc entry; - if (grammar_.lookupOperand(type, word, &entry)) { + spvtools::OperandDesc* entry = nullptr; + if (spvtools::LookupOperand(type, word, &entry)) { return diagnostic() << "Invalid " << spvOperandTypeStr(parsed_operand.type) << " operand: " << word; } // Prepare to accept operands to this operand, if needed. - spvPushOperandTypes(entry->operandTypes, expected_operands); + spvPushOperandTypes(entry->operands(), expected_operands); } break; case SPV_OPERAND_TYPE_SOURCE_LANGUAGE: { - spv_operand_desc entry; - if (grammar_.lookupOperand(type, word, &entry)) { + spvtools::OperandDesc* entry = nullptr; + if (spvtools::LookupOperand(type, word, &entry)) { return diagnostic() << "Invalid " << spvOperandTypeStr(parsed_operand.type) << " operand: " << word @@ -709,7 +710,7 @@ spv_result_t Parser::parseOperand(size_t inst_offset, "SPIRV-Headers"; } // Prepare to accept operands to this operand, if needed. - spvPushOperandTypes(entry->operandTypes, expected_operands); + spvPushOperandTypes(entry->operands(), expected_operands); } break; case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE: @@ -753,23 +754,23 @@ spv_result_t Parser::parseOperand(size_t inst_offset, uint32_t remaining_word = word; for (uint32_t mask = (1u << 31); remaining_word; mask >>= 1) { if (remaining_word & mask) { - spv_operand_desc entry; - if (grammar_.lookupOperand(type, mask, &entry)) { + spvtools::OperandDesc* entry = nullptr; + if (spvtools::LookupOperand(type, mask, &entry)) { return diagnostic() << "Invalid " << spvOperandTypeStr(parsed_operand.type) << " operand: " << word << " has invalid mask component " << mask; } remaining_word ^= mask; - spvPushOperandTypes(entry->operandTypes, expected_operands); + spvPushOperandTypes(entry->operands(), expected_operands); } } if (word == 0) { // An all-zeroes mask *might* also be valid. - spv_operand_desc entry; - if (SPV_SUCCESS == grammar_.lookupOperand(type, 0, &entry)) { + spvtools::OperandDesc* entry = nullptr; + if (SPV_SUCCESS == spvtools::LookupOperand(type, 0, &entry)) { // Prepare for its operands, if any. - spvPushOperandTypes(entry->operandTypes, expected_operands); + spvPushOperandTypes(entry->operands(), expected_operands); } } } break; diff --git a/source/diff/diff.cpp b/source/diff/diff.cpp index 2e455e5a7e..4217b5f771 100644 --- a/source/diff/diff.cpp +++ b/source/diff/diff.cpp @@ -2799,24 +2799,14 @@ spv_result_t Differ::Output() { dst_id_to_.inst_map_.resize(id_map_.DstToSrcMap().IdBound(), nullptr); const spv_target_env target_env = SPV_ENV_UNIVERSAL_1_6; - spv_opcode_table opcode_table; - spv_operand_table operand_table; spv_ext_inst_table ext_inst_table; spv_result_t result; - result = spvOpcodeTableGet(&opcode_table, target_env); - if (result != SPV_SUCCESS) return result; - - result = spvOperandTableGet(&operand_table, target_env); - if (result != SPV_SUCCESS) return result; - result = spvExtInstTableGet(&ext_inst_table, target_env); if (result != SPV_SUCCESS) return result; spv_context_t context{ target_env, - opcode_table, - operand_table, ext_inst_table, }; diff --git a/source/disassemble.cpp b/source/disassemble.cpp index 93791a0ca7..5ba707d027 100644 --- a/source/disassemble.cpp +++ b/source/disassemble.cpp @@ -40,6 +40,7 @@ #include "source/print.h" #include "source/spirv_constant.h" #include "source/spirv_endian.h" +#include "source/table2.h" #include "source/util/hex_float.h" #include "source/util/make_unique.h" #include "spirv-tools/libspirv.h" @@ -885,11 +886,11 @@ void InstructionDisassembler::EmitOperand(std::ostream& stream, } } break; case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: { - spv_opcode_desc opcode_desc; - if (grammar_.lookupOpcode(spv::Op(word), &opcode_desc)) + spvtools::InstructionDesc* opcodeEntry = nullptr; + if (LookupOpcode(spv::Op(word), &opcodeEntry)) assert(false && "should have caught this earlier"); SetRed(stream); - stream << opcode_desc->name; + stream << opcodeEntry->name().data(); } break; case SPV_OPERAND_TYPE_LITERAL_INTEGER: case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: @@ -948,10 +949,10 @@ void InstructionDisassembler::EmitOperand(std::ostream& stream, case SPV_OPERAND_TYPE_QUANTIZATION_MODES: case SPV_OPERAND_TYPE_FPENCODING: case SPV_OPERAND_TYPE_OVERFLOW_MODES: { - spv_operand_desc entry; - if (grammar_.lookupOperand(operand.type, word, &entry)) + spvtools::OperandDesc* entry = nullptr; + if (spvtools::LookupOperand(operand.type, word, &entry)) assert(false && "should have caught this earlier"); - stream << entry->name; + stream << entry->name().data(); } break; case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE: case SPV_OPERAND_TYPE_FUNCTION_CONTROL: @@ -968,10 +969,10 @@ void InstructionDisassembler::EmitOperand(std::ostream& stream, if (spvOperandIsConcreteMask(operand.type)) { EmitMaskOperand(stream, operand.type, word); } else if (spvOperandIsConcrete(operand.type)) { - spv_operand_desc entry; - if (grammar_.lookupOperand(operand.type, word, &entry)) + spvtools::OperandDesc* entry = nullptr; + if (spvtools::LookupOperand(operand.type, word, &entry)) assert(false && "should have caught this earlier"); - stream << entry->name; + stream << entry->name().data(); } else { assert(false && "unhandled or invalid case"); } @@ -991,20 +992,20 @@ void InstructionDisassembler::EmitMaskOperand(std::ostream& stream, for (mask = 1; remaining_word; mask <<= 1) { if (remaining_word & mask) { remaining_word ^= mask; - spv_operand_desc entry; - if (grammar_.lookupOperand(type, mask, &entry)) + spvtools::OperandDesc* entry = nullptr; + if (spvtools::LookupOperand(type, mask, &entry)) assert(false && "should have caught this earlier"); if (num_emitted) stream << "|"; - stream << entry->name; + stream << entry->name().data(); num_emitted++; } } if (!num_emitted) { // An operand value of 0 was provided, so represent it by the name // of the 0 value. In many cases, that's "None". - spv_operand_desc entry; - if (SPV_SUCCESS == grammar_.lookupOperand(type, 0, &entry)) - stream << entry->name; + spvtools::OperandDesc* entry = nullptr; + if (SPV_SUCCESS == spvtools::LookupOperand(type, 0, &entry)) + stream << entry->name().data(); } } diff --git a/source/link/linker.cpp b/source/link/linker.cpp index de305255d5..d15f9124b8 100644 --- a/source/link/linker.cpp +++ b/source/link/linker.cpp @@ -26,7 +26,6 @@ #include #include -#include "source/assembly_grammar.h" #include "source/diagnostic.h" #include "source/opt/build_module.h" #include "source/opt/compact_ids_pass.h" @@ -39,6 +38,7 @@ #include "source/opt/type_manager.h" #include "source/spirv_constant.h" #include "source/spirv_target_env.h" +#include "source/table2.h" #include "source/util/make_unique.h" #include "source/util/string_utils.h" #include "spirv-tools/libspirv.hpp" @@ -103,7 +103,6 @@ spv_result_t GenerateHeader(const MessageConsumer& consumer, // |linked_context| should not be null. spv_result_t MergeModules(const MessageConsumer& consumer, const std::vector& in_modules, - const AssemblyGrammar& grammar, IRContext* linked_context); // Compute all pairs of import and export and return it in |linkings_to_do|. @@ -246,7 +245,6 @@ spv_result_t GenerateHeader(const MessageConsumer& consumer, spv_result_t MergeModules(const MessageConsumer& consumer, const std::vector& input_modules, - const AssemblyGrammar& grammar, IRContext* linked_context) { spv_position_t position = {}; @@ -294,29 +292,33 @@ spv_result_t MergeModules(const MessageConsumer& consumer, const uint32_t module_addressing_model = memory_model_inst->GetSingleWordOperand(0u); if (module_addressing_model != linked_addressing_model) { - spv_operand_desc linked_desc = nullptr, module_desc = nullptr; - grammar.lookupOperand(SPV_OPERAND_TYPE_ADDRESSING_MODEL, - linked_addressing_model, &linked_desc); - grammar.lookupOperand(SPV_OPERAND_TYPE_ADDRESSING_MODEL, - module_addressing_model, &module_desc); + spvtools::OperandDesc* linked_desc = nullptr; + spvtools::OperandDesc* module_desc = nullptr; + spvtools::LookupOperand(SPV_OPERAND_TYPE_ADDRESSING_MODEL, + linked_addressing_model, &linked_desc); + spvtools::LookupOperand(SPV_OPERAND_TYPE_ADDRESSING_MODEL, + module_addressing_model, &module_desc); return DiagnosticStream(position, consumer, "", SPV_ERROR_INTERNAL) - << "Conflicting addressing models: " << linked_desc->name + << "Conflicting addressing models: " << linked_desc->name().data() << " (input modules 1 through " << i << ") vs " - << module_desc->name << " (input module " << (i + 1) << ")."; + << module_desc->name().data() << " (input module " << (i + 1) + << ")."; } const uint32_t module_memory_model = memory_model_inst->GetSingleWordOperand(1u); if (module_memory_model != linked_memory_model) { - spv_operand_desc linked_desc = nullptr, module_desc = nullptr; - grammar.lookupOperand(SPV_OPERAND_TYPE_MEMORY_MODEL, linked_memory_model, - &linked_desc); - grammar.lookupOperand(SPV_OPERAND_TYPE_MEMORY_MODEL, module_memory_model, - &module_desc); + spvtools::OperandDesc* linked_desc = nullptr; + spvtools::OperandDesc* module_desc = nullptr; + spvtools::LookupOperand(SPV_OPERAND_TYPE_MEMORY_MODEL, + linked_memory_model, &linked_desc); + spvtools::LookupOperand(SPV_OPERAND_TYPE_MEMORY_MODEL, + module_memory_model, &module_desc); return DiagnosticStream(position, consumer, "", SPV_ERROR_INTERNAL) - << "Conflicting memory models: " << linked_desc->name + << "Conflicting memory models: " << linked_desc->name().data() << " (input modules 1 through " << i << ") vs " - << module_desc->name << " (input module " << (i + 1) << ")."; + << module_desc->name().data() << " (input module " << (i + 1) + << ")."; } } linked_module->SetMemoryModel(std::unique_ptr( @@ -333,11 +335,11 @@ spv_result_t MergeModules(const MessageConsumer& consumer, return v.first == model && v.second == name; }); if (i != entry_points.end()) { - spv_operand_desc desc = nullptr; - grammar.lookupOperand(SPV_OPERAND_TYPE_EXECUTION_MODEL, model, &desc); + spvtools::OperandDesc* desc = nullptr; + spvtools::LookupOperand(SPV_OPERAND_TYPE_EXECUTION_MODEL, model, &desc); return DiagnosticStream(position, consumer, "", SPV_ERROR_INTERNAL) << "The entry point \"" << name << "\", with execution model " - << desc->name << ", was already defined."; + << desc->name().data() << ", was already defined."; } linked_module->AddEntryPoint( std::unique_ptr(inst.Clone(linked_context))); @@ -865,8 +867,7 @@ spv_result_t Link(const Context& context, const uint32_t* const* binaries, linked_context.module()->SetHeader(header); // Phase 3: Merge all the binaries into a single one. - AssemblyGrammar grammar(c_context); - res = MergeModules(consumer, modules, grammar, &linked_context); + res = MergeModules(consumer, modules, &linked_context); if (res != SPV_SUCCESS) return res; if (options.GetVerifyIds()) { diff --git a/source/name_mapper.cpp b/source/name_mapper.cpp index ae3ef49a1f..11d33bdd72 100644 --- a/source/name_mapper.cpp +++ b/source/name_mapper.cpp @@ -27,6 +27,7 @@ #include "source/binary.h" #include "source/latest_version_spirv_header.h" #include "source/parsed_operand.h" +#include "source/table2.h" #include "source/to_string.h" #include "spirv-tools/libspirv.h" @@ -327,9 +328,9 @@ spv_result_t FriendlyNameMapper::ParseInstruction( std::string FriendlyNameMapper::NameForEnumOperand(spv_operand_type_t type, uint32_t word) { - spv_operand_desc desc = nullptr; - if (SPV_SUCCESS == grammar_.lookupOperand(type, word, &desc)) { - return desc->name; + spvtools::OperandDesc* desc = nullptr; + if (SPV_SUCCESS == spvtools::LookupOperand(type, word, &desc)) { + return desc->name().data(); } else { // Invalid input. Just give something. return std::string("StorageClass") + to_string(word); diff --git a/source/opcode.cpp b/source/opcode.cpp index 985c91c365..6b5062ce48 100644 --- a/source/opcode.cpp +++ b/source/opcode.cpp @@ -27,18 +27,10 @@ #include "source/spirv_constant.h" #include "source/spirv_endian.h" #include "source/spirv_target_env.h" +#include "source/table2.h" #include "spirv-tools/libspirv.h" namespace { -struct OpcodeDescPtrLen { - const spv_opcode_desc_t* ptr; - uint32_t len; -}; - -#include "core.insts-unified1.inc" - -static const spv_opcode_table_t kOpcodeTable = {ARRAY_SIZE(kOpcodeTableEntries), - kOpcodeTableEntries}; // Represents a vendor tool entry in the SPIR-V XML Registry. struct VendorTool { @@ -78,115 +70,6 @@ void spvOpcodeSplit(const uint32_t word, uint16_t* pWordCount, } } -spv_result_t spvOpcodeTableGet(spv_opcode_table* pInstTable, spv_target_env) { - if (!pInstTable) return SPV_ERROR_INVALID_POINTER; - - // Descriptions of each opcode. Each entry describes the format of the - // instruction that follows a particular opcode. - - *pInstTable = &kOpcodeTable; - return SPV_SUCCESS; -} - -spv_result_t spvOpcodeTableNameLookup(spv_target_env env, - const spv_opcode_table table, - const char* name, - spv_opcode_desc* pEntry) { - if (!name || !pEntry) return SPV_ERROR_INVALID_POINTER; - if (!table) return SPV_ERROR_INVALID_TABLE; - - // TODO: This lookup of the Opcode table is suboptimal! Binary sort would be - // preferable but the table requires sorting on the Opcode name, but it's - // static const initialized and matches the order of the spec. - const size_t nameLength = strlen(name); - const auto version = spvVersionForTargetEnv(env); - for (uint64_t opcodeIndex = 0; opcodeIndex < table->count; ++opcodeIndex) { - const spv_opcode_desc_t& entry = table->entries[opcodeIndex]; - // We consider the current opcode as available as long as - // 1. The target environment satisfies the minimal requirement of the - // opcode; or - // 2. There is at least one extension enabling this opcode. - // - // Note that the second rule assumes the extension enabling this instruction - // is indeed requested in the SPIR-V code; checking that should be - // validator's work. - if ((version >= entry.minVersion && version <= entry.lastVersion) || - entry.numExtensions > 0u || entry.numCapabilities > 0u) { - // Exact match case. - if (nameLength == strlen(entry.name) && - !strncmp(name, entry.name, nameLength)) { - *pEntry = &entry; - return SPV_SUCCESS; - } - // Lack of binary search really hurts here. There isn't an easy filter to - // apply before checking aliases since we need to handle promotion from - // vendor to KHR/EXT and KHR/EXT to core. It would require a sure-fire way - // of dropping suffices. Fortunately, most lookup are based on token - // value. - // - // If this was a binary search we could iterate between the lower and - // upper bounds. - if (entry.numAliases > 0) { - for (uint32_t aliasIndex = 0; aliasIndex < entry.numAliases; - aliasIndex++) { - // Skip Op prefix. Should this be encoded in the table instead? - const auto alias = entry.aliases[aliasIndex] + 2; - const size_t aliasLength = strlen(alias); - if (nameLength == aliasLength && !strncmp(name, alias, nameLength)) { - *pEntry = &entry; - return SPV_SUCCESS; - } - } - } - } - } - - return SPV_ERROR_INVALID_LOOKUP; -} - -spv_result_t spvOpcodeTableValueLookup(spv_target_env env, - const spv_opcode_table table, - const spv::Op opcode, - spv_opcode_desc* pEntry) { - if (!table) return SPV_ERROR_INVALID_TABLE; - if (!pEntry) return SPV_ERROR_INVALID_POINTER; - - const auto beg = table->entries; - const auto end = table->entries + table->count; - - spv_opcode_desc_t needle = {"", opcode, 0, nullptr, 0, {}, 0, - {}, false, false, 0, nullptr, ~0u, ~0u}; - - auto comp = [](const spv_opcode_desc_t& lhs, const spv_opcode_desc_t& rhs) { - return lhs.opcode < rhs.opcode; - }; - - // We need to loop here because there can exist multiple symbols for the same - // opcode value, and they can be introduced in different target environments, - // which means they can have different minimal version requirements. - // Assumes the underlying table is already sorted ascendingly according to - // opcode value. - const auto version = spvVersionForTargetEnv(env); - for (auto it = std::lower_bound(beg, end, needle, comp); - it != end && it->opcode == opcode; ++it) { - // We considers the current opcode as available as long as - // 1. The target environment satisfies the minimal requirement of the - // opcode; or - // 2. There is at least one extension enabling this opcode. - // - // Note that the second rule assumes the extension enabling this instruction - // is indeed requested in the SPIR-V code; checking that should be - // validator's work. - if ((version >= it->minVersion && version <= it->lastVersion) || - it->numExtensions > 0u || it->numCapabilities > 0u) { - *pEntry = it; - return SPV_SUCCESS; - } - } - - return SPV_ERROR_INVALID_LOOKUP; -} - void spvInstructionCopy(const uint32_t* words, const spv::Op opcode, const uint16_t wordCount, const spv_endianness_t endian, spv_instruction_t* pInst) { @@ -205,25 +88,13 @@ void spvInstructionCopy(const uint32_t* words, const spv::Op opcode, } const char* spvOpcodeString(const uint32_t opcode) { - const auto beg = kOpcodeTableEntries; - const auto end = kOpcodeTableEntries + ARRAY_SIZE(kOpcodeTableEntries); - spv_opcode_desc_t needle = {"", static_cast(opcode), - 0, nullptr, - 0, {}, - 0, {}, - false, false, - 0, nullptr, - ~0u, ~0u}; - auto comp = [](const spv_opcode_desc_t& lhs, const spv_opcode_desc_t& rhs) { - return lhs.opcode < rhs.opcode; - }; - auto it = std::lower_bound(beg, end, needle, comp); - if (it != end && it->opcode == spv::Op(opcode)) { - return it->name; + spvtools::InstructionDesc* desc = nullptr; + if (SPV_SUCCESS != + spvtools::LookupOpcode(static_cast(opcode), &desc)) { + assert(0 && "Unreachable!"); + return "unknown"; } - - assert(0 && "Unreachable!"); - return "unknown"; + return desc->name().data(); } const char* spvOpcodeString(const spv::Op opcode) { diff --git a/source/opcode.h b/source/opcode.h index 08fc56d8a0..a314ff65c7 100644 --- a/source/opcode.h +++ b/source/opcode.h @@ -35,19 +35,6 @@ uint32_t spvOpcodeMake(uint16_t word_count, spv::Op opcode); void spvOpcodeSplit(const uint32_t word, uint16_t* word_count, uint16_t* opcode); -// Finds the named opcode in the given opcode table. On success, returns -// SPV_SUCCESS and writes a handle of the table entry into *entry. -spv_result_t spvOpcodeTableNameLookup(spv_target_env, - const spv_opcode_table table, - const char* name, spv_opcode_desc* entry); - -// Finds the opcode by enumerant in the given opcode table. On success, returns -// SPV_SUCCESS and writes a handle of the table entry into *entry. -spv_result_t spvOpcodeTableValueLookup(spv_target_env, - const spv_opcode_table table, - const spv::Op opcode, - spv_opcode_desc* entry); - // Copies an instruction's word and fixes the endianness to host native. The // source instruction's stream/opcode/endianness is in the words/opcode/endian // parameter. The word_count parameter specifies the number of words to copy. diff --git a/source/operand.cpp b/source/operand.cpp index 869a7ca137..fa060a9745 100644 --- a/source/operand.cpp +++ b/source/operand.cpp @@ -26,110 +26,9 @@ #include "source/macro.h" #include "source/opcode.h" #include "source/spirv_constant.h" - -// For now, assume unified1 contains up to SPIR-V 1.3 and no later -// SPIR-V version. -// TODO(dneto): Make one set of tables, but with version tags on a -// per-item basis. https://github.com/KhronosGroup/SPIRV-Tools/issues/1195 - -#include "operand.kinds-unified1.inc" +#include "source/table2.h" #include "spirv-tools/libspirv.h" -static const spv_operand_table_t kOperandTable = { - ARRAY_SIZE(pygen_variable_OperandInfoTable), - pygen_variable_OperandInfoTable}; - -spv_result_t spvOperandTableGet(spv_operand_table* pOperandTable, - spv_target_env) { - if (!pOperandTable) return SPV_ERROR_INVALID_POINTER; - - *pOperandTable = &kOperandTable; - return SPV_SUCCESS; -} - -spv_result_t spvOperandTableNameLookup(spv_target_env, - const spv_operand_table table, - const spv_operand_type_t type, - const char* name, - const size_t nameLength, - spv_operand_desc* pEntry) { - if (!table) return SPV_ERROR_INVALID_TABLE; - if (!name || !pEntry) return SPV_ERROR_INVALID_POINTER; - - for (uint64_t typeIndex = 0; typeIndex < table->count; ++typeIndex) { - const auto& group = table->types[typeIndex]; - if (type != group.type) continue; - for (uint64_t index = 0; index < group.count; ++index) { - const auto& entry = group.entries[index]; - // We consider the current operand as available as long as - // it is in the grammar. It might not be *valid* to use, - // but that should be checked by the validator, not by parsing. - // - // Exact match case - if (nameLength == strlen(entry.name) && - !strncmp(entry.name, name, nameLength)) { - *pEntry = &entry; - return SPV_SUCCESS; - } - - // Check the aliases. Ideally we would have a version of the table sorted - // by name and then we could iterate between the lower and upper bounds to - // restrict the amount comparisons. Fortunately, name-based lookups are - // mostly restricted to the assembler. - if (entry.numAliases > 0) { - for (uint32_t aliasIndex = 0; aliasIndex < entry.numAliases; - aliasIndex++) { - const auto alias = entry.aliases[aliasIndex]; - const size_t aliasLength = strlen(alias); - if (nameLength == aliasLength && !strncmp(name, alias, nameLength)) { - *pEntry = &entry; - return SPV_SUCCESS; - } - } - } - } - } - - return SPV_ERROR_INVALID_LOOKUP; -} - -spv_result_t spvOperandTableValueLookup(spv_target_env, - const spv_operand_table table, - const spv_operand_type_t type, - const uint32_t value, - spv_operand_desc* pEntry) { - if (!table) return SPV_ERROR_INVALID_TABLE; - if (!pEntry) return SPV_ERROR_INVALID_POINTER; - - spv_operand_desc_t needle = {"", value, 0, nullptr, 0, nullptr, - 0, nullptr, {}, ~0u, ~0u}; - - auto comp = [](const spv_operand_desc_t& lhs, const spv_operand_desc_t& rhs) { - return lhs.value < rhs.value; - }; - - for (uint64_t typeIndex = 0; typeIndex < table->count; ++typeIndex) { - const auto& group = table->types[typeIndex]; - if (type != group.type) continue; - - const auto beg = group.entries; - const auto end = group.entries + group.count; - - // Assumes the underlying table is already sorted ascendingly according to - // opcode value. - auto it = std::lower_bound(beg, end, needle, comp); - if (it != end && it->value == value) { - // The current operand is considered available as long as - // it is in the grammar. It might not be *valid* to use, - // but that should be checked by the validator, not by parsing. - *pEntry = it; - return SPV_SUCCESS; - } - } - - return SPV_ERROR_INVALID_LOOKUP; -} - const char* spvOperandTypeStr(spv_operand_type_t type) { switch (type) { case SPV_OPERAND_TYPE_ID: @@ -315,6 +214,7 @@ const char* spvOperandTypeStr(spv_operand_type_t type) { void spvPushOperandTypes(const spv_operand_type_t* types, spv_operand_pattern_t* pattern) { + // Push them on in backward order. const spv_operand_type_t* endTypes; for (endTypes = types; *endTypes != SPV_OPERAND_TYPE_NONE; ++endTypes) { } @@ -324,9 +224,22 @@ void spvPushOperandTypes(const spv_operand_type_t* types, } } -void spvPushOperandTypesForMask(spv_target_env env, - const spv_operand_table operandTable, - const spv_operand_type_t type, +void spvPushOperandTypes( + const spvtools::utils::Span& types, + spv_operand_pattern_t* pattern) { + // Push them on in backward order. + auto n = types.size(); + for (auto i = 0u; i < n; i++) { + auto type = types[n - 1 - i]; + // Check against the NONE type, in case the tables have them. + // This might be cleaned up. + if (type != SPV_OPERAND_TYPE_NONE) { + pattern->push_back(type); + } + } +} + +void spvPushOperandTypesForMask(const spv_operand_type_t type, const uint32_t mask, spv_operand_pattern_t* pattern) { // Scan from highest bits to lowest bits because we will append in LIFO @@ -334,10 +247,9 @@ void spvPushOperandTypesForMask(spv_target_env env, for (uint32_t candidate_bit = (1u << 31u); candidate_bit; candidate_bit >>= 1) { if (candidate_bit & mask) { - spv_operand_desc entry = nullptr; - if (SPV_SUCCESS == spvOperandTableValueLookup(env, operandTable, type, - candidate_bit, &entry)) { - spvPushOperandTypes(entry->operandTypes, pattern); + spvtools::OperandDesc* entry = nullptr; + if (SPV_SUCCESS == spvtools::LookupOperand(type, candidate_bit, &entry)) { + spvPushOperandTypes(entry->operands(), pattern); } } } diff --git a/source/operand.h b/source/operand.h index 3d42a0594d..54a6e6775d 100644 --- a/source/operand.h +++ b/source/operand.h @@ -19,6 +19,7 @@ #include #include "source/table.h" +#include "source/util/span.h" #include "spirv-tools/libspirv.h" // A sequence of operand types. @@ -35,25 +36,6 @@ // performance. using spv_operand_pattern_t = std::vector; -// Finds the named operand in the table. The type parameter specifies the -// operand's group. A handle of the operand table entry for this operand will -// be written into *entry. -spv_result_t spvOperandTableNameLookup(spv_target_env, - const spv_operand_table table, - const spv_operand_type_t type, - const char* name, - const size_t name_length, - spv_operand_desc* entry); - -// Finds the operand with value in the table. The type parameter specifies the -// operand's group. A handle of the operand table entry for this operand will -// be written into *entry. -spv_result_t spvOperandTableValueLookup(spv_target_env, - const spv_operand_table table, - const spv_operand_type_t type, - const uint32_t value, - spv_operand_desc* entry); - // Gets the name string of the non-variable operand type. const char* spvOperandTypeStr(spv_operand_type_t type); @@ -73,6 +55,12 @@ bool spvOperandIsVariable(spv_operand_type_t type); void spvPushOperandTypes(const spv_operand_type_t* types, spv_operand_pattern_t* pattern); +// Append a list of operand types to the end of the pattern vector. +// The types parameter specifies the source span of types. +void spvPushOperandTypes( + const spvtools::utils::Span& types, + spv_operand_pattern_t* pattern); + // Appends the operands expected after the given typed mask onto the // end of the given pattern. // @@ -81,9 +69,7 @@ void spvPushOperandTypes(const spv_operand_type_t* types, // appear after operands for a more significant bit. // // If a set bit is unknown, then we assume it has no operands. -void spvPushOperandTypesForMask(spv_target_env, - const spv_operand_table operand_table, - const spv_operand_type_t mask_type, +void spvPushOperandTypesForMask(const spv_operand_type_t mask_type, const uint32_t mask, spv_operand_pattern_t* pattern); diff --git a/source/opt/feature_manager.cpp b/source/opt/feature_manager.cpp index 51883706aa..25371ade2a 100644 --- a/source/opt/feature_manager.cpp +++ b/source/opt/feature_manager.cpp @@ -17,6 +17,7 @@ #include #include "source/enum_string_mapping.h" +#include "source/table2.h" namespace spvtools { namespace opt { @@ -54,11 +55,12 @@ void FeatureManager::AddCapability(spv::Capability cap) { capabilities_.insert(cap); - spv_operand_desc desc = {}; - if (SPV_SUCCESS == grammar_.lookupOperand(SPV_OPERAND_TYPE_CAPABILITY, - uint32_t(cap), &desc)) { + spvtools::OperandDesc* desc = nullptr; + if (SPV_SUCCESS == spvtools::LookupOperand(SPV_OPERAND_TYPE_CAPABILITY, + uint32_t(cap), &desc)) { for (auto capability : - CapabilitySet(desc->numCapabilities, desc->capabilities)) { + CapabilitySet(static_cast(desc->capabilities().size()), + desc->capabilities().data())) { AddCapability(capability); } } diff --git a/source/opt/replace_invalid_opc.cpp b/source/opt/replace_invalid_opc.cpp index 1b97c0e84f..844b9b936d 100644 --- a/source/opt/replace_invalid_opc.cpp +++ b/source/opt/replace_invalid_opc.cpp @@ -17,6 +17,8 @@ #include #include +#include "source/table2.h" + namespace spvtools { namespace opt { @@ -207,10 +209,10 @@ uint32_t ReplaceInvalidOpcodePass::GetSpecialConstant(uint32_t type_id) { } std::string ReplaceInvalidOpcodePass::BuildWarningMessage(spv::Op opcode) { - spv_opcode_desc opcode_info; - context()->grammar().lookupOpcode(opcode, &opcode_info); + spvtools::InstructionDesc* opcode_desc = nullptr; + spvtools::LookupOpcode(opcode, &opcode_desc); std::string message = "Removing "; - message += opcode_info->name; + message += opcode_desc->name().data(); message += " instruction because of incompatible execution model."; return message; } diff --git a/source/opt/trim_capabilities_pass.cpp b/source/opt/trim_capabilities_pass.cpp index ca5c625702..2354ea9b6d 100644 --- a/source/opt/trim_capabilities_pass.cpp +++ b/source/opt/trim_capabilities_pass.cpp @@ -31,6 +31,7 @@ #include "source/opt/ir_context.h" #include "source/opt/reflect.h" #include "source/spirv_target_env.h" +#include "source/table2.h" #include "source/util/string_utils.h" namespace spvtools { @@ -447,19 +448,18 @@ constexpr std::array, 14> kOpcodeHandlers{{ // ============== End opcode handler implementations. ======================= namespace { -ExtensionSet getExtensionsRelatedTo(const CapabilitySet& capabilities, - const AssemblyGrammar& grammar) { +ExtensionSet getExtensionsRelatedTo(const CapabilitySet& capabilities) { ExtensionSet output; - const spv_operand_desc_t* desc = nullptr; + spvtools::OperandDesc* desc = nullptr; for (auto capability : capabilities) { - if (SPV_SUCCESS != grammar.lookupOperand(SPV_OPERAND_TYPE_CAPABILITY, - static_cast(capability), - &desc)) { + if (SPV_SUCCESS != + spvtools::LookupOperand(SPV_OPERAND_TYPE_CAPABILITY, + static_cast(capability), &desc)) { continue; } - for (uint32_t i = 0; i < desc->numExtensions; ++i) { - output.insert(desc->extensions[i]); + for (auto extension : desc->extensions()) { + output.insert(extension); } } @@ -513,8 +513,8 @@ void TrimCapabilitiesPass::addInstructionRequirementsForOpcode( return; } - const spv_opcode_desc_t* desc = {}; - auto result = context()->grammar().lookupOpcode(opcode, &desc); + spvtools::InstructionDesc* desc; + auto result = spvtools::LookupOpcode(opcode, &desc); if (result != SPV_SUCCESS) { return; } @@ -551,9 +551,9 @@ void TrimCapabilitiesPass::addInstructionRequirementsForOperand( // case 1: Operand is a single value, can directly lookup. if (!spvOperandIsConcreteMask(operand.type)) { - const spv_operand_desc_t* desc = {}; - auto result = context()->grammar().lookupOperand(operand.type, - operand.words[0], &desc); + spvtools::OperandDesc* desc = nullptr; + auto result = + spvtools::LookupOperand(operand.type, operand.words[0], &desc); if (result != SPV_SUCCESS) { return; } @@ -569,8 +569,8 @@ void TrimCapabilitiesPass::addInstructionRequirementsForOperand( continue; } - const spv_operand_desc_t* desc = {}; - auto result = context()->grammar().lookupOperand(operand.type, mask, &desc); + spvtools::OperandDesc* desc = nullptr; + auto result = spvtools::LookupOperand(operand.type, mask, &desc); if (result != SPV_SUCCESS) { continue; } @@ -648,8 +648,8 @@ void TrimCapabilitiesPass::addInstructionRequirements( void TrimCapabilitiesPass::AddExtensionsForOperand( const spv_operand_type_t type, const uint32_t value, ExtensionSet* extensions) const { - const spv_operand_desc_t* desc = nullptr; - spv_result_t result = context()->grammar().lookupOperand(type, value, &desc); + spvtools::OperandDesc* desc = nullptr; + spv_result_t result = spvtools::LookupOperand(type, value, &desc); if (result != SPV_SUCCESS) { return; } @@ -724,7 +724,7 @@ Pass::Status TrimCapabilitiesPass::TrimUnrequiredCapabilities( Pass::Status TrimCapabilitiesPass::TrimUnrequiredExtensions( const ExtensionSet& required_extensions) const { const auto supported_extensions = - getExtensionsRelatedTo(supportedCapabilities_, context()->grammar()); + getExtensionsRelatedTo(supportedCapabilities_); bool modified_module = false; for (auto extension : supported_extensions) { diff --git a/source/opt/trim_capabilities_pass.h b/source/opt/trim_capabilities_pass.h index 3ec06d7ff7..511d996e9a 100644 --- a/source/opt/trim_capabilities_pass.h +++ b/source/opt/trim_capabilities_pass.h @@ -28,6 +28,7 @@ #include "source/opt/module.h" #include "source/opt/pass.h" #include "source/spirv_target_env.h" +#include "source/table2.h" namespace spvtools { namespace opt { @@ -129,11 +130,13 @@ class TrimCapabilitiesPass : public Pass { private: // Inserts every capability listed by `descriptor` this pass supports into - // `output`. Expects a Descriptor like `spv_opcode_desc_t` or - // `spv_operand_desc_t`. - template - inline void addSupportedCapabilitiesToSet(const Descriptor* const descriptor, - CapabilitySet* output) const { + // `output`. + // TODO(b/413723831): After extended instruction sets are converted to use + // descriptors, change this back into a template to collapse all three + // implementations. + void addSupportedCapabilitiesToSet( + const spv_ext_inst_desc_t* const descriptor, + CapabilitySet* output) const { const uint32_t capabilityCount = descriptor->numCapabilities; for (uint32_t i = 0; i < capabilityCount; ++i) { const auto capability = descriptor->capabilities[i]; @@ -142,10 +145,28 @@ class TrimCapabilitiesPass : public Pass { } } } + void addSupportedCapabilitiesToSet( + const spvtools::InstructionDesc* const descriptor, + CapabilitySet* output) const { + for (auto capability : descriptor->capabilities()) { + if (supportedCapabilities_.contains(capability)) { + output->insert(capability); + } + } + } + void addSupportedCapabilitiesToSet( + const spvtools::OperandDesc* const descriptor, + CapabilitySet* output) const { + for (auto capability : descriptor->capabilities()) { + if (supportedCapabilities_.contains(capability)) { + output->insert(capability); + } + } + } // Inserts every extension listed by `descriptor` required by the module into - // `output`. Expects a Descriptor like `spv_opcode_desc_t` or - // `spv_operand_desc_t`. + // `output`. Expects a Descriptor like spvtools::OperandDesc or + // spvtools::InstructionDesc. template inline void addSupportedExtensionsToSet(const Descriptor* const descriptor, ExtensionSet* output) const { @@ -153,8 +174,8 @@ class TrimCapabilitiesPass : public Pass { spvVersionForTargetEnv(context()->GetTargetEnv())) { return; } - output->insert(descriptor->extensions, - descriptor->extensions + descriptor->numExtensions); + output->insert(descriptor->extensions().begin(), + descriptor->extensions().end()); } void addInstructionRequirementsForOpcode(spv::Op opcode, diff --git a/source/table.cpp b/source/table.cpp index fb2726bdb7..0b9e2f5249 100644 --- a/source/table.cpp +++ b/source/table.cpp @@ -49,15 +49,11 @@ spv_context spvContextCreate(spv_target_env env) { return nullptr; } - spv_opcode_table opcode_table = nullptr; - spv_operand_table operand_table = nullptr; spv_ext_inst_table ext_inst_table = nullptr; - spvOpcodeTableGet(&opcode_table, env); - spvOperandTableGet(&operand_table, env); spvExtInstTableGet(&ext_inst_table, env); - return new spv_context_t{env, opcode_table, operand_table, ext_inst_table, + return new spv_context_t{env, ext_inst_table, nullptr /* a null default consumer */}; } diff --git a/source/table.h b/source/table.h index 47625c5fbd..6fef99c545 100644 --- a/source/table.h +++ b/source/table.h @@ -17,61 +17,32 @@ #include "source/extensions.h" #include "source/latest_version_spirv_header.h" +#include "source/util/index_range.h" #include "spirv-tools/libspirv.hpp" -typedef struct spv_opcode_desc_t { - const char* name; - const spv::Op opcode; - const uint32_t numAliases; - const char** aliases; - const uint32_t numCapabilities; - const spv::Capability* capabilities; - // operandTypes[0..numTypes-1] describe logical operands for the instruction. - // The operand types include result id and result-type id, followed by - // the types of arguments. - const uint16_t numTypes; - spv_operand_type_t operandTypes[16]; // TODO: Smaller/larger? - const bool hasResult; // Does the instruction have a result ID operand? - const bool hasType; // Does the instruction have a type ID operand? - // A set of extensions that enable this feature. If empty then this operand - // value is in core and its availability is subject to minVersion. The - // assembler, binary parser, and disassembler ignore this rule, so you can - // freely process invalid modules. - const uint32_t numExtensions; - const spvtools::Extension* extensions; - // Minimal core SPIR-V version required for this feature, if without - // extensions. ~0u means reserved for future use. ~0u and non-empty extension - // lists means only available in extensions. - const uint32_t minVersion; - const uint32_t lastVersion; -} spv_opcode_desc_t; +// NOTE: Instruction and operand tables have moved to table2.{h|cpp}, +// where they are represented more compactly. +// TODO(dneto): Move the remaining tables to the new scheme. -typedef struct spv_operand_desc_t { - const char* name; - const uint32_t value; - const uint32_t numAliases; - const char** aliases; - const uint32_t numCapabilities; - const spv::Capability* capabilities; - // A set of extensions that enable this feature. If empty then this operand - // value is in core and its availability is subject to minVersion. The - // assembler, binary parser, and disassembler ignore this rule, so you can - // freely process invalid modules. - const uint32_t numExtensions; - const spvtools::Extension* extensions; - const spv_operand_type_t operandTypes[16]; // TODO: Smaller/larger? - // Minimal core SPIR-V version required for this feature, if without - // extensions. ~0u means reserved for future use. ~0u and non-empty extension - // lists means only available in extensions. - const uint32_t minVersion; - const uint32_t lastVersion; -} spv_operand_desc_t; - -typedef struct spv_operand_desc_group_t { - const spv_operand_type_t type; - const uint32_t count; - const spv_operand_desc_t* entries; -} spv_operand_desc_group_t; +// Define the static tables that describe the grammatical structure +// of SPIR-V instructions and their operands. These tables are populated +// by reading the grammar files from SPIRV-Headers. +// +// Most clients access these tables indirectly via an spv_context_t object. +// +// The overall structure among containers (i.e. skipping scalar data members) +// is as follows: +// +// An spv_context_t: +// - points to spv_ext_inst_table_t = array of spv_ext_inst_group_t +// +// An spv_ext_inst_group_t has: +// - array of spv_ext_inst_desc_t +// +// An spv_ext_inst_desc_t has: +// - a name string +// - array of spv::Capability +// - array of spv_operand_type_t typedef struct spv_ext_inst_desc_t { const char* name; @@ -87,33 +58,17 @@ typedef struct spv_ext_inst_group_t { const spv_ext_inst_desc_t* entries; } spv_ext_inst_group_t; -typedef struct spv_opcode_table_t { - const uint32_t count; - const spv_opcode_desc_t* entries; -} spv_opcode_table_t; - -typedef struct spv_operand_table_t { - const uint32_t count; - const spv_operand_desc_group_t* types; -} spv_operand_table_t; - typedef struct spv_ext_inst_table_t { const uint32_t count; const spv_ext_inst_group_t* groups; } spv_ext_inst_table_t; -typedef const spv_opcode_desc_t* spv_opcode_desc; -typedef const spv_operand_desc_t* spv_operand_desc; typedef const spv_ext_inst_desc_t* spv_ext_inst_desc; -typedef const spv_opcode_table_t* spv_opcode_table; -typedef const spv_operand_table_t* spv_operand_table; typedef const spv_ext_inst_table_t* spv_ext_inst_table; struct spv_context_t { const spv_target_env target_env; - const spv_opcode_table opcode_table; - const spv_operand_table operand_table; const spv_ext_inst_table ext_inst_table; spvtools::MessageConsumer consumer; }; @@ -125,12 +80,6 @@ namespace spvtools { void SetContextMessageConsumer(spv_context context, MessageConsumer consumer); } // namespace spvtools -// Populates *table with entries for env. -spv_result_t spvOpcodeTableGet(spv_opcode_table* table, spv_target_env env); - -// Populates *table with entries for env. -spv_result_t spvOperandTableGet(spv_operand_table* table, spv_target_env env); - // Populates *table with entries for env. spv_result_t spvExtInstTableGet(spv_ext_inst_table* table, spv_target_env env); diff --git a/source/table2.cpp b/source/table2.cpp new file mode 100644 index 0000000000..a8eb3bfc3b --- /dev/null +++ b/source/table2.cpp @@ -0,0 +1,274 @@ +// 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 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 values, ordered by (operand kind, name) +// 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 opcode values, ordered by (name, value) +// 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 a NameValue entries for that kind. +// The result is an IndexRange into kOperandNames, and are sorted +// 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); + +// Returns the name of an extension, as an index into kStrings +IndexRange ExtensionToIndexRange(Extension extension); + +#include "core_tables.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); +} + +spv_result_t LookupOpcode(spv::Op opcode, InstructionDesc** desc) { + // Metaphor: Look for the needle in the haystack. + // The operand value is the first member. + 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, 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 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(kInstructionNames.begin(), + kInstructionNames.end(), needle, less); + if (where != kInstructionNames.end() && + std::strcmp(getChars(where->name), name) == 0) { + return LookupOpcode(static_cast(where->value), desc); + } + return SPV_ERROR_INVALID_LOOKUP; +} + +namespace { +template +spv_result_t LookupOpcodeForEnvInternal(spv_target_env env, KEY_TYPE key, + InstructionDesc** desc) { + InstructionDesc* desc_proxy; + auto status = LookupOpcode(key, &desc_proxy); + if (status != SPV_SUCCESS) { + return status; + } + 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, + InstructionDesc** desc) { + return LookupOpcodeForEnvInternal(env, name, desc); +} + +spv_result_t LookupOpcodeForEnv(spv_target_env env, spv::Op opcode, + InstructionDesc** desc) { + return LookupOpcodeForEnvInternal(env, opcode, desc); +} + +spv_result_t LookupOperand(spv_operand_type_t type, uint32_t value, + 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, 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 NameValue 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 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); + 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.value == kSentinel ? name_len : lhs.name.count() - 1; + const auto rhs_len = + rhs.value == 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) { + return LookupOperand(type, where->value, desc); + } + return SPV_ERROR_INVALID_LOOKUP; +} + +namespace to_be_used { +void functions_to_be_used() { + (void)ExtensionToIndexRange(spvtools::kSPV_AMD_shader_ballot); +} +} // namespace to_be_used + +} // namespace spvtools diff --git a/source/table2.h b/source/table2.h new file mode 100644 index 0000000000..7be8604507 --- /dev/null +++ b/source/table2.h @@ -0,0 +1,182 @@ +// Copyright (c) 2025 The Khronos Group Inc. +// 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. + +#ifndef SOURCE_TABLE2_H_ +#define SOURCE_TABLE2_H_ + +#include "source/extensions.h" +#include "source/latest_version_spirv_header.h" +#include "source/util/index_range.h" +#include "spirv-tools/libspirv.hpp" + +// Define the objects that describe the grammatical structure of SPIR-V +// instructions and their operands. The objects are owned by static +// tables populated at C++ build time from the grammar files from SPIRV-Headers. +// +// Clients use freestanding methods to lookup an opcode or an operand, either +// by numeric value (in the binary), or by name. +// +// For historical reasons, the opcode lookup can also use a target enviroment +// enum to filter for opcodes supported in that environment. +// +// It should be very fast for the system loader to load (and possibly relocate) +// the static tables. In particular, there should be very few global symbols +// with independent addresses. Prefer a very few large tables of items rather +// than dozens or hundreds of global symbols. +// +// The overall structure among containers (i.e. skipping scalar data members) +// is as follows: +// +// An OperandDesc describes an operand. +// An InstructionDesc desribes an instruction. +// +// Both OperandDesc and InstructionDesc have members: +// - a name string +// - array of alias strings +// - array of spv::Capability (as an enum) +// - array of spv_operand_type_t (as an enum) +// - array of spvtools::Extension (as an enum) +// - a minVersion +// - a lastVersion +// +// An OperandDesc also has: +// - a uint32_t value. +// +// An InstructionDesc also has: +// - a spv::Op opcode +// - a bool hasResult +// - a bool hasType +// +// The arrays are represented by spans into a global static array, with one +// array for each of: +// - null-terminated strings, for names +// - arrays of null-terminated strings, for alias lists +// - spv_operand_type_t +// - spv::Capability +// - spvtools::Extension +// +// Note: Currently alias lists never have more than one element. +// The data structures and code do not assume this. + +// TODO(dneto): convert the tables for extended instructions, and extensions. +// Currently (as defined in table.h): +// An spv_ext_inst_group_t has: +// - array of spv_ext_inst_desc_t +// +// An spv_ext_inst_desc_t has: +// - a name string +// - array of spv::Capability +// - array of spv_operand_type_t + +namespace spvtools { + +using IndexRange = utils::IndexRange; + +// Describes a SPIR-V operand. +struct OperandDesc { + const uint32_t value; + + const IndexRange operands_range; // Indexes kOperandSpans + const IndexRange name_range; // Indexes kStrings + const IndexRange aliases_range; // Indexes kAliasSpans + const IndexRange capabilities_range; // Indexes kCapabilitySpans + // A set of extensions that enable this feature. If empty then this operand + // value is in core and its availability is subject to minVersion. The + // assembler, binary parser, and disassembler ignore this rule, so you can + // freely process invalid modules. + const IndexRange extensions_range; // Indexes kExtensionSpans + // Minimal core SPIR-V version required for this feature, if without + // extensions. ~0u means reserved for future use. ~0u and non-empty + // extension lists means only available in extensions. + const uint32_t minVersion; + const uint32_t lastVersion; + utils::Span operands() const; + utils::Span name() const; + utils::Span aliases() const; + utils::Span capabilities() const; + utils::Span extensions() const; + + OperandDesc(const OperandDesc&) = delete; + OperandDesc(OperandDesc&&) = delete; +}; + +// Describes an Instruction +struct InstructionDesc { + const spv::Op opcode; + const bool hasResult; + const bool hasType; + + const IndexRange operands_range; // Indexes kOperandSpans + const IndexRange name_range; // Indexes kStrings + const IndexRange aliases_range; // Indexes kAliasSpans + const IndexRange capabilities_range; // Indexes kCapbilitySpans + // A set of extensions that enable this feature. If empty then this operand + // value is in core and its availability is subject to minVersion. The + // assembler, binary parser, and disassembler ignore this rule, so you can + // freely process invalid modules. + const IndexRange extensions_range; // Indexes kExtensionSpans + // Minimal core SPIR-V version required for this feature, if without + // extensions. ~0u means reserved for future use. ~0u and non-empty + // extension lists means only available in extensions. + const uint32_t minVersion; + const uint32_t lastVersion; + // Returns the span of elements in the global grammar tables corresponding + // to the privately-stored index ranges + utils::Span operands() const; + utils::Span name() const; + utils::Span aliases() const; + utils::Span capabilities() const; + utils::Span extensions() const; + + InstructionDesc(const InstructionDesc&) = delete; + InstructionDesc(InstructionDesc&&) = delete; +}; + +// Finds the instruction description by opcode name. The name should not +// have the "Op" prefix. On success, returns SPV_SUCCESS and updates *desc. +spv_result_t LookupOpcode(const char* name, InstructionDesc** desc); +// Finds the instruction description by opcode value. +// On success, returns SPV_SUCCESS and updates *desc. +spv_result_t LookupOpcode(spv::Op opcode, InstructionDesc** desc); + +// Finds the instruction description by opcode name, without the "Op" prefix. +// A lookup will succeed if: +// - The instruction exists, and +// - Either the target environment supports the SPIR-V version of the +// instruction, +// or the instruction is enabled by at least one extension, +// or the instruction is enabled by at least one capability., +// On success, returns SPV_SUCCESS and updates *desc. +spv_result_t LookupOpcodeForEnv(spv_target_env env, const char* name, + InstructionDesc** desc); + +// Finds the instruction description by opcode value. +// A lookup will succeed if: +// - The instruction exists, and +// - Either the target environment supports the SPIR-V version of the +// instruction, +// or the instruction is enabled by at least one extension, +// or the instruction is enabled by at least one capability., +// On success, returns SPV_SUCCESS and updates *desc. +spv_result_t LookupOpcodeForEnv(spv_target_env env, spv::Op, + InstructionDesc** desc); + +spv_result_t LookupOperand(spv_operand_type_t type, const char* name, + size_t name_len, OperandDesc** desc); +spv_result_t LookupOperand(spv_operand_type_t type, uint32_t operand, + OperandDesc** desc); + +} // namespace spvtools +#endif // SOURCE_TABLE2_H_ diff --git a/source/text.cpp b/source/text.cpp index 156d96272c..d32ab19194 100644 --- a/source/text.cpp +++ b/source/text.cpp @@ -38,6 +38,7 @@ #include "source/spirv_constant.h" #include "source/spirv_target_env.h" #include "source/table.h" +#include "source/table2.h" #include "source/text_handler.h" #include "source/util/bitutils.h" #include "source/util/parse_number.h" @@ -284,8 +285,8 @@ spv_result_t spvTextEncodeOperand(const spvtools::AssemblyGrammar& grammar, return context->diagnostic() << "Invalid " << spvOperandTypeStr(type) << " '" << textValue << "'."; } - spv_opcode_desc opcodeEntry = nullptr; - if (grammar.lookupOpcode(opcode, &opcodeEntry)) { + spvtools::InstructionDesc* opcodeEntry = nullptr; + if (LookupOpcodeForEnv(grammar.target_env(), opcode, &opcodeEntry)) { return context->diagnostic(SPV_ERROR_INTERNAL) << "OpSpecConstant opcode table out of sync"; } @@ -295,8 +296,9 @@ spv_result_t spvTextEncodeOperand(const spvtools::AssemblyGrammar& grammar, // type Id and result Id, since they've already been processed. assert(opcodeEntry->hasType); assert(opcodeEntry->hasResult); - assert(opcodeEntry->numTypes >= 2); - spvPushOperandTypes(opcodeEntry->operandTypes + 2, pExpectedOperands); + assert(opcodeEntry->operands().size() >= 2); + spvPushOperandTypes(opcodeEntry->operands().subspan(2), + pExpectedOperands); } break; case SPV_OPERAND_TYPE_LITERAL_INTEGER: @@ -346,10 +348,11 @@ spv_result_t spvTextEncodeOperand(const spvtools::AssemblyGrammar& grammar, context->getTypeOfTypeGeneratingValue(pInst->resultTypeId); if (!spvtools::isScalarFloating(expected_type) && !spvtools::isScalarIntegral(expected_type)) { - spv_opcode_desc d; + spvtools::InstructionDesc* opcodeEntry = nullptr; const char* opcode_name = "opcode"; - if (SPV_SUCCESS == grammar.lookupOpcode(pInst->opcode, &d)) { - opcode_name = d->name; + if (SPV_SUCCESS == LookupOpcode(pInst->opcode, &opcodeEntry)) { + opcode_name = + opcodeEntry->name().data(); // assumes it's null-terminated } return context->diagnostic() << "Type for " << opcode_name @@ -455,8 +458,8 @@ spv_result_t spvTextEncodeOperand(const spvtools::AssemblyGrammar& grammar, default: { // NOTE: All non literal operands are handled here using the operand // table. - spv_operand_desc entry; - if (grammar.lookupOperand(type, textValue, strlen(textValue), &entry)) { + spvtools::OperandDesc* entry = nullptr; + if (spvtools::LookupOperand(type, textValue, strlen(textValue), &entry)) { return context->diagnostic() << "Invalid " << spvOperandTypeStr(type) << " '" << textValue << "'."; } @@ -466,7 +469,7 @@ spv_result_t spvTextEncodeOperand(const spvtools::AssemblyGrammar& grammar, } // Prepare to parse the operands for this logical operand. - spvPushOperandTypes(entry->operandTypes, pExpectedOperands); + spvPushOperandTypes(entry->operands(), pExpectedOperands); } break; } return SPV_SUCCESS; @@ -503,7 +506,7 @@ spv_result_t encodeInstructionStartingWithImmediate( if (operandValue == "=") return context->diagnostic() << firstWord << " not allowed before =."; - // Needed to pass to spvTextEncodeOpcode(), but it shouldn't ever be + // Needed to pass to spvTextEncodeOperand(), but it shouldn't ever be // expanded. spv_operand_pattern_t dummyExpectedOperands; error = spvTextEncodeOperand( @@ -620,7 +623,7 @@ spv_result_t encodeInstructionStartingWithOpUnknown( if (operandValue == "=") return context->diagnostic() << "OpUnknown not allowed before =."; - // Needed to pass to spvTextEncodeOpcode(), but it shouldn't ever be + // Needed to pass to spvTextEncodeOperand(), but it shouldn't ever be // expanded. spv_operand_pattern_t dummyExpectedOperands; error = spvTextEncodeOperand( @@ -705,8 +708,8 @@ spv_result_t spvTextEncodeOpcode(const spvtools::AssemblyGrammar& grammar, // NOTE: The table contains Opcode names without the "Op" prefix. const char* pInstName = opcodeName.data() + 2; - spv_opcode_desc opcodeEntry; - error = grammar.lookupOpcode(pInstName, &opcodeEntry); + spvtools::InstructionDesc* opcodeEntry = nullptr; + error = LookupOpcodeForEnv(grammar.target_env(), pInstName, &opcodeEntry); if (error) { return context->diagnostic(error) << "Invalid Opcode name '" << opcodeName << "'"; @@ -734,10 +737,15 @@ spv_result_t spvTextEncodeOpcode(const spvtools::AssemblyGrammar& grammar, // ExecutionMode), or for extended instructions that may have their // own operands depending on the selected extended instruction. spv_operand_pattern_t expectedOperands; - expectedOperands.reserve(opcodeEntry->numTypes); - for (auto i = 0; i < opcodeEntry->numTypes; i++) - expectedOperands.push_back( - opcodeEntry->operandTypes[opcodeEntry->numTypes - i - 1]); + { + const auto operands = opcodeEntry->operands(); + const auto n = operands.size(); + expectedOperands.reserve(n); + for (auto i = 0u; i < n; i++) { + auto ty = operands[n - i - 1]; + expectedOperands.push_back(ty); + } + } while (!expectedOperands.empty()) { const spv_operand_type_t type = expectedOperands.back(); diff --git a/source/text_handler.h b/source/text_handler.h index 19972e9519..0b610ceaa5 100644 --- a/source/text_handler.h +++ b/source/text_handler.h @@ -141,7 +141,14 @@ class AssemblyContext { spv_result_t advance(); // Sets word to the next word in the input text. Fills next_position with - // the next location past the end of the word. + // the next location past the end of the word. Returns an error if the + // context is invalid or has no more text. Otherwise returns SPV_SUCCESS. + // Assumes the next part of the input is not whitespace. + // + // A word ends at the next comment or whitespace. However, double-quoted + // strings remain intact, and a backslash always escapes the next character. + // The input stream may end before a matching double-quote, or immediately + // after a backslash. Both such cases still count as success. spv_result_t getWord(std::string* word, spv_position next_position); // Returns true if the next word in the input is the start of a new Opcode. diff --git a/source/util/index_range.h b/source/util/index_range.h index ee2299f62a..d256bde3d3 100644 --- a/source/util/index_range.h +++ b/source/util/index_range.h @@ -16,6 +16,7 @@ #define SOURCE_UTIL_INDEX_RANGE_H_ #include +#include #include #include "source/util/span.h" @@ -53,8 +54,6 @@ class IndexRange { using span_type = spvtools::utils::Span; return base ? span_type(base + first_, count_) : span_type(); } - // This specialization lets us pass in a null pointer, which yields - // an empty span. template spvtools::utils::Span apply(std::nullptr_t) const { using span_type = spvtools::utils::Span; diff --git a/source/val/validate_capability.cpp b/source/val/validate_capability.cpp index 75b96c965d..9be2967f96 100644 --- a/source/val/validate_capability.cpp +++ b/source/val/validate_capability.cpp @@ -18,6 +18,7 @@ #include #include "source/opcode.h" +#include "source/table2.h" #include "source/val/instruction.h" #include "source/val/validate.h" #include "source/val/validation_state.h" @@ -285,16 +286,16 @@ bool IsSupportOptionalOpenCL_1_2(uint32_t capability) { // Checks if |capability| was enabled by extension. bool IsEnabledByExtension(ValidationState_t& _, uint32_t capability) { - spv_operand_desc operand_desc = nullptr; - _.grammar().lookupOperand(SPV_OPERAND_TYPE_CAPABILITY, capability, - &operand_desc); + spvtools::OperandDesc* operand_desc = nullptr; + spvtools::LookupOperand(SPV_OPERAND_TYPE_CAPABILITY, capability, + &operand_desc); // operand_desc is expected to be not null, otherwise validator would have // failed at an earlier stage. This 'assert' is 'just in case'. assert(operand_desc); - ExtensionSet operand_exts(operand_desc->numExtensions, - operand_desc->extensions); + ExtensionSet operand_exts(operand_desc->extensions_range.count(), + operand_desc->extensions().data()); if (operand_exts.empty()) return false; return _.HasAnyOfExtensions(operand_exts); @@ -352,14 +353,14 @@ spv_result_t CapabilityPass(ValidationState_t& _, const Instruction* inst) { assert(operand.offset < inst->words().size()); const uint32_t capability = inst->word(operand.offset); - const auto capability_str = [&_, capability]() { - spv_operand_desc desc = nullptr; - if (_.grammar().lookupOperand(SPV_OPERAND_TYPE_CAPABILITY, capability, - &desc) != SPV_SUCCESS || + const auto capability_str = [capability]() { + spvtools::OperandDesc* desc = nullptr; + if (spvtools::LookupOperand(SPV_OPERAND_TYPE_CAPABILITY, capability, + &desc) != SPV_SUCCESS || !desc) { return std::string("Unknown"); } - return std::string(desc->name); + return std::string(desc->name().data()); }; const auto env = _.context()->target_env; diff --git a/source/val/validate_extensions.cpp b/source/val/validate_extensions.cpp index 912b9ae891..131102d955 100644 --- a/source/val/validate_extensions.cpp +++ b/source/val/validate_extensions.cpp @@ -26,6 +26,7 @@ #include "source/latest_version_glsl_std_450_header.h" #include "source/latest_version_opencl_std_header.h" #include "source/spirv_constant.h" +#include "source/table2.h" #include "source/val/instruction.h" #include "source/val/validate.h" #include "source/val/validation_state.h" @@ -93,17 +94,17 @@ spv_result_t ValidateOperandForDebugInfo( const std::function& ext_inst_name) { auto* operand = _.FindDef(inst->word(word_index)); if (operand->opcode() != expected_opcode) { - spv_opcode_desc desc = nullptr; - if (_.grammar().lookupOpcode(expected_opcode, &desc) != SPV_SUCCESS || + spvtools::InstructionDesc* desc = nullptr; + if (spvtools::LookupOpcodeForEnv(_.context()->target_env, expected_opcode, + &desc) != SPV_SUCCESS || !desc) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << ext_inst_name() << ": " << "expected operand " << operand_name << " is invalid"; } return _.diag(SPV_ERROR_INVALID_DATA, inst) - << ext_inst_name() << ": " - << "expected operand " << operand_name << " must be a result id of " - << "Op" << desc->name; + << ext_inst_name() << ": " << "expected operand " << operand_name + << " must be a result id of " << "Op" << desc->name().data(); } return SPV_SUCCESS; } diff --git a/source/val/validate_instruction.cpp b/source/val/validate_instruction.cpp index acb0d6379f..d7dd04a04d 100644 --- a/source/val/validate_instruction.cpp +++ b/source/val/validate_instruction.cpp @@ -27,6 +27,7 @@ #include "source/spirv_constant.h" #include "source/spirv_target_env.h" #include "source/spirv_validator_options.h" +#include "source/table2.h" #include "source/util/string_utils.h" #include "source/val/validate.h" #include "source/val/validation_state.h" @@ -35,14 +36,13 @@ namespace spvtools { namespace val { namespace { -std::string ToString(const CapabilitySet& capabilities, - const AssemblyGrammar& grammar) { +std::string ToString(const CapabilitySet& capabilities) { std::stringstream ss; for (auto capability : capabilities) { - spv_operand_desc desc; - if (SPV_SUCCESS == grammar.lookupOperand(SPV_OPERAND_TYPE_CAPABILITY, - uint32_t(capability), &desc)) - ss << desc->name << " "; + spvtools::OperandDesc* desc = nullptr; + if (SPV_SUCCESS == spvtools::LookupOperand(SPV_OPERAND_TYPE_CAPABILITY, + uint32_t(capability), &desc)) + ss << desc->name().data() << " "; else ss << uint32_t(capability) << " "; } @@ -72,10 +72,11 @@ CapabilitySet EnablingCapabilitiesForOp(const ValidationState_t& state, break; } // Look it up in the grammar - spv_opcode_desc opcode_desc = {}; - if (SPV_SUCCESS == state.grammar().lookupOpcode(opcode, &opcode_desc)) { + spvtools::InstructionDesc* opcode_desc = nullptr; + if (SPV_SUCCESS == + LookupOpcodeForEnv(state.context()->target_env, opcode, &opcode_desc)) { return state.grammar().filterCapsAgainstTargetEnv( - opcode_desc->capabilities, opcode_desc->numCapabilities); + opcode_desc->capabilities()); } return CapabilitySet(); } @@ -86,7 +87,7 @@ CapabilitySet EnablingCapabilitiesForOp(const ValidationState_t& state, // return an error code. spv_result_t OperandVersionExtensionCheck( ValidationState_t& _, const Instruction* inst, size_t which_operand, - const spv_operand_desc_t& operand_desc, uint32_t word) { + const spvtools::OperandDesc& operand_desc, uint32_t word) { const uint32_t module_version = _.version(); const uint32_t operand_min_version = operand_desc.minVersion; const uint32_t operand_last_version = operand_desc.lastVersion; @@ -103,27 +104,29 @@ spv_result_t OperandVersionExtensionCheck( return _.diag(SPV_ERROR_WRONG_VERSION, inst) << spvtools::utils::CardinalToOrdinal(which_operand) << " operand of " << spvOpcodeString(inst->opcode()) << ": operand " - << operand_desc.name << "(" << word << ") requires SPIR-V version " + << operand_desc.name().data() << "(" << word + << ") requires SPIR-V version " << SPV_SPIRV_VERSION_MAJOR_PART(operand_last_version) << "." << SPV_SPIRV_VERSION_MINOR_PART(operand_last_version) << " or earlier"; } - if (!reserved && operand_desc.numExtensions == 0) { + if (!reserved && operand_desc.extensions_range.empty()) { return _.diag(SPV_ERROR_WRONG_VERSION, inst) << spvtools::utils::CardinalToOrdinal(which_operand) << " operand of " << spvOpcodeString(inst->opcode()) << ": operand " - << operand_desc.name << "(" << word << ") requires SPIR-V version " + << operand_desc.name().data() << "(" << word + << ") requires SPIR-V version " << SPV_SPIRV_VERSION_MAJOR_PART(operand_min_version) << "." << SPV_SPIRV_VERSION_MINOR_PART(operand_min_version) << " or later"; } else { - ExtensionSet required_extensions(operand_desc.numExtensions, - operand_desc.extensions); + ExtensionSet required_extensions(operand_desc.extensions_range.count(), + operand_desc.extensions().data()); if (!_.HasAnyOfExtensions(required_extensions)) { return _.diag(SPV_ERROR_MISSING_EXTENSION, inst) << spvtools::utils::CardinalToOrdinal(which_operand) << " operand of " << spvOpcodeString(inst->opcode()) - << ": operand " << operand_desc.name << "(" << word + << ": operand " << operand_desc.name().data() << "(" << word << ") requires one of these extensions: " << ExtensionSetToString(required_extensions); } @@ -166,9 +169,9 @@ spv_result_t CheckRequiredCapabilities(ValidationState_t& state, } CapabilitySet enabling_capabilities; - spv_operand_desc operand_desc = nullptr; + spvtools::OperandDesc* operand_desc = nullptr; const auto lookup_result = - state.grammar().lookupOperand(operand.type, word, &operand_desc); + spvtools::LookupOperand(operand.type, word, &operand_desc); if (lookup_result == SPV_SUCCESS) { // Allow FPRoundingMode decoration if requested. if (operand.type == SPV_OPERAND_TYPE_DECORATION && @@ -186,7 +189,7 @@ spv_result_t CheckRequiredCapabilities(ValidationState_t& state, } } else { enabling_capabilities = state.grammar().filterCapsAgainstTargetEnv( - operand_desc->capabilities, operand_desc->numCapabilities); + operand_desc->capabilities()); } // When encountering an OpCapability instruction, the instruction pass @@ -201,7 +204,7 @@ spv_result_t CheckRequiredCapabilities(ValidationState_t& state, << "Operand " << which_operand << " of " << spvOpcodeString(inst->opcode()) << " requires one of these capabilities: " - << ToString(enabling_capabilities, state.grammar()); + << ToString(enabling_capabilities); } } return OperandVersionExtensionCheck(state, inst, which_operand, @@ -222,10 +225,10 @@ spv_result_t ReservedCheck(ValidationState_t& _, const Instruction* inst) { case spv::Op::OpImageSparseSampleProjExplicitLod: case spv::Op::OpImageSparseSampleProjDrefImplicitLod: case spv::Op::OpImageSparseSampleProjDrefExplicitLod: { - spv_opcode_desc inst_desc; - _.grammar().lookupOpcode(opcode, &inst_desc); + spvtools::InstructionDesc* inst_desc = nullptr; + spvtools::LookupOpcode(opcode, &inst_desc); return _.diag(SPV_ERROR_INVALID_BINARY, inst) - << "Invalid Opcode name 'Op" << inst_desc->name << "'"; + << "Invalid Opcode name 'Op" << inst_desc->name().data() << "'"; } default: break; @@ -242,8 +245,7 @@ spv_result_t CapabilityCheck(ValidationState_t& _, const Instruction* inst) { if (!_.HasAnyOfCapabilities(opcode_caps)) { return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst) << "Opcode " << spvOpcodeString(opcode) - << " requires one of these capabilities: " - << ToString(opcode_caps, _.grammar()); + << " requires one of these capabilities: " << ToString(opcode_caps); } for (size_t i = 0; i < inst->operands().size(); ++i) { const auto& operand = inst->operand(i); @@ -276,8 +278,8 @@ spv_result_t CapabilityCheck(ValidationState_t& _, const Instruction* inst) { // dependencies for the opcode. spv_result_t VersionCheck(ValidationState_t& _, const Instruction* inst) { const auto opcode = inst->opcode(); - spv_opcode_desc inst_desc; - const spv_result_t r = _.grammar().lookupOpcode(opcode, &inst_desc); + spvtools::InstructionDesc* inst_desc; + const spv_result_t r = spvtools::LookupOpcode(opcode, &inst_desc); assert(r == SPV_SUCCESS); (void)r; @@ -297,13 +299,14 @@ spv_result_t VersionCheck(ValidationState_t& _, const Instruction* inst) { const bool capability_check_is_sufficient = inst->opcode() != spv::Op::OpTerminateInvocation; - if (capability_check_is_sufficient && (inst_desc->numCapabilities > 0u)) { + if (capability_check_is_sufficient && !inst_desc->capabilities().empty()) { // We already checked that the direct capability dependency has been // satisfied. We don't need to check any further. return SPV_SUCCESS; } - ExtensionSet exts(inst_desc->numExtensions, inst_desc->extensions); + ExtensionSet exts(inst_desc->extensions().begin(), + inst_desc->extensions().end()); if (exts.empty()) { // If no extensions can enable this instruction, then emit error // messages only concerning core SPIR-V versions if errors happen. diff --git a/source/val/validate_mode_setting.cpp b/source/val/validate_mode_setting.cpp index 2bf8b5bf3a..2e12308e03 100644 --- a/source/val/validate_mode_setting.cpp +++ b/source/val/validate_mode_setting.cpp @@ -18,6 +18,7 @@ #include "source/opcode.h" #include "source/spirv_target_env.h" +#include "source/table2.h" #include "source/val/instruction.h" #include "source/val/validate.h" #include "source/val/validation_state.h" @@ -284,7 +285,6 @@ spv_result_t ValidateEntryPoint(ValidationState_t& _, const Instruction* inst) { } } - // TODO - Need to add TileShadingRateQCOM support if (spvIsVulkanEnv(_.context()->target_env)) { switch (execution_model) { case spv::ExecutionModel::GLCompute: @@ -313,7 +313,7 @@ spv_result_t ValidateEntryPoint(ValidationState_t& _, const Instruction* inst) { } if (!ok) { return _.diag(SPV_ERROR_INVALID_DATA, inst) - << _.VkErrorID(10685) + << _.VkErrorID(6426) << "In the Vulkan environment, GLCompute execution model " "entry points require either the LocalSize or " "LocalSizeId execution mode or an object decorated with " @@ -926,12 +926,12 @@ spv_result_t ValidateDuplicateExecutionModes(ValidationState_t& _) { std::set seen_per_entry; std::set seen_per_operand; - const auto lookupMode = [&_](spv::ExecutionMode mode) -> std::string { - spv_operand_desc desc = nullptr; - if (_.grammar().lookupOperand(SPV_OPERAND_TYPE_EXECUTION_MODE, - static_cast(mode), - &desc) == SPV_SUCCESS) { - return std::string(desc->name); + const auto lookupMode = [](spv::ExecutionMode mode) -> std::string { + spvtools::OperandDesc* desc = nullptr; + if (spvtools::LookupOperand(SPV_OPERAND_TYPE_EXECUTION_MODE, + static_cast(mode), + &desc) == SPV_SUCCESS) { + return std::string(desc->name().data()); } return "Unknown"; }; diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp index fa567febfa..ba3915f5f3 100644 --- a/source/val/validation_state.cpp +++ b/source/val/validation_state.cpp @@ -23,6 +23,7 @@ #include "source/opcode.h" #include "source/spirv_constant.h" #include "source/spirv_target_env.h" +#include "source/table2.h" #include "source/util/make_unique.h" #include "source/val/basic_block.h" #include "source/val/construct.h" @@ -367,11 +368,11 @@ void ValidationState_t::RegisterCapability(spv::Capability cap) { if (module_capabilities_.contains(cap)) return; module_capabilities_.insert(cap); - spv_operand_desc desc; - if (SPV_SUCCESS == grammar_.lookupOperand(SPV_OPERAND_TYPE_CAPABILITY, - uint32_t(cap), &desc)) { - for (auto capability : - CapabilitySet(desc->numCapabilities, desc->capabilities)) { + spvtools::OperandDesc* desc = nullptr; + if (SPV_SUCCESS == spvtools::LookupOperand(SPV_OPERAND_TYPE_CAPABILITY, + uint32_t(cap), &desc)) { + for (auto capability : CapabilitySet(desc->capabilities_range.count(), + desc->capabilities().data())) { RegisterCapability(capability); } } @@ -2379,8 +2380,8 @@ std::string ValidationState_t::VkErrorID(uint32_t id, return VUID_WRAP(VUID-StandaloneSpirv-OpTypeRuntimeArray-04680); case 4682: return VUID_WRAP(VUID-StandaloneSpirv-OpControlBarrier-04682); - case 10685: - return VUID_WRAP(VUID-StandaloneSpirv-None-10685); // formally 04683 and 06426 + case 6426: + return VUID_WRAP(VUID-StandaloneSpirv-LocalSize-06426); // formally 04683 case 4685: return VUID_WRAP(VUID-StandaloneSpirv-OpGroupNonUniformBallotBitCount-04685); case 4686: diff --git a/source/val/validation_state.h b/source/val/validation_state.h index 8cc87a4ace..08d33a224d 100644 --- a/source/val/validation_state.h +++ b/source/val/validation_state.h @@ -32,6 +32,7 @@ #include "source/name_mapper.h" #include "source/spirv_definition.h" #include "source/spirv_validator_options.h" +#include "source/table2.h" #include "source/val/decoration.h" #include "source/val/function.h" #include "source/val/instruction.h" @@ -788,12 +789,12 @@ class ValidationState_t { // Returns the string name for |decoration|. std::string SpvDecorationString(uint32_t decoration) { - spv_operand_desc desc = nullptr; - if (grammar_.lookupOperand(SPV_OPERAND_TYPE_DECORATION, decoration, - &desc) != SPV_SUCCESS) { + spvtools::OperandDesc* desc = nullptr; + if (spvtools::LookupOperand(SPV_OPERAND_TYPE_DECORATION, decoration, + &desc) != SPV_SUCCESS) { return std::string("Unknown"); } - return std::string(desc->name); + return std::string(desc->name().data()); } std::string SpvDecorationString(spv::Decoration decoration) { return SpvDecorationString(uint32_t(decoration)); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5387a9795e..a07dc22c59 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -117,11 +117,12 @@ set(TEST_SOURCES named_id_test.cpp name_mapper_test.cpp op_unknown_test.cpp + opcode_lookup_test.cpp opcode_make_test.cpp opcode_require_capabilities_test.cpp opcode_split_test.cpp - opcode_table_get_test.cpp operand_capabilities_test.cpp + operand_lookup_test.cpp operand_test.cpp operand_pattern_test.cpp parse_number_test.cpp diff --git a/test/opcode_lookup_test.cpp b/test/opcode_lookup_test.cpp new file mode 100644 index 0000000000..9587c13c0a --- /dev/null +++ b/test/opcode_lookup_test.cpp @@ -0,0 +1,178 @@ +// Copyright 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. + +#include +#include + +#include "gmock/gmock.h" +#include "source/spirv_target_env.h" +#include "source/table2.h" +#include "test/unit_spirv.h" + +using ::testing::ContainerEq; +using ::testing::ValuesIn; + +namespace spvtools { +namespace { + +struct OpcodeLookupCase { + std::string name; + uint32_t opcode; + bool expect_pass = true; +}; + +std::ostream& operator<<(std::ostream& os, const OpcodeLookupCase& olc) { + os << "OLC('" << olc.name << "', " << olc.opcode << ", expect pass? " + << olc.expect_pass << ")"; + return os; +} + +using OpcodeLookupTest = ::testing::TestWithParam; + +TEST_P(OpcodeLookupTest, OpcodeLookup_ByName) { + InstructionDesc* desc = nullptr; + auto status = LookupOpcode(GetParam().name.data(), &desc); + if (GetParam().expect_pass) { + EXPECT_EQ(status, SPV_SUCCESS); + ASSERT_NE(desc, nullptr); + EXPECT_EQ(static_cast(desc->opcode), GetParam().opcode); + } else { + EXPECT_NE(status, SPV_SUCCESS); + EXPECT_EQ(desc, nullptr); + } +} + +TEST_P(OpcodeLookupTest, OpcodeLookup_ByOpcode_Success) { + InstructionDesc* desc = nullptr; + if (GetParam().expect_pass) { + spv::Op opcode = static_cast(GetParam().opcode); + auto status = LookupOpcode(opcode, &desc); + EXPECT_EQ(status, SPV_SUCCESS); + ASSERT_NE(desc, nullptr); + EXPECT_EQ(desc->opcode, opcode); + } +} + +INSTANTIATE_TEST_SUITE_P(Samples, OpcodeLookupTest, + ValuesIn(std::vector{ + {"Nop", 0}, + {"WritePipe", 275}, + {"TypeAccelerationStructureKHR", 5341}, + {"TypeAccelerationStructureNV", 5341}, + {"does not exist", 0, false}, + {"CopyLogical", 400}, + {"FPGARegINTEL", 5949}, + {"SubgroupMatrixMultiplyAccumulateINTEL", 6237}, + })); + +TEST(OpcodeLookupSingleTest, OpcodeLookup_ByOpcode_Fails) { + // This list may need adjusting over time. + std::array bad_opcodes = {{99999, 37737, 110101}}; + for (auto bad_opcode : bad_opcodes) { + InstructionDesc* desc = nullptr; + spv::Op opcode = static_cast(bad_opcode); + auto status = LookupOpcode(opcode, &desc); + EXPECT_NE(status, SPV_SUCCESS); + ASSERT_EQ(desc, nullptr); + } +} + +struct OpcodeLookupEnvCase { + std::string name; + uint32_t opcode; + spv_target_env env = SPV_ENV_UNIVERSAL_1_0; + bool expect_pass = true; +}; + +std::ostream& operator<<(std::ostream& os, const OpcodeLookupEnvCase& olec) { + os << "OLC('" << olec.name << "', " << olec.opcode << ", env " + << spvTargetEnvDescription(olec.env) << ", expect pass? " + << olec.expect_pass << ")"; + return os; +} + +using OpcodeLookupEnvTest = ::testing::TestWithParam; + +TEST_P(OpcodeLookupEnvTest, OpcodeLookupForEnv_ByName) { + InstructionDesc* desc = nullptr; + auto status = + LookupOpcodeForEnv(GetParam().env, GetParam().name.data(), &desc); + if (GetParam().expect_pass) { + EXPECT_EQ(status, SPV_SUCCESS); + ASSERT_NE(desc, nullptr); + EXPECT_EQ(static_cast(desc->opcode), GetParam().opcode); + } else { + EXPECT_NE(status, SPV_SUCCESS); + EXPECT_EQ(desc, nullptr); + } +} + +TEST_P(OpcodeLookupEnvTest, OpcodeLookupForEnv_ByOpcode) { + InstructionDesc* desc = nullptr; + spv::Op opcode = static_cast(GetParam().opcode); + auto status = LookupOpcodeForEnv(GetParam().env, opcode, &desc); + if (GetParam().expect_pass) { + EXPECT_EQ(status, SPV_SUCCESS); + ASSERT_NE(desc, nullptr); + EXPECT_EQ(desc->opcode, opcode); + } else { + // Skip nonsense cases created for the lookup-by-name case. + if (GetParam().name != "does not exist") { + EXPECT_NE(status, SPV_SUCCESS); + EXPECT_EQ(desc, nullptr); + } + } +} + +INSTANTIATE_TEST_SUITE_P(Samples, OpcodeLookupEnvTest, + ValuesIn(std::vector{ + {"Nop", 0}, + {"WritePipe", 275}, + {"TypeAccelerationStructureKHR", 5341}, + {"TypeAccelerationStructureNV", 5341}, + {"does not exist", 0, SPV_ENV_UNIVERSAL_1_0, + false}, + {"CopyLogical", 400, SPV_ENV_UNIVERSAL_1_0, false}, + {"CopyLogical", 400, SPV_ENV_UNIVERSAL_1_3, false}, + {"CopyLogical", 400, SPV_ENV_UNIVERSAL_1_4, true}, + {"FPGARegINTEL", 5949}, + {"SubgroupMatrixMultiplyAccumulateINTEL", 6237}, + })); + +TEST(OpcodeLookupExtInstTest, Operands) { + // The SPIR-V spec grammar has a single rule for OpExtInst, where the last + // item is "sequence of Ids". SPIRV-Tools handles it differently. It drops + // that last item, and instead specifies those operands as operands of the + // extended instruction enum, such as 'cos'. + // See https://github.com/KhronosGroup/SPIRV-Tools/issues/233 + // Test the exact sequence of operand types extracted for OpExtInst. + InstructionDesc* desc = nullptr; + auto status = LookupOpcode("ExtInst", &desc); + EXPECT_EQ(status, SPV_SUCCESS); + ASSERT_NE(desc, nullptr); + + EXPECT_EQ(desc->operands_range.count(), 4u); + + auto operands = desc->operands(); + using vtype = std::vector; + + EXPECT_THAT( + vtype(operands.begin(), operands.end()), + ContainerEq(vtype{SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, + SPV_OPERAND_TYPE_ID, + SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER})); +} + +} // namespace +} // namespace spvtools diff --git a/test/opcode_require_capabilities_test.cpp b/test/opcode_require_capabilities_test.cpp index 615c09429e..d9af9fd5a4 100644 --- a/test/opcode_require_capabilities_test.cpp +++ b/test/opcode_require_capabilities_test.cpp @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "test/unit_spirv.h" - #include "source/enum_set.h" +#include "source/table2.h" +#include "test/unit_spirv.h" namespace spvtools { namespace { @@ -31,15 +31,12 @@ using OpcodeTableCapabilitiesTest = ::testing::TestWithParam; TEST_P(OpcodeTableCapabilitiesTest, TableEntryMatchesExpectedCapabilities) { - auto env = SPV_ENV_UNIVERSAL_1_1; - spv_opcode_table opcodeTable; - ASSERT_EQ(SPV_SUCCESS, spvOpcodeTableGet(&opcodeTable, env)); - spv_opcode_desc entry; - ASSERT_EQ(SPV_SUCCESS, spvOpcodeTableValueLookup(env, opcodeTable, - GetParam().opcode, &entry)); - EXPECT_EQ( - ElementsIn(GetParam().capabilities), - ElementsIn(CapabilitySet(entry->numCapabilities, entry->capabilities))); + spvtools::InstructionDesc* desc = nullptr; + ASSERT_EQ(SPV_SUCCESS, spvtools::LookupOpcode(GetParam().opcode, &desc)); + auto caps = desc->capabilities(); + EXPECT_EQ(ElementsIn(GetParam().capabilities), + ElementsIn(CapabilitySet(static_cast(caps.size()), + caps.data()))); } INSTANTIATE_TEST_SUITE_P( diff --git a/test/opcode_table_get_test.cpp b/test/opcode_table_get_test.cpp deleted file mode 100644 index 4ff67d9555..0000000000 --- a/test/opcode_table_get_test.cpp +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2015-2016 The Khronos Group Inc. -// -// 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. - -#include "gmock/gmock.h" -#include "test/unit_spirv.h" - -namespace spvtools { -namespace { - -using GetTargetOpcodeTableGetTest = ::testing::TestWithParam; -using ::testing::ValuesIn; - -TEST_P(GetTargetOpcodeTableGetTest, IntegrityCheck) { - spv_opcode_table table; - ASSERT_EQ(SPV_SUCCESS, spvOpcodeTableGet(&table, GetParam())); - ASSERT_NE(0u, table->count); - ASSERT_NE(nullptr, table->entries); -} - -TEST_P(GetTargetOpcodeTableGetTest, InvalidPointerTable) { - ASSERT_EQ(SPV_ERROR_INVALID_POINTER, spvOpcodeTableGet(nullptr, GetParam())); -} - -INSTANTIATE_TEST_SUITE_P(OpcodeTableGet, GetTargetOpcodeTableGetTest, - ValuesIn(spvtest::AllTargetEnvironments())); - -} // namespace -} // namespace spvtools diff --git a/test/operand_capabilities_test.cpp b/test/operand_capabilities_test.cpp index 53dbe07010..5cd89b80ef 100644 --- a/test/operand_capabilities_test.cpp +++ b/test/operand_capabilities_test.cpp @@ -23,6 +23,7 @@ #include "source/operand.h" #include "source/spirv_target_env.h" #include "source/table.h" +#include "source/table2.h" #include "spirv-tools/libspirv.h" #include "test/unit_spirv.h" @@ -80,13 +81,13 @@ TEST_P(EnumCapabilityTest, Sample) { const auto env = std::get<0>(GetParam()); const auto context = spvContextCreate(env); const AssemblyGrammar grammar(context); - spv_operand_desc entry; + spvtools::OperandDesc* entry = nullptr; ASSERT_EQ(SPV_SUCCESS, - grammar.lookupOperand(std::get<1>(GetParam()).type, - std::get<1>(GetParam()).value, &entry)); - const auto cap_set = grammar.filterCapsAgainstTargetEnv( - entry->capabilities, entry->numCapabilities); + spvtools::LookupOperand(std::get<1>(GetParam()).type, + std::get<1>(GetParam()).value, &entry)); + const auto cap_set = + grammar.filterCapsAgainstTargetEnv(entry->capabilities()); EXPECT_THAT(ElementsIn(cap_set), Eq(ElementsIn(std::get<1>(GetParam()).expected_capabilities))) diff --git a/test/operand_lookup_test.cpp b/test/operand_lookup_test.cpp new file mode 100644 index 0000000000..9a48e0f8b2 --- /dev/null +++ b/test/operand_lookup_test.cpp @@ -0,0 +1,133 @@ +// Copyright 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. + +#include +#include + +#include "gmock/gmock.h" +#include "source/operand.h" +#include "source/table2.h" +#include "spirv-tools/libspirv.h" +#include "test/unit_spirv.h" + +using ::testing::ContainerEq; +using ::testing::ValuesIn; + +namespace spvtools { +namespace { + +struct OperandLookupCase { + spv_operand_type_t type; + std::string name; + size_t name_length; + uint32_t value; + bool expect_pass = true; +}; + +std::ostream& operator<<(std::ostream& os, const OperandLookupCase& olc) { + os << "OLC('" << spvOperandTypeStr(olc.type) << " '" << olc.name + << "', len:" << olc.name_length << ", value:" << olc.value + << ", expect pass? " << olc.expect_pass << ")"; + return os; +} + +using OperandLookupTest = ::testing::TestWithParam; + +TEST_P(OperandLookupTest, OperandLookup_ByName) { + OperandDesc* desc = nullptr; + auto status = LookupOperand(GetParam().type, GetParam().name.data(), + GetParam().name_length, &desc); + if (GetParam().expect_pass) { + EXPECT_EQ(status, SPV_SUCCESS); + ASSERT_NE(desc, nullptr); + EXPECT_EQ(desc->value, GetParam().value); + } else { + EXPECT_NE(status, SPV_SUCCESS); + EXPECT_EQ(desc, nullptr); + } +} + +TEST_P(OperandLookupTest, OperandLookup_ByValue_Success) { + OperandDesc* desc = nullptr; + if (GetParam().expect_pass) { + const auto value = GetParam().value; + auto status = LookupOperand(GetParam().type, GetParam().value, &desc); + EXPECT_EQ(status, SPV_SUCCESS); + ASSERT_NE(desc, nullptr); + EXPECT_EQ(desc->value, value); + } +} + +INSTANTIATE_TEST_SUITE_P( + Samples, OperandLookupTest, + ValuesIn(std::vector{ + {SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, "Relaxed", 7, 0}, + // "None" is an alias for "Relaxed" + {SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, "None", 4, 0}, + // "NonPrivatePointer" is the canonical name that appeared + // in an extension and incorporated in SPIR-V 1.5. + {SPV_OPERAND_TYPE_MEMORY_ACCESS, "NonPrivatePointer", 17, 32}, + // "NonPrivatePointerKHR" is the name from the extension. + {SPV_OPERAND_TYPE_MEMORY_ACCESS, "NonPrivatePointerKHR", 20, 32}, + // "NoAliasINTELMask" is only in an extension + {SPV_OPERAND_TYPE_MEMORY_ACCESS, "NoAliasINTELMask", 16, 0x20000}, + {SPV_OPERAND_TYPE_RAY_FLAGS, "TerminateOnFirstHitKHR", 22, 4}, + {SPV_OPERAND_TYPE_FPENCODING, "BFloat16KHR", 11, 0}, + // Lookup on An optional operand type should match the base lookup. + {SPV_OPERAND_TYPE_OPTIONAL_FPENCODING, "BFloat16KHR", 11, 0}, + // Lookup is type-specific. + {SPV_OPERAND_TYPE_OPTIONAL_FPENCODING, "Relaxed", 7, 0, false}, + // Invalid string + {SPV_OPERAND_TYPE_RAY_FLAGS, "does_not_exist", 14, 0, false}, + // Check lengths + {SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, "Relaxed", 6, 0, false}, + {SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, "Relaxed", 7, 0, true}, + {SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, "Relaxed|", 7, 0, true}, + {SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, "Relaxed|", 8, 0, false}, + })); + +TEST(OperandLookupSingleTest, OperandLookup_ByValue_Fails) { + // This list may need adjusting over time. + std::array types = { + {SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, SPV_OPERAND_TYPE_RAY_FLAGS, + SPV_OPERAND_TYPE_FPENCODING}}; + std::array bad_values = {{99999, 37737, 110101}}; + for (auto type : types) { + for (auto bad_value : bad_values) { + OperandDesc* desc = nullptr; + auto status = LookupOperand(type, bad_value, &desc); + EXPECT_NE(status, SPV_SUCCESS); + ASSERT_EQ(desc, nullptr); + } + } +} + +TEST(OperandLookupOperands, Sample) { + // Check the operand list for a valid operand lookup. + OperandDesc* desc = nullptr; + auto status = LookupOperand(SPV_OPERAND_TYPE_IMAGE, "Grad", 4, &desc); + EXPECT_EQ(status, SPV_SUCCESS); + ASSERT_NE(desc, nullptr); + + EXPECT_EQ(desc->operands_range.count(), 2u); + + auto operands = desc->operands(); + using vtype = std::vector; + + EXPECT_THAT(vtype(operands.begin(), operands.end()), + ContainerEq(vtype{SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID})); +} + +} // namespace +} // namespace spvtools diff --git a/test/operand_pattern_test.cpp b/test/operand_pattern_test.cpp index 58b8a0866c..2138c3f03d 100644 --- a/test/operand_pattern_test.cpp +++ b/test/operand_pattern_test.cpp @@ -69,13 +69,8 @@ struct MaskExpansionCase { using MaskExpansionTest = ::testing::TestWithParam; TEST_P(MaskExpansionTest, Sample) { - spv_operand_table operandTable = nullptr; - auto env = SPV_ENV_UNIVERSAL_1_0; - ASSERT_EQ(SPV_SUCCESS, spvOperandTableGet(&operandTable, env)); - spv_operand_pattern_t pattern(GetParam().initial); - spvPushOperandTypesForMask(env, operandTable, GetParam().type, - GetParam().mask, &pattern); + spvPushOperandTypesForMask(GetParam().type, GetParam().mask, &pattern); EXPECT_THAT(pattern, Eq(GetParam().expected)); } diff --git a/test/operand_test.cpp b/test/operand_test.cpp index ec45da5816..4cc0818f17 100644 --- a/test/operand_test.cpp +++ b/test/operand_test.cpp @@ -19,25 +19,8 @@ namespace spvtools { namespace { -using GetTargetTest = ::testing::TestWithParam; using ::testing::ValuesIn; -TEST_P(GetTargetTest, Default) { - spv_operand_table table; - ASSERT_EQ(SPV_SUCCESS, spvOperandTableGet(&table, GetParam())); - ASSERT_NE(0u, table->count); - ASSERT_NE(nullptr, table->types); -} - -TEST_P(GetTargetTest, InvalidPointerTable) { - ASSERT_EQ(SPV_ERROR_INVALID_POINTER, spvOperandTableGet(nullptr, GetParam())); -} - -INSTANTIATE_TEST_SUITE_P(OperandTableGet, GetTargetTest, - ValuesIn(std::vector{ - SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, - SPV_ENV_VULKAN_1_0})); - TEST(OperandString, AllAreDefinedExceptVariable) { // None has no string, so don't test it. EXPECT_EQ(0u, SPV_OPERAND_TYPE_NONE); diff --git a/test/text_to_binary.extension_test.cpp b/test/text_to_binary.extension_test.cpp index 59f2af9e79..711095ee74 100644 --- a/test/text_to_binary.extension_test.cpp +++ b/test/text_to_binary.extension_test.cpp @@ -70,9 +70,15 @@ TEST_F(TextToBinaryTest, MultiImport) { Eq("Import Id is being defined a second time")); } -TEST_F(TextToBinaryTest, TooManyArguments) { +TEST_F(TextToBinaryTest, TooManyArgumentsIdEqualQuote) { const std::string input = R"(%opencl = OpExtInstImport "OpenCL.std" - %2 = OpExtInst %float %opencl cos %x %oops")"; + %2 = OpExtInst %float %opencl cos %x %oops=")"; + EXPECT_THAT(CompileFailure(input), Eq("Expected '=', found end of stream.")); +} + +TEST_F(TextToBinaryTest, TooManyArgumentsIdEqual) { + const std::string input = R"(%opencl = OpExtInstImport "OpenCL.std" + %2 = OpExtInst %float %opencl cos %x %oops=)"; EXPECT_THAT(CompileFailure(input), Eq("Expected '=', found end of stream.")); } diff --git a/test/val/val_capability_test.cpp b/test/val/val_capability_test.cpp index cc5011ed85..d3e305437a 100644 --- a/test/val/val_capability_test.cpp +++ b/test/val/val_capability_test.cpp @@ -2370,24 +2370,16 @@ INSTANTIATE_TEST_SUITE_P( // TODO(umar): Instruction capability checks spv_result_t spvCoreOperandTableNameLookup(spv_target_env env, - const spv_operand_table table, const spv_operand_type_t type, const char* name, const size_t nameLength) { - if (!table) return SPV_ERROR_INVALID_TABLE; if (!name) return SPV_ERROR_INVALID_POINTER; - for (uint64_t typeIndex = 0; typeIndex < table->count; ++typeIndex) { - const auto& group = table->types[typeIndex]; - if (type != group.type) continue; - for (uint64_t index = 0; index < group.count; ++index) { - const auto& entry = group.entries[index]; - // Check for min version only. - if (spvVersionForTargetEnv(env) >= entry.minVersion && - nameLength == strlen(entry.name) && - !strncmp(entry.name, name, nameLength)) { - return SPV_SUCCESS; - } + spvtools::OperandDesc* entry = nullptr; + if (SPV_SUCCESS == spvtools::LookupOperand(type, name, nameLength, &entry)) { + // Check for min version only. + if (spvVersionForTargetEnv(env) >= entry->minVersion) { + return SPV_SUCCESS; } } @@ -2398,8 +2390,7 @@ spv_result_t spvCoreOperandTableNameLookup(spv_target_env env, bool Exists(const std::string& capability, spv_target_env env) { ScopedContext sc(env); return SPV_SUCCESS == - spvCoreOperandTableNameLookup(env, sc.context->operand_table, - SPV_OPERAND_TYPE_CAPABILITY, + spvCoreOperandTableNameLookup(env, SPV_OPERAND_TYPE_CAPABILITY, capability.c_str(), capability.size()); } diff --git a/test/val/val_modes_test.cpp b/test/val/val_modes_test.cpp index 625d76b008..256ab43c4b 100644 --- a/test/val/val_modes_test.cpp +++ b/test/val/val_modes_test.cpp @@ -64,7 +64,7 @@ OpEntryPoint GLCompute %main "main" CompileSuccessfully(spirv, env); EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions(env)); EXPECT_THAT(getDiagnosticString(), - AnyVUID("VUID-StandaloneSpirv-None-10685")); + AnyVUID("VUID-StandaloneSpirv-LocalSize-06426")); EXPECT_THAT( getDiagnosticString(), HasSubstr( diff --git a/utils/Table/Context.py b/utils/Table/Context.py new file mode 100755 index 0000000000..1d9110e7d3 --- /dev/null +++ b/utils/Table/Context.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +# Copyright 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. + +#from typing import * +from enum import IntEnum +from . IndexRange import * +from . StringList import * + + +class Context(): + """ + Contains global tables for strings, and lists-of-strings. + It contains: + + - string_buffer: A list of null-terminated strings. The list is + partitioned into contiguous segments dedicated to a specific + kind of string, e.g.: + - all instruction opcodes + - all extension names + - all aliases for a given enum + - all enum names for a first operand type + - all enum names for a second operand type, etc. + Strings are sorted within each segment. + + - string_total_len: The sum of lengths of strings in string_buffer. + + - strings: Maps a string to an IndexRange indicating where the string + can be found in the (future) concatenation of all strings in the + string_buffer. + + - range_buffer: A dictionary mapping a string kind to a list IndexRange + objects R. Each R is one of: + + - An index range referencing one string as it appears in the + (future) concatenation of all strings in the string_buffer. + In this case R represents a single string. + + - An index range referencing earlier elements in range_buffer[kind] + itself. In this case R represents a list of strings. + + - ranges: A dictionary mapping a string kind and lists-of-strings + to its encoding in the range_buffer array. + + It is a two-level mapping of Python type: + + dict[str,dict[StringList,IndexRange]] + + where + + ranges[kind][list of strings] = an IndexRange + + The 'kind' string encodes a purpose including: + - opcodes: the list of instruction opcode strings. + - the list of aliases for an opcode, or an enum + - an operand type such as 'SPV_OPERAND_TYPE_DIMENSIONALITY': + the list of operand type names, e.g. '2D', '3D', 'Cube', + 'Rect', etc. in the case of SPV_OPERAND_TYPE_DIMENSIONALITY. + By convention, the 'kind' string should be a singular noun for + the type of object named by each member of the list. + + The IndexRange leaf value encodes a list of strings as in the + second case described for 'range_buffer'. + + """ + def __init__(self) -> None: + self.string_total_len: int = 0 # Sum of lengths of all strings in string_buffer + self.string_buffer: list[str] = [] + self.strings: dict[str, IndexRange] = {} + self.ir_to_string: dict[IndexRange, str] = {} # Inverse of self.strings + + self.range_buffer: dict[str,list[IndexRange]] = {} + # We need StringList here because it's hashable, and so it + # can be used as the key for a dict. + self.ranges: dict[str,dict[StringList,IndexRange]] = {} + + def GetString(self, ir: IndexRange) -> str: + if ir in self.ir_to_string: + return self.ir_to_string[ir] + raise Exception("unregistered index range {}".format(str(ir))) + + def AddString(self, s: str) -> IndexRange: + """ + Adds or finds a string in the string_buffer. + Returns its IndexRange. + """ + if s in self.strings: + return self.strings[s] + # Allocate space, including for the terminating null. + s_space: int = len(s) + 1 + ir = IndexRange(self.string_total_len, s_space) + self.strings[s] = ir + self.ir_to_string[ir] = s + self.string_total_len += s_space + self.string_buffer.append(s) + return ir + + def AddStringList(self, kind: str, words: list[str]) -> IndexRange: + """ + Ensures a list of strings is recorded in range_buffer[kind], and + returns its location in the range_buffer[kind]. + As a side effect, also ensures each string in the list is in + the string_buffer. + """ + l = StringList(words) + + entry: dict[StringList, IndexRange] = self.ranges.get(kind, {}) + if kind not in self.ranges: + self.ranges[kind] = entry + self.range_buffer[kind] = [] + + if l in entry: + return entry[l] + new_ranges = [self.AddString(s) for s in l] + ir = IndexRange(len(self.range_buffer[kind]), len(new_ranges)) + self.range_buffer[kind].extend(new_ranges) + entry[l] = ir + return ir + + def dump(self) -> None: + print("string_total_len: {}".format(self.string_total_len)) + + sbi = 0 + print("string_buffer:") + for sb in self.string_buffer: + print(" {}: '{}'".format(sbi, sb)) + sbi += len(sb) + 1 + print("") + + s = [] + for k,v in self.strings.items(): + s.append("'{}': {}".format(k,str(v))) + print("strings:\n {}\n".format('\n '.join(s))) + + for rbk, rbv in self.range_buffer.items(): + print("range_buffer[{}]:".format(rbk)) + i: int = 0 + for r in rbv: + print(" {} {}: {}".format(rbk, i, str(r))) + i += 1 + print("") + + for rk, rv in self.ranges.items(): + for key,val in rv.items(): + print("ranges[{}][{}]: {}".format(str(rk),str(key), str(val))) + print("") + + diff --git a/utils/Table/Context_test.py b/utils/Table/Context_test.py new file mode 100755 index 0000000000..a168bf78b6 --- /dev/null +++ b/utils/Table/Context_test.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +# Copyright 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. + +import unittest +from . Context import Context +from . IndexRange import IndexRange +from . StringList import StringList + +class TestCreate(unittest.TestCase): + def test_creation(self) -> None: + x = Context() + self.assertIsInstance(x.string_total_len, int) + self.assertIsInstance(x.string_buffer, list) + self.assertIsInstance(x.strings, dict) + self.assertEqual(x.string_total_len, 0) + self.assertEqual(x.string_buffer, []) + self.assertEqual(x.strings, {}) + +class TestString(unittest.TestCase): + def test_AddString_new(self) -> None: + x = Context() + abc_ir = x.AddString("abc") + self.assertEqual(abc_ir, IndexRange(0,4)) + self.assertEqual(x.string_total_len, 4) + self.assertEqual(x.string_buffer, ["abc"]) + self.assertEqual(x.strings, {"abc": IndexRange(0,4)}) + + qz_ir = x.AddString("qz") + self.assertEqual(qz_ir, IndexRange(4,3)) + self.assertEqual(x.string_total_len, 7) + self.assertEqual(x.string_buffer, ["abc", "qz"]) + self.assertEqual(x.strings, {"abc": IndexRange(0,4), "qz": IndexRange(4,3)}) + + empty_ir = x.AddString("") + self.assertEqual(empty_ir, IndexRange(7,1)) + self.assertEqual(x.string_total_len, 8) + self.assertEqual(x.string_buffer, ["abc", "qz", ""]) + self.assertEqual(x.strings, {"abc": IndexRange(0,4), "qz": IndexRange(4,3), "": IndexRange(7,1)}) + + def test_AddString_idempotent(self) -> None: + x = Context() + abc_ir = x.AddString("abc") + self.assertEqual(abc_ir, IndexRange(0,4)) + self.assertEqual(x.string_total_len, 4) + self.assertEqual(x.string_buffer, ["abc"]) + self.assertEqual(x.strings, {"abc": IndexRange(0,4)}) + + abc_ir = x.AddString("abc") + self.assertEqual(abc_ir, IndexRange(0,4)) + self.assertEqual(x.string_total_len, 4) + self.assertEqual(x.string_buffer, ["abc"]) + self.assertEqual(x.strings, {"abc": IndexRange(0,4)}) + +class TestStringList(unittest.TestCase): + def test_AddStringList_empty(self) -> None: + x = Context() + x_ir = x.AddStringList('x', []) + self.assertEqual(x_ir, IndexRange(0,0)) + self.assertEqual(x.string_buffer, []) + self.assertEqual(x.range_buffer, { 'x': [] }) + self.assertEqual(x.ranges, {'x': {StringList([]): IndexRange(0,0)}}) + + def test_AddgStringList_nonempty(self) -> None: + x = Context() + x_ir = x.AddStringList('x', ["abc", "def"]) + + self.assertEqual(x_ir, IndexRange(0,2)) + self.assertEqual(x.range_buffer, {'x': [IndexRange(0,4), IndexRange(4,4)]}) + self.assertEqual(x.ranges, {'x': {StringList(['abc','def']): IndexRange(0,2)}}) + + def test_AddgStringList_nonempty_idempotent(self) -> None: + x = Context() + x_ir = x.AddStringList('x', ["abc", "def"]) + y_ir = x.AddStringList('x', ["abc", "def"]) + + self.assertEqual(x_ir, IndexRange(0,2)) + self.assertEqual(y_ir, IndexRange(0,2)) + self.assertEqual(x.range_buffer, {'x': [IndexRange(0,4), IndexRange(4,4)]}) + self.assertEqual(x.ranges, {'x': {StringList(['abc','def']): IndexRange(0,2)}}) + + def test_AddgStringList_nonempty_does_not_sort(self) -> None: + x = Context() + x_ir = x.AddStringList('x', ["abc", "def"]) + y_ir = x.AddStringList('x', ["def", "abc"]) + + self.assertEqual(x_ir, IndexRange(0,2)) + self.assertEqual(y_ir, IndexRange(2,2)) + self.assertEqual(x.range_buffer, {'x': [IndexRange(0,4), + IndexRange(4,4), + IndexRange(4,4), + IndexRange(0,4)]}) + self.assertEqual(x.ranges, {'x':{StringList(['abc','def']): IndexRange(0,2), + StringList(['def','abc']): IndexRange(2,2)}}) + + def test_AddgStringList_separate_by_kind(self) -> None: + x = Context() + x_ir = x.AddStringList('x', ["abc", "def"]) + y_ir = x.AddStringList('y', ["ghi", "abc"]) + + self.assertEqual(x_ir, IndexRange(0,2)) + self.assertEqual(y_ir, IndexRange(0,2)) + self.assertEqual(x.range_buffer, + {'x': [IndexRange(0,4), IndexRange(4,4)], + 'y': [IndexRange(8,4), IndexRange(0,4)]}) + self.assertEqual(x.ranges, {'x': {StringList(['abc','def']): IndexRange(0,2)}, + 'y': {StringList(['ghi','abc']): IndexRange(0,2)}}) + +if __name__ == "__main__": + unittest.main() diff --git a/utils/Table/IndexRange.py b/utils/Table/IndexRange.py new file mode 100755 index 0000000000..ba29ee2247 --- /dev/null +++ b/utils/Table/IndexRange.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# Copyright 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. + +from typing import Any + +class IndexRange(): + def __init__(self, first: int, count: int) -> None: + self.first = first + self.count = count + if first < 0: + raise Exception("invalid arg: first {} must be non-negative".format(first)) + if count < 0: + raise Exception("invalid arg: count {} must be non-negative".format(count)) + + def __eq__(self, other: Any) -> bool: + return isinstance(other, IndexRange) and self.first == other.first and self.count == other.count + + def __hash__(self) -> int: + return hash("{} {}".format(self.first, self.count)) + + def __str__(self) -> str: + return "IR({}, {})".format(self.first, self.count) diff --git a/utils/Table/IndexRange_test.py b/utils/Table/IndexRange_test.py new file mode 100755 index 0000000000..16f38e45ce --- /dev/null +++ b/utils/Table/IndexRange_test.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# Copyright 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. + +import unittest +from . IndexRange import IndexRange + +class TestIndexRange(unittest.TestCase): + def test_creation(self) -> None: + x: IndexRange = IndexRange(4,5); + self.assertEqual(x.first, 4) + self.assertEqual(x.count, 5) + + def test_creation_bad_first(self) -> None: + self.assertRaises(Exception, IndexRange, -1, 5) + + def test_creation_bad_count(self) -> None: + self.assertRaises(Exception, IndexRange, 1, -5) + + def test_distinct(self) -> None: + x = IndexRange(4, 5); + y = IndexRange(6, 7); + self.assertNotEqual(x.first, y.first) + self.assertNotEqual(x.count, y.count) + + def test_equality(self) -> None: + self.assertEqual(IndexRange(4,5), IndexRange(4,5)) + self.assertNotEqual(IndexRange(4,5), IndexRange(4,7)) + self.assertNotEqual(IndexRange(7,5), IndexRange(4,5)) + + def test_hash_heuristic(self) -> None: + x = hash(IndexRange(4,5)) + y = hash(IndexRange(4,5)) + z = hash(IndexRange(6,7)) + self.assertEqual(x, y) + self.assertNotEqual(x, z) + +if __name__ == "__main__": + unittest.main() diff --git a/utils/Table/Operand.py b/utils/Table/Operand.py new file mode 100755 index 0000000000..46c9e18516 --- /dev/null +++ b/utils/Table/Operand.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# Copyright 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. + +from typing import Any + +class Operand(): + def __init__(self, json: dict) -> None: + val = json.get('value',None) + + self._obj = json + + @property + def enumerant(self) -> str: + result = self._obj.get('enumerant', None) + if result is None: + raise Exception("operand needs an enumerant string") + return result + + @property + def value(self) -> int: + val: str|int = self._obj['value'] + if isinstance(val, int): + return val + elif isinstance(val,str): + if val.startswith("0x"): + return int(val, 16) + else: + return int(val, 10) + else: + raise Exception("operand needs a value integer or string") + + @property + def capabilities(self) -> list[str]: + return self._obj.get('capabilities',[]) + + @property + def extensions(self) -> list[str]: + return self._obj.get('extensions',[]) + + @property + def aliases(self) -> list[str]: + return self._obj.get('aliases',[]) + + @property + def parameters(self) -> list[dict]: + return self._obj.get('parameters',[]) + + @property + def version(self) -> str | None: + return self._obj.get('version',None) + + @property + def lastVersion(self) -> str | None: + return self._obj.get('lastVersion',None) diff --git a/utils/Table/Operand_test.py b/utils/Table/Operand_test.py new file mode 100755 index 0000000000..30a197cb99 --- /dev/null +++ b/utils/Table/Operand_test.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +# Copyright 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. + +import unittest +from . Operand import * + +class TestOperand(unittest.TestCase): + def test_enumerant(self) -> None: + x = Operand({'enumerant': 'abc'}); + self.assertEqual(x.enumerant, 'abc'); + + def test_value_decimal(self) -> None: + x = Operand({'value': 123}); + self.assertEqual(x.value, 123); + + def test_value_str_hex(self) -> None: + x = Operand({'value': "0x0101"}); + self.assertEqual(x.value, 257); + + def test_value_str_dec(self) -> None: + x = Operand({'value': "0101"}); + self.assertEqual(x.value, 101); + + def test_value_str_invalid_dec(self) -> None: + x = Operand({'value': "01ab"}); + self.assertRaises(Exception, lambda y: x.value, 0); + + def test_value_str_invalid_hex(self) -> None: + x = Operand({'value': "0x010j"}); + self.assertRaises(Exception, lambda y: x.value, 0); + + def test_capabilities_absent(self) -> None: + x = Operand({}); + self.assertEqual(x.capabilities, []); + + def test_capabilities_present(self) -> None: + x = Operand({'capabilities': ['abc', 'def']}); + self.assertEqual(x.capabilities, ['abc', 'def']); + + def test_extensions_absent(self) -> None: + x = Operand({}); + self.assertEqual(x.extensions, []); + + def test_extensions_present(self) -> None: + x = Operand({'extensions': ['abc', 'def']}); + self.assertEqual(x.extensions, ['abc', 'def']); + + def test_aliases_absent(self) -> None: + x = Operand({}); + self.assertEqual(x.aliases, []); + + def test_aliases_present(self) -> None: + x = Operand({'aliases': ['abc', 'def']}); + self.assertEqual(x.aliases, ['abc', 'def']); + + def test_parameters_absent(self) -> None: + x = Operand({}); + self.assertEqual(x.parameters, []); + + def test_parameters_present(self) -> None: + x = Operand({'parameters': ['abc', 'def']}); + self.assertEqual(x.parameters, ['abc', 'def']); + + def test_version_absent(self) -> None: + x = Operand({}); + self.assertEqual(x.version, None); + + def test_version_present(self) -> None: + x = Operand({'version': '1.0'}); + self.assertEqual(x.version, '1.0'); + + def test_lastVersion_absent(self) -> None: + x = Operand({}); + self.assertEqual(x.lastVersion, None); + + def test_lastVersion_present(self) -> None: + x = Operand({'lastVersion': '1.3'}); + self.assertEqual(x.lastVersion, '1.3'); + + def test_all_propertites(self) -> None: + x = Operand({'enumerant': 'Foobar', + 'value': 12, + 'capabilities': ["yes"], + 'extensions': ["SPV_FOOBAR_baz_bat"], + 'version': "1.0", + 'lastVersion': "1.3", + }); + self.assertEqual(x.enumerant, 'Foobar'); + self.assertEqual(x.value, 12); + self.assertEqual(x.capabilities, ["yes"]); + self.assertEqual(x.extensions, ["SPV_FOOBAR_baz_bat"]); + self.assertEqual(x.version, '1.0'); + self.assertEqual(x.lastVersion, '1.3'); diff --git a/utils/Table/StringList.py b/utils/Table/StringList.py new file mode 100755 index 0000000000..15ad6ca6e2 --- /dev/null +++ b/utils/Table/StringList.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# Copyright 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. + +import functools + +class StringList(list): + """ + A hashable ordered list of strings. + This can be used as the key for a dictionary. + """ + def __init__(self, strs: list[str]) -> None: + super().__init__(strs) + + def __hash__(self) -> int: # type: ignore[override] + return functools.reduce(lambda h, ir: hash((h, hash(ir))), self, 0) diff --git a/utils/Table/StringList_test.py b/utils/Table/StringList_test.py new file mode 100755 index 0000000000..d3cd1ed4df --- /dev/null +++ b/utils/Table/StringList_test.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# Copyright 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. + +import unittest +from . StringList import * + +class TestStringList(unittest.TestCase): + def test_creation_empty(self) -> None: + x = StringList([]) + self.assertEqual(len(x), 0) + self.assertEqual(x, []) + + def test_creation_nonempty(self) -> None: + x = StringList(["abc", "def"]) + self.assertEqual(len(x), 2) + self.assertEqual(x, ["abc", "def"]) + + def test_creation_does_not_sort(self) -> None: + x = StringList(["abc", "def"]) + self.assertEqual(x, ["abc", "def"]) + self.assertNotEqual(x, ["def", "abc"]) + + def test_equality(self) -> None: + x = StringList(["abc", "def"]) + y = StringList(["abc", "def"]) + z = StringList(["abc", "ef"]) + self.assertEqual(x, x) + self.assertEqual(x, y) + self.assertNotEqual(x, z) + + def test_hash_heuristic(self) -> None: + x = StringList(["abc", "def"]) + y = StringList(["abc", "def"]) + z = StringList(["abc", "df"]) + self.assertEqual(hash(x), hash(x)) + self.assertEqual(hash(x), hash(y)) + self.assertNotEqual(hash(x), hash(z)) + +if __name__ == "__main__": + unittest.main() diff --git a/utils/Table/__init__.py b/utils/Table/__init__.py new file mode 100644 index 0000000000..2210b4e47e --- /dev/null +++ b/utils/Table/__init__.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# Copyright 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. +__all__ = [ + "Context", + "IndexRange", +] diff --git a/utils/generate_grammar_tables.py b/utils/generate_grammar_tables.py index 7045235b5f..48bdeeed39 100755 --- a/utils/generate_grammar_tables.py +++ b/utils/generate_grammar_tables.py @@ -14,6 +14,8 @@ # limitations under the License. """Generates various info tables from SPIR-V JSON grammar.""" +# NOTE: This is being replaced by ggt.py + import errno import json import os.path @@ -793,12 +795,6 @@ def main(): help='input JSON grammar file for OpenCL.DebugInfo.100 ' 'extended instruction set') - parser.add_argument('--core-insts-output', metavar='', - type=str, required=False, default=None, - help='output file for core SPIR-V instructions') - parser.add_argument('--operand-kinds-output', metavar='', - type=str, required=False, default=None, - help='output file for operand kinds') parser.add_argument('--extension-enum-output', metavar='', type=str, required=False, default=None, help='output file for extension enumeration') @@ -822,25 +818,12 @@ def main(): if args.vendor_operand_kind_prefix == "...nil...": args.vendor_operand_kind_prefix = "" - if (args.core_insts_output is None) != \ - (args.operand_kinds_output is None): - print('error: --core-insts-output and --operand-kinds-output ' - 'should be specified together.') - exit(1) - if args.operand_kinds_output and not (args.spirv_core_grammar and - args.extinst_debuginfo_grammar and - args.extinst_cldebuginfo100_grammar): - print('error: --operand-kinds-output requires --spirv-core-grammar ' - 'and --extinst-debuginfo-grammar ' - 'and --extinst-cldebuginfo100-grammar') - exit(1) if (args.vendor_insts_output is None) != \ (args.extinst_vendor_grammar is None): print('error: --vendor-insts-output and ' '--extinst-vendor-grammar should be specified together.') exit(1) - if all([args.core_insts_output is None, - args.vendor_insts_output is None, + if all([args.vendor_insts_output is None, args.extension_enum_output is None, args.enum_string_mapping_output is None]): print('error: at least one output should be specified.') @@ -864,14 +847,6 @@ def main(): operand_kinds.extend(cldebuginfo100_grammar['operand_kinds']) extensions = get_extension_list(instructions, operand_kinds) operand_kinds = precondition_operand_kinds(operand_kinds) - if args.core_insts_output is not None: - make_path_to_file(args.core_insts_output) - make_path_to_file(args.operand_kinds_output) - with open(args.core_insts_output, 'w') as f: - f.write(generate_instruction_table( - core_grammar['instructions'])) - with open(args.operand_kinds_output, 'w') as f: - f.write(generate_operand_kind_table(operand_kinds)) if args.extension_enum_output is not None: make_path_to_file(args.extension_enum_output) with open(args.extension_enum_output, 'w') as f: diff --git a/utils/ggt.py b/utils/ggt.py new file mode 100755 index 0000000000..7d3fccd60b --- /dev/null +++ b/utils/ggt.py @@ -0,0 +1,840 @@ +#!/usr/bin/env python3 +# Copyright (c) 2016 Google Inc. +# +# 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. +"""Generates compressed grammar tables from SPIR-V JSON grammar.""" + +# Note: This will eventually replace generate_grammar_tables.py + +import errno +import json +import os.path +import re +import sys + +# Find modules relative to the directory containing this script. +# This is needed for hermetic Bazel builds, where the Table files are bundled +# together with this script, while keeping their relative locations. +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from Table.Context import Context +from Table.IndexRange import IndexRange +from Table.Operand import Operand + +# Extensions to recognize, but which don't necessarily come from the SPIR-V +# core or KHR grammar files. Get this list from the SPIR-V registry web page. +# NOTE: Only put things on this list if it is not in those grammar files. +EXTENSIONS_FROM_SPIRV_REGISTRY_AND_NOT_FROM_GRAMMARS = """ +SPV_AMD_gcn_shader +SPV_AMD_gpu_shader_half_float +SPV_AMD_gpu_shader_int16 +SPV_AMD_shader_trinary_minmax +SPV_KHR_non_semantic_info +SPV_EXT_relaxed_printf_string_address_space +""" + +MODE='new' + +def convert_min_required_version(version: str | None) -> str: + """Converts the minimal required SPIR-V version encoded in the grammar to + the symbol in SPIRV-Tools.""" + if version is None: + return 'SPV_SPIRV_VERSION_WORD(1, 0)' + if version == 'None': + return '0xffffffffu' + return 'SPV_SPIRV_VERSION_WORD({})'.format(version.replace('.', ',')) + + +def convert_max_required_version(version: str | None) -> str: + """Converts the maximum required SPIR-V version encoded in the grammar to + the symbol in SPIRV-Tools.""" + if version is None: + return '0xffffffffu' + return 'SPV_SPIRV_VERSION_WORD({})'.format(version.replace('.', ',')) + + +def c_bool(b: bool) -> str: + return 'true' if b else 'false' + + +def ctype(kind: str, quantifier: str) -> str: + """Returns the corresponding operand type used in spirv-tools for the given + operand kind and quantifier used in the JSON grammar. + + Arguments: + - kind, e.g. 'IdRef' + - quantifier, e.g. '', '?', '*' + + Returns: + a string of the enumerant name in spv_operand_type_t + """ + if kind == '': + raise Error("operand JSON object missing a 'kind' field") + # The following cases are where we differ between the JSON grammar and + # spirv-tools. + if kind == 'IdResultType': + kind = 'TypeId' + elif kind == 'IdResult': + kind = 'ResultId' + elif kind == 'IdMemorySemantics' or kind == 'MemorySemantics': + kind = 'MemorySemanticsId' + elif kind == 'IdScope' or kind == 'Scope': + kind = 'ScopeId' + elif kind == 'IdRef': + kind = 'Id' + + elif kind == 'ImageOperands': + kind = 'Image' + elif kind == 'Dim': + kind = 'Dimensionality' + elif kind == 'ImageFormat': + kind = 'SamplerImageFormat' + elif kind == 'KernelEnqueueFlags': + kind = 'KernelEnqFlags' + + elif kind == 'LiteralExtInstInteger': + kind = 'ExtensionInstructionNumber' + elif kind == 'LiteralSpecConstantOpInteger': + kind = 'SpecConstantOpNumber' + elif kind == 'LiteralContextDependentNumber': + kind = 'TypedLiteralNumber' + + elif kind == 'PairLiteralIntegerIdRef': + kind = 'LiteralIntegerId' + elif kind == 'PairIdRefLiteralInteger': + kind = 'IdLiteralInteger' + elif kind == 'PairIdRefIdRef': # Used by OpPhi in the grammar + kind = 'Id' + + if kind == 'FPRoundingMode': + kind = 'FpRoundingMode' + elif kind == 'FPFastMathMode': + kind = 'FpFastMathMode' + + if quantifier == '?': + kind = 'Optional{}'.format(kind) + elif quantifier == '*': + kind = 'Variable{}'.format(kind) + + return 'SPV_OPERAND_TYPE_{}'.format( + re.sub(r'([a-z])([A-Z])', r'\1_\2', kind).upper()) + + +def convert_operand_kind(obj: dict[str, str]) -> str: + """Returns the corresponding operand type used in spirv-tools for the given + operand kind and quantifier used in the JSON grammar. + + Arguments: + - obj: an instruction operand, having keys: + - 'kind', e.g. 'IdRef' + - optionally, a quantifier: '?' or '*' + + Returns: + a string of the enumerant name in spv_operand_type_t + """ + kind = obj.get('kind', '') + quantifier = obj.get('quantifier', '') + return ctype(kind, quantifier) + + +class Grammar(): + """ + Accumulates string and enum tables. + The extensions and operand kinds lists are fixed at creation time. + Prints tables for instructions, operand kinds, and underlying string + and enum tables. + Assumes an index range is emitted by printing an IndexRange object. + """ + def __init__(self, extensions: list[str], operand_kinds:list[dict]) -> None: + self.context = Context() + self.extensions = extensions + self.operand_kinds = sorted(operand_kinds, key = lambda ok: convert_operand_kind(ok)) + self.header_decls: list[str] = [self.IndexRangeDecls()] + self.body_decls: list[str] = [] + + if len(self.operand_kinds) == 0: + raise Exception("operand_kinds should be a non-empty list") + if len(self.extensions) == 0: + raise Exception("extensions should be a non-empty list") + + # Preload the string table + self.context.AddStringList('extension', extensions) + + # These operand kinds need to have their optional counterpart to also + # be represented in the lookup tables, with the same content. + self.operand_kinds_needing_optional_variant = [ + 'ImageOperands', + 'AccessQualifier', + 'MemoryAccess', + 'PackedVectorFormat', + 'CooperativeMatrixOperands', + 'MatrixMultiplyAccumulateOperands', + 'RawAccessChainOperands', + 'FPEncoding'] + + def dump(self) -> None: + self.context.dump() + + def IndexRangeDecls(self) -> str: + return """ +struct IndexRange { + uint32_t first = 0; // index of the first element in the range + uint32_t count = 0; // number of elements in the range +}; +constexpr inline IndexRange IR(uint32_t first, uint32_t count) { + return {first, count}; +} +""" + + def ExtensionEnumList(self) -> str: + """ + Returns the spvtools::Extension enum values, as a string. + This is kept separate because it will be included in 'source/extensions.h' + which has an otherwise narrow interface. + """ + return ',\n'.join(['k' + e for e in self.extensions]) + + def ComputeOperandTables(self) -> None: + """ + Returns the string for the C definitions of the operand kind tables. + + An operand kind such as ImageOperands also has an associated + operand kind that is an 'optional' variant. + These are represented as two distinct operand kinds in spv_operand_type_t. + For example, ImageOperands maps to both SPV_OPERAND_TYPE_IMAGE, and also + to SPV_OPERAND_TYPE_OPTIONAL_IMAGE. + + The definitions are: + - kOperandsByValue: a 1-dimensional array of all operand descriptions + sorted first by operand kind, then by operand value. + Only non-optional operand kinds are represented here. + + - kOperandsByValueRangeByKind: a function mapping from operand kind to + the index range into kOperandByValue. + This has mappings for both concrete and corresponding optional operand kinds. + + - kOperandNames: a 1-dimensional array of all operand name-value pairs, + sorted first by operand kinds, then by operand name. + This can have more entries than the by-value array, because names + can have string aliases. For example,the MemorySemantics value 0 + is named both "Relaxed" and "None". + Each entry is represented by an index range into the string table. + Only non-optional operand kinds are represented here. + + - kOperandNamesRangeByKind: a mapping from operand kind to the index + range into kOperandNames. + This has mappings for both concrete and corresponding optional operand kinds. + + """ + + self.header_decls.append( +""" +struct NameValue { + // Location of the null-terminated name in the global string table. + IndexRange name; + // Enum value in the binary format. + uint32_t value; +}; +// Describes a SPIR-V operand. +struct OperandDesc { + uint32_t value; + IndexRange operands_range; // Indexes kOperandSpans + IndexRange name_range; // Indexes kStrings + IndexRange aliases_range; // Indexes kAliasSpans + IndexRange capabilities_range; // Indexes kCapabilitySpans + // A set of extensions that enable this feature. If empty then this operand + // value is in core and its availability is subject to minVersion. The + // assembler, binary parser, and disassembler ignore this rule, so you can + // freely process invalid modules. + IndexRange extensions_range; // Indexes kExtensionSpans + // Minimal core SPIR-V version required for this feature, if without + // extensions. ~0u means reserved for future use. ~0u and non-empty + // extension lists means only available in extensions. + uint32_t minVersion; + uint32_t lastVersion; + utils::Span operands() const; + utils::Span name() const; + utils::Span aliases() const; + utils::Span capabilities() const; + utils::Span extensions() const; + OperandDesc(const OperandDesc&) = delete; + OperandDesc(OperandDesc&&) = delete; +}; +""") + + def ShouldEmit(operand_kind_json: dict[str,any]): + """ Returns true if we should emit a table for the given + operand kind. + """ + category = operand_kind_json.get('category') + return category in ['ValueEnum', 'BitEnum'] + + # Populate kOperandNames + operand_names: list[tuple[IndexRange,int]] = [] + name_range_for_kind: dict[str,IndexRange] = {} + for operand_kind_json in self.operand_kinds: + kind_key: str = convert_operand_kind(operand_kind_json) + if ShouldEmit(operand_kind_json): + operands = [Operand(o) for o in operand_kind_json['enumerants']] + tuples: list[tuple[str,int,str]] = [] + for o in operands: + tuples.append((o.enumerant, o.value, kind_key)) + for a in o.aliases: + tuples.append((a, o.value, kind_key)) + tuples = sorted(tuples, key = lambda t: t[0]) + ir_tuples = [(self.context.AddString(t[0]),t[1],t[2]) for t in tuples] + name_range_for_kind[kind_key] = IndexRange(len(operand_names), len(ir_tuples)) + operand_names.extend(ir_tuples) + else: + pass + operand_name_strings: list[str] = [] + for i in range(0, len(operand_names)): + ir, value, kind_key = operand_names[i] + operand_name_strings.append('{{{}, {}}}, // {} {} in {}'.format( + str(ir),value,i,self.context.GetString(ir),kind_key)) + + parts: list[str] = [] + parts.append("""// Operand names and values, ordered by (operand kind, name) +// The fields in order are: +// name, either the primary name or an alias, indexing into kStrings +// enum value""") + parts.append("std::array kOperandNames{{{{".format(len(operand_name_strings))) + parts.extend([' ' + str(x) for x in operand_name_strings]) + parts.append("}};\n") + self.body_decls.extend(parts) + + parts.append("""// Maps an operand kind to possible names for operands of that kind. +// The result is an IndexRange into kOperandNames, and the names +// are sorted by name within that span. +// An optional variant of a kind maps to the details for the corresponding +// concrete operand kind.""") + parts = ["IndexRange OperandNameRangeForKind(spv_operand_type_t type) {\n switch(type) {"] + for kind_key, ir in name_range_for_kind.items(): + parts.append(" case {}: return {};".format( + kind_key, + str(name_range_for_kind[kind_key]))) + for kind in self.operand_kinds_needing_optional_variant: + parts.append(" case {}: return {};".format( + ctype(kind, '?'), + str(name_range_for_kind[ctype(kind,'')]))) + parts.append(" default: break;"); + parts.append(" }\n return IR(0,0);\n}\n") + self.body_decls.extend(parts) + + # Populate kOperandsByValue + operands_by_value: list[str] = [] + operands_by_value_by_kind: dict[str,IndexRange] = {} + for operand_kind_json in self.operand_kinds: + kind_key: str = convert_operand_kind(operand_kind_json) + if ShouldEmit(operand_kind_json): + operands = [Operand(o) for o in operand_kind_json['enumerants']] + operand_descs: list[str] = [] + for o in sorted(operands, key = lambda o: o.value): + suboperands = [convert_operand_kind(p) for p in o.parameters] + desc = [ + o.value, + self.context.AddStringList('operand', suboperands), + str(self.context.AddString(o.enumerant)) + '/* {} */'.format(o.enumerant), + self.context.AddStringList('alias', o.aliases), + self.context.AddStringList('capability', o.capabilities), + self.context.AddStringList('extension', o.extensions), + convert_min_required_version(o.version), + convert_max_required_version(o.lastVersion), + ] + operand_descs.append('{' + ','.join([str(d) for d in desc]) + '}}, // {}'.format(kind_key)) + operands_by_value_by_kind[kind_key] = IndexRange(len(operands_by_value), len(operand_descs)) + operands_by_value.extend(operand_descs) + else: + pass + + parts = [] + parts.append("""// Operand descriptions, ordered by (operand kind, operand enum value). +// The fields in order are: +// enum value +// operands, an IndexRange into kOperandSpans +// name, a character-counting IndexRange into kStrings +// aliases, an IndexRange into kAliasSpans +// capabilities, an IndexRange into kCapabilitySpans +// extensions, as an IndexRange into kExtensionSpans +// version, first version of SPIR-V that has it +// lastVersion, last version of SPIR-V that has it""") + parts.append("std::array kOperandsByValue{{{{".format(len(operands_by_value))) + parts.extend([' ' + str(x) for x in operands_by_value]) + parts.append("}};\n") + self.body_decls.extend(parts) + + parts = [] + parts.append("""// 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. +// An optional variant of a kind maps to the details for the corresponding +// concrete operand kind.""") + parts.append("IndexRange OperandByValueRangeForKind(spv_operand_type_t type) {\n switch(type) {") + for kind_key, ir in operands_by_value_by_kind.items(): + parts.append(" case {}: return {};".format( + kind_key, + str(operands_by_value_by_kind[kind_key]))) + for kind in self.operand_kinds_needing_optional_variant: + parts.append(" case {}: return {};".format( + ctype(kind, '?'), + str(operands_by_value_by_kind[ctype(kind,'')]))) + parts.append(" default: break;"); + parts.append(" }\n return IR(0,0);\n}\n") + self.body_decls.extend(parts) + + + def ComputeInstructionTables(self, insts) -> None: + """ + Creates declarations for instruction tables. + Populates self.header_decls, self.body_decls. + + Params: + insts: an array of instructions objects using the JSON schema + """ + self.header_decls.append( +""" +// Describes an Instruction +struct InstructionDesc { + const spv::Op value; + const bool hasResult; + const bool hasType; + const IndexRange operands_range; // Indexes kOperandSpans + const IndexRange name_range; // Indexes kStrings + const IndexRange aliases_range; // Indexes kAliasSpans + const IndexRange capabilities_range; // Indexes kCapbilitySpans + // A set of extensions that enable this feature. If empty then this operand + // value is in core and its availability is subject to minVersion. The + // assembler, binary parser, and disassembler ignore this rule, so you can + // freely process invalid modules. + const IndexRange extensions_range; // Indexes kExtensionSpans + // Minimal core SPIR-V version required for this feature, if without + // extensions. ~0u means reserved for future use. ~0u and non-empty + // extension lists means only available in extensions. + uint32_t minVersion; + uint32_t lastVersion; + utils::Span operands() const; + utils::Span name() const; + utils::Span aliases() const; + utils::Span capabilities() const; + utils::Span extensions() const; + OperandDesc(const OperandDesc&) = delete; + OperandDesc(OperandDesc&&) = delete; +}; +""") + + # Create the sorted list of opcode strings, without the 'Op' prefix. + opcode_name_entries: list[str] = [] + name_value_pairs: list[tuple[str,int]] = [] + for i in insts: + name_value_pairs.append((i['opname'][2:], i['opcode'])) + for a in i.get('aliases',[]): + name_value_pairs.append((a[2:], i['opcode'])) + name_value_pairs = sorted(name_value_pairs) + inst_name_strings: list[str] = [] + for i in range(0, len(name_value_pairs)): + name, value = name_value_pairs[i] + ir = self.context.AddString(name) + inst_name_strings.append('{{{}, {}}}, // {} {}'.format(str(ir),value,i,name)) + parts: list[str] = [] + parts.append("""// Opcode strings (without the 'Op' prefix) and opcode values, ordered by name. +// The fields in order are: +// name, either the primary name or an alias, indexing into kStrings +// opcode value""") + parts.append("std::array kInstructionNames{{{{".format(len(inst_name_strings))) + parts.extend([' ' + str(x) for x in inst_name_strings]) + parts.append("}};\n") + self.body_decls.extend(parts) + + # Create the array of InstructionDesc + lines: list[str] = [] + for inst in insts: + parts: list[str] = [] + + opname: str = inst['opname'] + + operand_kinds = [convert_operand_kind(o) for o in inst.get('operands',[])] + if opname == 'OpExtInst' and operand_kinds[-1] == 'SPV_OPERAND_TYPE_VARIABLE_ID': + # The published grammar uses 'sequence of ID' at the + # end of the ExtInst operands. But SPIRV-Tools uses + # a specific pattern based on the particular opcode. + # Drop it here. + # See https://github.com/KhronosGroup/SPIRV-Tools/issues/233 + operand_kinds.pop() + + hasResult = 'SPV_OPERAND_TYPE_RESULT_ID' in operand_kinds + hasType = 'SPV_OPERAND_TYPE_TYPE_ID' in operand_kinds + + # Remove the "Op" prefix from opcode alias names + aliases = [name[2:] for name in inst.get('aliases',[])] + + parts.extend([ + 'spv::Op::' + opname, + c_bool(hasResult), + c_bool(hasType), + self.context.AddStringList('operand', operand_kinds), + self.context.AddString(opname[2:]), + self.context.AddStringList('alias', aliases), + self.context.AddStringList('capability', inst.get('capabilities',[])), + self.context.AddStringList('extension', inst.get('extensions',[])), + convert_min_required_version(inst.get('version', None)), + convert_max_required_version(inst.get('lastVersion', None)), + ]) + + lines.append('{{{}}},'.format(', '.join([str(x) for x in parts]))) + parts = [] + parts.append("""// Instruction descriptions, ordered by opcode. +// The fields in order are: +// opcode +// a boolean indicating if the instruction produces a result ID +// a boolean indicating if the instruction result ID has a type +// operands, an IndexRange into kOperandSpans +// opcode name (without the 'Op' prefix), a character-counting IndexRange into kStrings +// aliases, an IndexRange into kAliasSpans +// capabilities, an IndexRange into kCapabilitySpans +// extensions, as an IndexRange into kExtensionSpans +// version, first version of SPIR-V that has it +// lastVersion, last version of SPIR-V that has it""") + parts.append("std::array kInstructionDesc{{{{".format(len(lines))); + parts.extend([' ' + l for l in lines]) + parts.append("}};\n"); + self.body_decls.extend(parts) + + + def ComputeLeafTables(self) -> None: + """ + Generates the tables that the instruction and operand tables point to. + The tables are: + - the string table + - the table of sequences of: + - capabilities + - extensions + - operands + + This method must be called after computing instruction and operand tables. + """ + + def c_str(s: str): + """ + Returns the source for a C string literal or the given string, including + the explicit null at the end + """ + return '"{}\\0"'.format(json.dumps(s).strip('"')) + + parts: list[str] = [] + parts.append("// Array of characters, referenced by IndexRanges elsewhere.") + parts.append("// Each IndexRange denotes a string.") + parts.append('static const char kStrings[] ='); + parts.extend([' {} // {}'.format(c_str(s), str(self.context.strings[s])) for s in self.context.string_buffer]) + parts.append(';\n'); + self.body_decls.extend(parts); + + parts: list[str] = [] + parts.append("""// 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.""") + parts.append('static const IndexRange kAliasSpans[] = {'); + ranges = self.context.range_buffer['alias'] + for i in range(0, len(ranges)): + ir = ranges[i] + parts.append(' {}, // {} {}'.format(str(ir), i, self.context.GetString(ir))) + parts.append('};\n'); + self.body_decls.extend(parts); + + parts = [] + parts.append("// Array of capabilities, referenced by IndexRanges elsewhere.") + parts.append("// Contains all sequences of capabilities used in the grammar.") + parts.append('static const spv::Capability kCapabilitySpans[] = {'); + capability_ranges = self.context.range_buffer['capability'] + for i in range(0, len(capability_ranges)): + ir = capability_ranges[i] + cap = self.context.GetString(ir) + parts.append(' spv::Capability::{}, // {}'.format(cap, i)) + parts.append('};\n'); + self.body_decls.extend(parts); + + parts = [] + parts.append("// Array of extensions, referenced by IndexRanges elsewhere.") + parts.append("// Contains all sequences of extensions used in the grammar.") + parts.append('static const spvtools::Extension kExtensionSpans[] = {'); + ranges = self.context.range_buffer['extension'] + for i in range(0, len(ranges)): + ir = ranges[i] + name = self.context.GetString(ir) + parts.append(' spvtools::Extension::k{}, // {}'.format(name, i)) + parts.append('};\n'); + self.body_decls.extend(parts); + + parts = [] + parts.append("// Array of operand types, referenced by IndexRanges elsewhere.") + parts.append("// Contains all sequences of operand types used in the grammar.") + parts.append('static const spv_operand_type_t kOperandSpans[] = {'); + ranges = self.context.range_buffer['operand'] + for i in range(0, len(ranges)): + ir = ranges[i] + name = self.context.GetString(ir) + parts.append(' {}, // {}'.format(name, i)) + parts.append('};\n'); + self.body_decls.extend(parts); + + parts: list[str] = [] + parts.append("// Returns the name of an extension, as an index into kStrings") + parts.append("IndexRange ExtensionToIndexRange(Extension extension) {\n switch(extension) {") + for e in self.extensions: + parts.append(' case Extension::k{}: return {};'.format(e,self.context.AddString(e))) + parts.append(" default: break;"); + parts.append(' }\n return {};\n}\n'); + + self.body_decls.extend(parts) + + +def make_path_to_file(f: str) -> None: + """Makes all ancestor directories to the given file, if they don't yet + exist. + + Arguments: + f: The file whose ancestor directories are to be created. + """ + dir = os.path.dirname(os.path.abspath(f)) + try: + os.makedirs(dir) + except OSError as e: + if e.errno == errno.EEXIST and os.path.isdir(dir): + pass + else: + raise + + +def get_extension_list(instructions, operand_kinds): + """Returns extensions as an alphabetically sorted list of strings. + + Args: + instructions: list of instruction objects, using the JSON grammar file schema + operand_kinds: list of operand_kind objects, using the JSON grammar file schema + """ + + things_with_an_extensions_field = [item for item in instructions] + + enumerants = sum([item.get('enumerants', []) + for item in operand_kinds], []) + + things_with_an_extensions_field.extend(enumerants) + + extensions = sum([item.get('extensions', []) + for item in things_with_an_extensions_field + if item.get('extensions')], []) + + for item in EXTENSIONS_FROM_SPIRV_REGISTRY_AND_NOT_FROM_GRAMMARS.split(): + # If it's already listed in a grammar, then don't put it in the + # special exceptions list. + assert item not in extensions, 'Extension %s is already in a grammar file' % item + + extensions.extend( + EXTENSIONS_FROM_SPIRV_REGISTRY_AND_NOT_FROM_GRAMMARS.split()) + + # Validator would ignore type declaration unique check. Should only be used + # for legacy autogenerated test files containing multiple instances of the + # same type declaration, if fixing the test by other methods is too + # difficult. Shouldn't be used for any other reasons. + extensions.append('SPV_VALIDATOR_ignore_type_decl_unique') + + return sorted(set(extensions)) + + +def precondition_operand_kinds(operand_kinds): + """For operand kinds that have the same number, make sure they all have the + same extension list.""" + + # Map operand kind and value to list of the union of extensions + # for same-valued enumerants. + exts = {} + for kind_entry in operand_kinds: + kind = kind_entry.get('kind') + for enum_entry in kind_entry.get('enumerants', []): + value = enum_entry.get('value') + key = kind + '.' + str(value) + if key in exts: + exts[key].extend(enum_entry.get('extensions', [])) + else: + exts[key] = enum_entry.get('extensions', []) + exts[key] = sorted(set(exts[key])) + + # Now make each entry the same list. + for kind_entry in operand_kinds: + kind = kind_entry.get('kind') + for enum_entry in kind_entry.get('enumerants', []): + value = enum_entry.get('value') + key = kind + '.' + str(value) + if len(exts[key]) > 0: + enum_entry['extensions'] = exts[key] + + return operand_kinds + + +def prefix_operand_kind_names(prefix, json_dict): + """Modifies json_dict, by prefixing all the operand kind names + with the given prefix. Also modifies their uses in the instructions + to match. + """ + + old_to_new = {} + for operand_kind in json_dict["operand_kinds"]: + old_name = operand_kind["kind"] + new_name = prefix + old_name + operand_kind["kind"] = new_name + old_to_new[old_name] = new_name + + for instruction in json_dict["instructions"]: + for operand in instruction.get("operands", []): + replacement = old_to_new.get(operand["kind"]) + if replacement is not None: + operand["kind"] = replacement + + +def main(): + import argparse + parser = argparse.ArgumentParser(description='Generate SPIR-V info tables') + + parser.add_argument('--spirv-core-grammar', metavar='', + type=str, required=False, + help='input JSON grammar file for core SPIR-V ' + 'instructions') + parser.add_argument('--extinst-debuginfo-grammar', metavar='', + type=str, required=False, default=None, + help='input JSON grammar file for DebugInfo extended ' + 'instruction set') + parser.add_argument('--extinst-cldebuginfo100-grammar', metavar='', + type=str, required=False, default=None, + help='input JSON grammar file for OpenCL.DebugInfo.100 ' + 'extended instruction set') + parser.add_argument('--extinst-glsl-grammar', metavar='', + type=str, required=False, default=None, + help='input JSON grammar file for GLSL extended ' + 'instruction set') + parser.add_argument('--extinst-opencl-grammar', metavar='', + type=str, required=False, default=None, + help='input JSON grammar file for OpenCL extended ' + 'instruction set') + + parser.add_argument('--core-tables-output', metavar='', + type=str, required=False, default=None, + help='output file for core SPIR-V grammar tables') + parser.add_argument('--core-insts-output', metavar='', + type=str, required=False, default=None, + help='output file for core SPIR-V instructions') + parser.add_argument('--glsl-insts-output', metavar='', + type=str, required=False, default=None, + help='output file for GLSL extended instruction set') + parser.add_argument('--opencl-insts-output', metavar='', + type=str, required=False, default=None, + help='output file for OpenCL extended instruction set') + parser.add_argument('--operand-kinds-output', metavar='', + type=str, required=False, default=None, + help='output file for operand kinds') + parser.add_argument('--extension-enum-output', metavar='', + type=str, required=False, default=None, + help='output file for extension enumeration') + parser.add_argument('--enum-string-mapping-output', metavar='', + type=str, required=False, default=None, + help='output file for enum-string mappings') + parser.add_argument('--extinst-vendor-grammar', metavar='', + type=str, required=False, default=None, + help='input JSON grammar file for vendor extended ' + 'instruction set'), + parser.add_argument('--vendor-insts-output', metavar='', + type=str, required=False, default=None, + help='output file for vendor extended instruction set') + parser.add_argument('--vendor-operand-kind-prefix', metavar='', + type=str, required=False, default=None, + help='prefix for operand kinds (to disambiguate operand type enums)') + args = parser.parse_args() + + + # The GN build system needs this because it doesn't handle quoting + # empty string arguments well. + if args.vendor_operand_kind_prefix == "...nil...": + args.vendor_operand_kind_prefix = "" + + if (args.core_insts_output is None) != \ + (args.operand_kinds_output is None): + print('error: --core-insts-output and --operand-kinds-output ' + 'should be specified together.') + exit(1) + if args.operand_kinds_output and not (args.spirv_core_grammar and + args.extinst_debuginfo_grammar and + args.extinst_cldebuginfo100_grammar): + print('error: --operand-kinds-output requires --spirv-core-grammar ' + 'and --extinst-debuginfo-grammar ' + 'and --extinst-cldebuginfo100-grammar') + exit(1) + if (args.glsl_insts_output is None) != \ + (args.extinst_glsl_grammar is None): + print('error: --glsl-insts-output and --extinst-glsl-grammar ' + 'should be specified together.') + exit(1) + if (args.opencl_insts_output is None) != \ + (args.extinst_opencl_grammar is None): + print('error: --opencl-insts-output and --extinst-opencl-grammar ' + 'should be specified together.') + exit(1) + if (args.vendor_insts_output is None) != \ + (args.extinst_vendor_grammar is None): + print('error: --vendor-insts-output and ' + '--extinst-vendor-grammar should be specified together.') + exit(1) + if all([args.core_insts_output is None, + args.core_tables_output is None, + args.glsl_insts_output is None, + args.opencl_insts_output is None, + args.vendor_insts_output is None, + args.extension_enum_output is None, + args.enum_string_mapping_output is None]): + print('error: at least one output should be specified.') + exit(1) + + if args.spirv_core_grammar is not None: + # Populate instructions, extensions, operand_kinds list of json objects + with open(args.spirv_core_grammar) as json_file: + core_grammar = json.loads(json_file.read()) + with open(args.extinst_debuginfo_grammar) as debuginfo_json_file: + debuginfo_grammar = json.loads(debuginfo_json_file.read()) + with open(args.extinst_cldebuginfo100_grammar) as cldebuginfo100_json_file: + cldebuginfo100_grammar = json.loads(cldebuginfo100_json_file.read()) + prefix_operand_kind_names("CLDEBUG100_", cldebuginfo100_grammar) + instructions = [] + instructions.extend(core_grammar['instructions']) + instructions.extend(debuginfo_grammar['instructions']) + instructions.extend(cldebuginfo100_grammar['instructions']) + operand_kinds = [] + operand_kinds.extend(core_grammar['operand_kinds']) + operand_kinds.extend(debuginfo_grammar['operand_kinds']) + operand_kinds.extend(cldebuginfo100_grammar['operand_kinds']) + + extensions = get_extension_list(instructions, operand_kinds) + operand_kinds = precondition_operand_kinds(operand_kinds) + + g = Grammar(extensions, operand_kinds) + + g.ComputeOperandTables() + g.ComputeInstructionTables(core_grammar['instructions']) + g.ComputeLeafTables() + + if args.core_tables_output is not None: + make_path_to_file(args.core_tables_output) + with open(args.core_tables_output, 'w') as f: + f.write('\n'.join(g.body_decls)) + + +if __name__ == '__main__': + main()