Sync with NDK master.
diff --git a/CHANGES b/CHANGES
index 0e76412..653e6df 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,7 +1,28 @@
Revision history for SPIRV-Tools
-v2016.2-dev 2016-07-19
- - Start v2016.2
+v2016.3-dev 2016-08-05
+ - Start v2016.3
+ - Add target environment enums for OpenCL 2.1, OpenCL 2.2,
+ OpenGL 4.0, OpenGL 4.1, OpenGL 4.2, OpenGL 4.3, OpenGL 4.5.
+
+v2016.2 2016-08-05
+ - Validator is incomplete
+ - Checks ID use block is dominated by definition block
+ - Add optimization passes (in API and spirv-opt command)
+ - Strip debug info instructions
+ - Freeze spec constant to their default values
+ - Allow INotEqual as operation for OpSpecConstantOp
+ - Fixes bugs:
+ #270: validator: crash when continue construct is unreachable
+ #279: validator: infinite loop when analyzing some degenerate control
+ flow graphs
+ #286: validator: don't incorrectly generate def-use error for
+ (variable,parent) parameters to OpPhi
+ #290: disassembler: never generate bare % for an identifier
+ #295: validator: def-use dominance check should ignore unreachable uses
+ #276: validator: allow unreachable continue constructs
+ #297: validator: allow an unreachable block to branch to a reachable
+ merge block
v2016.1 2016-07-19
- Fix https://github.com/KhronosGroup/SPIRV-Tools/issues/261
diff --git a/CMakeLists.txt b/CMakeLists.txt
index bdb9f3b..bb9fe7d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -116,8 +116,8 @@
# But it still depends on MSVCRT.dll.
if (${CMAKE_SYSTEM_NAME} MATCHES "Windows")
if (${CMAKE_CXX_COMPILER_ID} MATCHES "GNU")
- set_target_properties(${TARGET} PROPERTIES LINK_FLAGS
- -static -static-libgcc -static-libstdc++)
+ set_target_properties(${TARGET} PROPERTIES
+ LINK_FLAGS -static -static-libgcc -static-libstdc++)
endif()
endif()
endfunction()
@@ -138,8 +138,13 @@
# Defaults to OFF if the user didn't set it.
option(SPIRV_SKIP_EXECUTABLES
- "Skip building the executable and tests along with the library"
- ${SPIRV_SKIP_EXECUTABLES})
+ "Skip building the executable and tests along with the library"
+ ${SPIRV_SKIP_EXECUTABLES})
+option(SPIRV_SKIP_TESTS
+ "Skip building tests along with the library" ${SPIRV_SKIP_TESTS})
+if ("${SPIRV_SKIP_EXECUTABLES}")
+ set(SPIRV_SKIP_TESTS ON)
+endif()
add_subdirectory(external)
diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h
index a3a1647..dc11c2a 100644
--- a/include/spirv-tools/libspirv.h
+++ b/include/spirv-tools/libspirv.h
@@ -352,6 +352,14 @@
SPV_ENV_UNIVERSAL_1_0, // SPIR-V 1.0 latest revision, no other restrictions.
SPV_ENV_VULKAN_1_0, // Vulkan 1.0 latest revision.
SPV_ENV_UNIVERSAL_1_1, // SPIR-V 1.1 latest revision, no other restrictions.
+ SPV_ENV_OPENCL_2_1, // OpenCL 2.1 latest revision.
+ SPV_ENV_OPENCL_2_2, // OpenCL 2.2 latest revision.
+ SPV_ENV_OPENGL_4_0, // OpenGL 4.0 plus GL_ARB_gl_spirv, latest revisions.
+ SPV_ENV_OPENGL_4_1, // OpenGL 4.1 plus GL_ARB_gl_spirv, latest revisions.
+ SPV_ENV_OPENGL_4_2, // OpenGL 4.2 plus GL_ARB_gl_spirv, latest revisions.
+ SPV_ENV_OPENGL_4_3, // OpenGL 4.3 plus GL_ARB_gl_spirv, latest revisions.
+ // There is no variant for OpenGL 4.4.
+ SPV_ENV_OPENGL_4_5, // OpenGL 4.5 plus GL_ARB_gl_spirv, latest revisions.
} spv_target_env;
// Returns a string describing the given SPIR-V target environment.
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index de227d6..316645e 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -103,11 +103,11 @@
PROPERTIES OBJECT_DEPENDS "${EXTINST_CPP_DEPENDS}")
set(SPIRV_TOOLS_BUILD_VERSION_INC
- ${spirv-tools_BINARY_DIR}/build-version.inc)
+ ${spirv-tools_BINARY_DIR}/build-version.inc)
set(SPIRV_TOOLS_BUILD_VERSION_INC_GENERATOR
- ${spirv-tools_SOURCE_DIR}/utils/update_build_version.py)
+ ${spirv-tools_SOURCE_DIR}/utils/update_build_version.py)
set(SPIRV_TOOLS_CHANGES_FILE
- ${spirv-tools_SOURCE_DIR}/CHANGES)
+ ${spirv-tools_SOURCE_DIR}/CHANGES)
add_custom_command(OUTPUT ${SPIRV_TOOLS_BUILD_VERSION_INC}
COMMAND ${PYTHON_EXECUTABLE}
${SPIRV_TOOLS_BUILD_VERSION_INC_GENERATOR}
@@ -167,10 +167,10 @@
${CMAKE_CURRENT_SOURCE_DIR}/validate_id.cpp
${CMAKE_CURRENT_SOURCE_DIR}/validate_instruction.cpp
${CMAKE_CURRENT_SOURCE_DIR}/validate_layout.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/validate_ssa.cpp
${CMAKE_CURRENT_SOURCE_DIR}/val/BasicBlock.cpp
${CMAKE_CURRENT_SOURCE_DIR}/val/Construct.cpp
${CMAKE_CURRENT_SOURCE_DIR}/val/Function.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/val/Id.cpp
${CMAKE_CURRENT_SOURCE_DIR}/val/ValidationState.cpp)
# The software_version.cpp file includes build-version.inc.
diff --git a/source/assembly_grammar.cpp b/source/assembly_grammar.cpp
index b85aa03..11f9bc3 100644
--- a/source/assembly_grammar.cpp
+++ b/source/assembly_grammar.cpp
@@ -150,6 +150,7 @@
CASE(Select),
// Comparison
CASE(IEqual),
+ CASE(INotEqual),
CASE(ULessThan),
CASE(SLessThan),
CASE(UGreaterThan),
@@ -165,8 +166,8 @@
CASE(InBoundsPtrAccessChain),
};
-// The 58 is determined by counting the opcodes listed in the spec.
-static_assert(58 == sizeof(kOpSpecConstantOpcodes)/sizeof(kOpSpecConstantOpcodes[0]),
+// The 59 is determined by counting the opcodes listed in the spec.
+static_assert(59 == sizeof(kOpSpecConstantOpcodes)/sizeof(kOpSpecConstantOpcodes[0]),
"OpSpecConstantOp opcode table is incomplete");
#undef CASE
// clang-format on
diff --git a/source/binary.h b/source/binary.h
index 923a834..0d9ef28 100644
--- a/source/binary.h
+++ b/source/binary.h
@@ -28,7 +28,8 @@
#define LIBSPIRV_BINARY_H_
#include "spirv-tools/libspirv.h"
-#include "table.h"
+#include "spirv/1.1/spirv.h"
+#include "spirv_definition.h"
// Functions
diff --git a/source/ext_inst.cpp b/source/ext_inst.cpp
index e92c99c..0851fcf 100644
--- a/source/ext_inst.cpp
+++ b/source/ext_inst.cpp
@@ -62,6 +62,13 @@
case SPV_ENV_UNIVERSAL_1_0:
case SPV_ENV_VULKAN_1_0:
case SPV_ENV_UNIVERSAL_1_1:
+ case SPV_ENV_OPENCL_2_1:
+ case SPV_ENV_OPENCL_2_2:
+ case SPV_ENV_OPENGL_4_0:
+ case SPV_ENV_OPENGL_4_1:
+ case SPV_ENV_OPENGL_4_2:
+ case SPV_ENV_OPENGL_4_3:
+ case SPV_ENV_OPENGL_4_5:
*pExtInstTable = &table_1_0;
return SPV_SUCCESS;
default:
diff --git a/source/instruction.h b/source/instruction.h
index 37b35b2..f203dd0 100644
--- a/source/instruction.h
+++ b/source/instruction.h
@@ -30,10 +30,9 @@
#include <cstdint>
#include <vector>
+#include "spirv-tools/libspirv.h"
#include "spirv/1.1/spirv.h"
-#include "table.h"
-
// Describes an instruction.
struct spv_instruction_t {
// Normally, both opcode and extInstType contain valid data.
diff --git a/source/name_mapper.cpp b/source/name_mapper.cpp
index f2c03ca..0dac99a 100644
--- a/source/name_mapper.cpp
+++ b/source/name_mapper.cpp
@@ -76,7 +76,8 @@
}
std::string FriendlyNameMapper::Sanitize(const std::string& suggested_name) {
- // Just replace invalid characters by '_'.
+ if (suggested_name.empty()) return "_";
+ // Otherwise, replace invalid characters by '_'.
std::string result;
std::string valid =
"abcdefghijklmnopqrstuvwxyz"
diff --git a/source/opcode.cpp b/source/opcode.cpp
index 22a0df6..2e6c7da 100644
--- a/source/opcode.cpp
+++ b/source/opcode.cpp
@@ -101,9 +101,16 @@
switch (env) {
case SPV_ENV_UNIVERSAL_1_0:
case SPV_ENV_VULKAN_1_0:
+ case SPV_ENV_OPENCL_2_1:
+ case SPV_ENV_OPENGL_4_0:
+ case SPV_ENV_OPENGL_4_1:
+ case SPV_ENV_OPENGL_4_2:
+ case SPV_ENV_OPENGL_4_3:
+ case SPV_ENV_OPENGL_4_5:
*pInstTable = &table_1_0;
return SPV_SUCCESS;
case SPV_ENV_UNIVERSAL_1_1:
+ case SPV_ENV_OPENCL_2_2:
*pInstTable = &table_1_1;
return SPV_SUCCESS;
}
diff --git a/source/operand.cpp b/source/operand.cpp
index a909380..b6dacc5 100644
--- a/source/operand.cpp
+++ b/source/operand.cpp
@@ -53,9 +53,16 @@
switch (env) {
case SPV_ENV_UNIVERSAL_1_0:
case SPV_ENV_VULKAN_1_0:
+ case SPV_ENV_OPENCL_2_1:
+ case SPV_ENV_OPENGL_4_0:
+ case SPV_ENV_OPENGL_4_1:
+ case SPV_ENV_OPENGL_4_2:
+ case SPV_ENV_OPENGL_4_3:
+ case SPV_ENV_OPENGL_4_5:
*pOperandTable = &table_1_0;
return SPV_SUCCESS;
case SPV_ENV_UNIVERSAL_1_1:
+ case SPV_ENV_OPENCL_2_2:
*pOperandTable = &table_1_1;
return SPV_SUCCESS;
}
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index 24a201d..b4e23e4 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -1,5 +1,6 @@
add_library(SPIRV-Tools-opt
basic_block.h
+ def_use_manager.h
function.h
instruction.h
ir_loader.h
@@ -9,6 +10,7 @@
passes.h
pass_manager.h
+ def_use_manager.cpp
function.cpp
instruction.cpp
ir_loader.cpp
diff --git a/source/opt/def_use_manager.cpp b/source/opt/def_use_manager.cpp
new file mode 100644
index 0000000..4775421
--- /dev/null
+++ b/source/opt/def_use_manager.cpp
@@ -0,0 +1,121 @@
+// Copyright (c) 2016 Google Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+// https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#include "def_use_manager.h"
+
+#include <cassert>
+#include <functional>
+
+#include "instruction.h"
+#include "module.h"
+
+namespace spvtools {
+namespace opt {
+namespace analysis {
+
+void DefUseManager::AnalyzeDefUse(ir::Module* module) {
+ module->ForEachInst(std::bind(&DefUseManager::AnalyzeInstDefUse, this,
+ std::placeholders::_1));
+}
+
+ir::Instruction* DefUseManager::GetDef(uint32_t id) {
+ if (id_to_def_.count(id) == 0) return nullptr;
+ return id_to_def_.at(id);
+}
+
+UseList* DefUseManager::GetUses(uint32_t id) {
+ if (id_to_uses_.count(id) == 0) return nullptr;
+ return &id_to_uses_.at(id);
+}
+
+bool DefUseManager::KillDef(uint32_t id) {
+ if (id_to_def_.count(id) == 0) return false;
+
+ // Go through all ids usd by this instruction, remove this instruction's uses
+ // of them.
+ for (const auto use_id : result_id_to_used_ids_[id]) {
+ if (id_to_uses_.count(use_id) == 0) continue;
+ auto& uses = id_to_uses_[use_id];
+ for (auto it = uses.begin(); it != uses.end();) {
+ if (it->inst->result_id() == id) {
+ it = uses.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ if (uses.empty()) id_to_uses_.erase(use_id);
+ }
+ result_id_to_used_ids_.erase(id);
+
+ id_to_uses_.erase(id); // Remove all uses of this id.
+ // This must happen at the last since we use information inside the instuction
+ // in the above.
+ id_to_def_[id]->ToNop();
+ id_to_def_.erase(id);
+ return true;
+}
+
+bool DefUseManager::ReplaceAllUsesWith(uint32_t before, uint32_t after) {
+ if (before == after) return false;
+ if (id_to_uses_.count(before) == 0) return false;
+
+ for (auto it = id_to_uses_[before].cbegin(); it != id_to_uses_[before].cend();
+ ++it) {
+ // Make the modification in the instruction.
+ it->inst->SetOperand(it->operand_index, {after});
+ // Register the use of |after| id into id_to_uses_.
+ // TODO(antiagainst): de-duplication.
+ id_to_uses_[after].push_back({it->inst, it->operand_index});
+ }
+ id_to_uses_.erase(before);
+ return true;
+}
+
+void DefUseManager::AnalyzeInstDefUse(ir::Instruction* inst) {
+ const uint32_t def_id = inst->result_id();
+ if (def_id != 0) id_to_def_[def_id] = inst;
+
+ for (uint32_t i = 0; i < inst->NumOperands(); ++i) {
+ switch (inst->GetOperand(i).type) {
+ // For any id type but result id type
+ case SPV_OPERAND_TYPE_ID:
+ case SPV_OPERAND_TYPE_TYPE_ID:
+ case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
+ case SPV_OPERAND_TYPE_SCOPE_ID: {
+ uint32_t use_id = inst->GetSingleWordOperand(i);
+ // use_id is used by the instruction generating def_id.
+ id_to_uses_[use_id].push_back({inst, i});
+ if (def_id != 0) result_id_to_used_ids_[def_id].push_back(use_id);
+ } break;
+ default:
+ break;
+ }
+ }
+}
+
+} // namespace analysis
+} // namespace opt
+} // namespace spvtools
diff --git a/source/opt/def_use_manager.h b/source/opt/def_use_manager.h
new file mode 100644
index 0000000..d7745d7
--- /dev/null
+++ b/source/opt/def_use_manager.h
@@ -0,0 +1,108 @@
+// Copyright (c) 2016 Google Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+// https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#ifndef LIBSPIRV_OPT_DEF_USE_MANAGER_H_
+#define LIBSPIRV_OPT_DEF_USE_MANAGER_H_
+
+#include <list>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "instruction.h"
+#include "module.h"
+
+namespace spvtools {
+namespace opt {
+namespace analysis {
+
+// Class for representing a use of id. Note that result:
+// * Type id is a use.
+// * Ids referenced in OpSectionMerge & OpLoopMerge are considered as use.
+// * Ids referenced in OpPhi's in operands are considered as use.
+struct Use {
+ ir::Instruction* inst; // Instruction using the id.
+ uint32_t operand_index; // logical operand index of the id use. This can be
+ // the index of result type id.
+};
+
+using UseList = std::list<Use>;
+
+// A class for analyzing and managing defs and uses in an ir::Module.
+class DefUseManager {
+ public:
+ using IdToDefMap = std::unordered_map<uint32_t, ir::Instruction*>;
+ using IdToUsesMap = std::unordered_map<uint32_t, UseList>;
+
+ // Analyzes the defs and uses in the given |module| and populates data
+ // structures in this class.
+ // TODO(antiagainst): This method should not modify the given module. Create
+ // const overload for ForEachInst().
+ void AnalyzeDefUse(ir::Module* module);
+
+ // Returns the def instruction for the given |id|. If there is no instruction
+ // defining |id|, returns nullptr.
+ ir::Instruction* GetDef(uint32_t id);
+ // Returns the use instructions for the given |id|. If there is no uses of
+ // |id|, returns nullptr.
+ UseList* GetUses(uint32_t id);
+
+ // Returns the map from ids to their def instructions.
+ const IdToDefMap& id_to_defs() const { return id_to_def_; }
+ // Returns the map from ids to their uses in instructions.
+ const IdToUsesMap& id_to_uses() const { return id_to_uses_; }
+
+ // Turns the instruction defining the given |id| into a Nop. Returns true on
+ // success, false if the given |id| is not defined at all. This method also
+ // erases both the uses of |id| and the |id|-generating instruction's use
+ // information kept in this manager, but not the operands in the original
+ // instructions.
+ bool KillDef(uint32_t id);
+ // Replaces all uses of |before| id with |after| id. Returns true if any
+ // replacement happens. This method does not kill the definition of the
+ // |before| id. If |after| is the same as |before|, does nothing and returns
+ // false.
+ bool ReplaceAllUsesWith(uint32_t before, uint32_t after);
+
+ private:
+ using ResultIdToUsedIdsMap =
+ std::unordered_map<uint32_t, std::vector<uint32_t>>;
+
+ // Analyzes the defs and uses in the given |inst|.
+ void AnalyzeInstDefUse(ir::Instruction* inst);
+
+ IdToDefMap id_to_def_; // Mapping from ids to their definitions
+ IdToUsesMap id_to_uses_; // Mapping from ids to their uses
+ // Mapping from result ids to the ids used in the instructions generating the
+ // result ids.
+ ResultIdToUsedIdsMap result_id_to_used_ids_;
+};
+
+} // namespace analysis
+} // namespace opt
+} // namespace spvtools
+
+#endif // LIBSPIRV_OPT_DEF_USE_MANAGER_H_
diff --git a/source/opt/instruction.h b/source/opt/instruction.h
index 0bbcd6e..18447ca 100644
--- a/source/opt/instruction.h
+++ b/source/opt/instruction.h
@@ -85,7 +85,18 @@
Instruction(const spv_parsed_instruction_t& inst,
std::vector<Instruction>&& dbg_line = {});
+ Instruction(const Instruction&) = default;
+ Instruction& operator=(const Instruction&) = default;
+
+ Instruction(Instruction&&) = default;
+ Instruction& operator=(Instruction&&) = default;
+
SpvOp opcode() const { return opcode_; }
+ // Sets the opcode of this instruction to a specific opcode. Note this may
+ // invalidate the instruction.
+ // TODO(qining): Remove this function when instruction building and insertion
+ // is well implemented.
+ void SetOpcode(SpvOp op) { opcode_ = op; }
uint32_t type_id() const { return type_id_; }
uint32_t result_id() const { return result_id_; }
// Returns the vector of line-related debug instructions attached to this
diff --git a/source/opt/libspirv.cpp b/source/opt/libspirv.cpp
index fe1b2b5..c076762 100644
--- a/source/opt/libspirv.cpp
+++ b/source/opt/libspirv.cpp
@@ -68,13 +68,12 @@
}
spv_result_t SpvTools::Disassemble(const std::vector<uint32_t>& binary,
- std::string* text) {
+ std::string* text, uint32_t options) {
spv_text spvtext = nullptr;
spv_diagnostic diagnostic = nullptr;
spv_result_t status = spvBinaryToText(context_, binary.data(), binary.size(),
- SPV_BINARY_TO_TEXT_OPTION_NO_HEADER,
- &spvtext, &diagnostic);
+ options, &spvtext, &diagnostic);
if (status == SPV_SUCCESS) {
text->assign(spvtext->str, spvtext->str + spvtext->length);
}
diff --git a/source/opt/libspirv.hpp b/source/opt/libspirv.hpp
index 667276f..e039c17 100644
--- a/source/opt/libspirv.hpp
+++ b/source/opt/libspirv.hpp
@@ -54,10 +54,14 @@
// Returns SPV_SUCCESS on successful assembling.
spv_result_t Assemble(const std::string& text, std::vector<uint32_t>* binary);
- // Disassembles the given SPIR-V |binary| and returns the assembly. Returns
- // SPV_SUCCESS on successful disassembling.
- spv_result_t Disassemble(const std::vector<uint32_t>& binary,
- std::string* text);
+ // Disassembles the given SPIR-V |binary| with the given options and returns
+ // the assembly. By default the options are set to generate assembly with
+ // friendly variable names and no SPIR-V assembly header. Returns SPV_SUCCESS
+ // on successful disassembling.
+ spv_result_t Disassemble(
+ const std::vector<uint32_t>& binary, std::string* text,
+ uint32_t options = SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
// Builds and returns a Module from the given SPIR-V |binary|.
std::unique_ptr<ir::Module> BuildModule(const std::vector<uint32_t>& binary);
diff --git a/source/opt/module.cpp b/source/opt/module.cpp
index c6bab3a..85d1f0e 100644
--- a/source/opt/module.cpp
+++ b/source/opt/module.cpp
@@ -43,7 +43,7 @@
for (auto& i : capabilities_) f(&i);
for (auto& i : extensions_) f(&i);
for (auto& i : ext_inst_imports_) f(&i);
- f(&memory_model_);
+ if (memory_model_) f(memory_model_.get());
for (auto& i : entry_points_) f(&i);
for (auto& i : execution_modes_) f(&i);
for (auto& i : debugs_) f(&i);
@@ -64,7 +64,7 @@
for (const auto& c : capabilities_) c.ToBinary(binary, skip_nop);
for (const auto& e : extensions_) e.ToBinary(binary, skip_nop);
for (const auto& e : ext_inst_imports_) e.ToBinary(binary, skip_nop);
- memory_model_.ToBinary(binary, skip_nop);
+ if (memory_model_) memory_model_->ToBinary(binary, skip_nop);
for (const auto& e : entry_points_) e.ToBinary(binary, skip_nop);
for (const auto& e : execution_modes_) e.ToBinary(binary, skip_nop);
for (const auto& d : debugs_) d.ToBinary(binary, skip_nop);
diff --git a/source/opt/module.h b/source/opt/module.h
index f4bfbbc..df02dbd 100644
--- a/source/opt/module.h
+++ b/source/opt/module.h
@@ -28,6 +28,7 @@
#define LIBSPIRV_OPT_MODULE_H_
#include <functional>
+#include <memory>
#include <utility>
#include <vector>
@@ -63,8 +64,10 @@
void AddExtInstImport(Instruction&& e) {
ext_inst_imports_.push_back(std::move(e));
}
- // Appends a memory model instruction to this module.
- void SetMemoryModel(Instruction&& m) { memory_model_ = std::move(m); }
+ // Adds a memory model instruction to this module.
+ void SetMemoryModel(Instruction&& m) {
+ memory_model_.reset(new Instruction(std::move(m)));
+ }
// Appends an entry point instruction to this module.
void AddEntryPoint(Instruction&& e) { entry_points_.push_back(std::move(e)); }
// Appends an execution mode instruction to this module.
@@ -113,7 +116,8 @@
std::vector<Instruction> capabilities_;
std::vector<Instruction> extensions_;
std::vector<Instruction> ext_inst_imports_;
- Instruction memory_model_; // A module only has one memory model instruction.
+ std::unique_ptr<Instruction>
+ memory_model_; // A module only has one memory model instruction.
std::vector<Instruction> entry_points_;
std::vector<Instruction> execution_modes_;
std::vector<Instruction> debugs_;
diff --git a/source/opt/passes.cpp b/source/opt/passes.cpp
index 3749c53..65ab769 100644
--- a/source/opt/passes.cpp
+++ b/source/opt/passes.cpp
@@ -41,5 +41,35 @@
return modified;
}
+bool FreezeSpecConstantValuePass::Process(ir::Module* module) {
+ bool modified = false;
+ module->ForEachInst([&modified](ir::Instruction* inst) {
+ switch (inst->opcode()) {
+ case SpvOp::SpvOpSpecConstant:
+ inst->SetOpcode(SpvOp::SpvOpConstant);
+ modified = true;
+ break;
+ case SpvOp::SpvOpSpecConstantTrue:
+ inst->SetOpcode(SpvOp::SpvOpConstantTrue);
+ modified = true;
+ break;
+ case SpvOp::SpvOpSpecConstantFalse:
+ inst->SetOpcode(SpvOp::SpvOpConstantFalse);
+ modified = true;
+ break;
+ case SpvOp::SpvOpDecorate:
+ if (inst->GetSingleWordInOperand(1) ==
+ SpvDecoration::SpvDecorationSpecId) {
+ inst->ToNop();
+ modified = true;
+ }
+ break;
+ default:
+ break;
+ }
+ });
+ return modified;
+}
+
} // namespace opt
} // namespace spvtools
diff --git a/source/opt/passes.h b/source/opt/passes.h
index cb07e0b..23873a8 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -46,7 +46,7 @@
// A null pass that does nothing.
class NullPass : public Pass {
- const char* name() const override { return "Null"; }
+ const char* name() const override { return "null"; }
bool Process(ir::Module*) override { return false; }
};
@@ -54,10 +54,24 @@
// Section 3.32.2 of the SPIR-V spec).
class StripDebugInfoPass : public Pass {
public:
- const char* name() const override { return "StripDebugInfo"; }
+ const char* name() const override { return "strip-debug"; }
bool Process(ir::Module* module) override;
};
+// The transformation pass that specializes the value of spec constants to
+// their default values. This pass only processes the spec constants that have
+// Spec ID decorations (defined by OpSpecConstant, OpSpecConstantTrue and
+// OpSpecConstantFalse instructions) and replaces them with their front-end
+// version counterparts (OpConstant, OpConstantTrue and OpConstantFalse). The
+// corresponding Spec ID annotation instructions will also be removed. This
+// pass does not fold the newly added front-end constants and does not process
+// other spec constants defined by OpSpecConstantComposite or OpSpecConstantOp.
+class FreezeSpecConstantValuePass : public Pass {
+ public:
+ const char* name() const override { return "freeze-spec-const"; }
+ bool Process(ir::Module*) override;
+};
+
} // namespace opt
} // namespace spvtools
diff --git a/source/spirv_definition.h b/source/spirv_definition.h
index 59f8509..9cd3f36 100644
--- a/source/spirv_definition.h
+++ b/source/spirv_definition.h
@@ -29,7 +29,7 @@
#include <cstdint>
-#include "spirv-tools/libspirv.h"
+#include "spirv/1.1/spirv.h"
#define spvIsInBitfield(value, bitfield) ((value) == ((value)&bitfield))
diff --git a/source/spirv_target_env.cpp b/source/spirv_target_env.cpp
index 116d6b5..e95ce9e 100644
--- a/source/spirv_target_env.cpp
+++ b/source/spirv_target_env.cpp
@@ -38,8 +38,20 @@
return "SPIR-V 1.0 (under Vulkan 1.0 semantics)";
case SPV_ENV_UNIVERSAL_1_1:
return "SPIR-V 1.1";
- default:
- break;
+ case SPV_ENV_OPENCL_2_1:
+ return "SPIR-V 1.0 (under OpenCL 2.1 semantics)";
+ case SPV_ENV_OPENCL_2_2:
+ return "SPIR-V 1.1 (under OpenCL 2.2 semantics)";
+ case SPV_ENV_OPENGL_4_0:
+ return "SPIR-V 1.0 (under OpenCL 4.0 semantics)";
+ case SPV_ENV_OPENGL_4_1:
+ return "SPIR-V 1.0 (under OpenCL 4.1 semantics)";
+ case SPV_ENV_OPENGL_4_2:
+ return "SPIR-V 1.0 (under OpenCL 4.2 semantics)";
+ case SPV_ENV_OPENGL_4_3:
+ return "SPIR-V 1.0 (under OpenCL 4.3 semantics)";
+ case SPV_ENV_OPENGL_4_5:
+ return "SPIR-V 1.0 (under OpenCL 4.5 semantics)";
}
assert(0 && "Unhandled SPIR-V target environment");
return "";
@@ -49,26 +61,55 @@
switch (env) {
case SPV_ENV_UNIVERSAL_1_0:
case SPV_ENV_VULKAN_1_0:
+ case SPV_ENV_OPENCL_2_1:
+ case SPV_ENV_OPENGL_4_0:
+ case SPV_ENV_OPENGL_4_1:
+ case SPV_ENV_OPENGL_4_2:
+ case SPV_ENV_OPENGL_4_3:
+ case SPV_ENV_OPENGL_4_5:
return SPV_SPIRV_VERSION_WORD(1, 0);
case SPV_ENV_UNIVERSAL_1_1:
+ case SPV_ENV_OPENCL_2_2:
return SPV_SPIRV_VERSION_WORD(1, 1);
- default:
- break;
}
assert(0 && "Unhandled SPIR-V target environment");
return SPV_SPIRV_VERSION_WORD(0, 0);
}
bool spvParseTargetEnv(const char* s, spv_target_env* env) {
- if (!strncmp(s, "vulkan1.0", strlen("vulkan1.0"))) {
+ auto match = [s](const char* b) {
+ return s && (0 == strncmp(s, b, strlen(b)));
+ };
+ if (match("vulkan1.0")) {
if (env) *env = SPV_ENV_VULKAN_1_0;
return true;
- } else if (!strncmp(s, "spv1.0", strlen("spv1.0"))) {
+ } else if (match("spv1.0")) {
if (env) *env = SPV_ENV_UNIVERSAL_1_0;
return true;
- } else if (!strncmp(s, "spv1.1", strlen("spv1.1"))) {
+ } else if (match("spv1.1")) {
if (env) *env = SPV_ENV_UNIVERSAL_1_1;
return true;
+ } else if (match("opencl2.1")) {
+ if (env) *env = SPV_ENV_OPENCL_2_1;
+ return true;
+ } else if (match("opencl2.2")) {
+ if (env) *env = SPV_ENV_OPENCL_2_2;
+ return true;
+ } else if (match("opengl4.0")) {
+ if (env) *env = SPV_ENV_OPENGL_4_0;
+ return true;
+ } else if (match("opengl4.1")) {
+ if (env) *env = SPV_ENV_OPENGL_4_1;
+ return true;
+ } else if (match("opengl4.2")) {
+ if (env) *env = SPV_ENV_OPENGL_4_2;
+ return true;
+ } else if (match("opengl4.3")) {
+ if (env) *env = SPV_ENV_OPENGL_4_3;
+ return true;
+ } else if (match("opengl4.5")) {
+ if (env) *env = SPV_ENV_OPENGL_4_5;
+ return true;
} else {
if (env) *env = SPV_ENV_UNIVERSAL_1_0;
return false;
diff --git a/source/table.cpp b/source/table.cpp
index 3b142a2..2312ceb 100644
--- a/source/table.cpp
+++ b/source/table.cpp
@@ -33,6 +33,13 @@
case SPV_ENV_UNIVERSAL_1_0:
case SPV_ENV_VULKAN_1_0:
case SPV_ENV_UNIVERSAL_1_1:
+ case SPV_ENV_OPENCL_2_1:
+ case SPV_ENV_OPENCL_2_2:
+ case SPV_ENV_OPENGL_4_0:
+ case SPV_ENV_OPENGL_4_1:
+ case SPV_ENV_OPENGL_4_2:
+ case SPV_ENV_OPENGL_4_3:
+ case SPV_ENV_OPENGL_4_5:
break;
default:
return nullptr;
diff --git a/source/val/BasicBlock.cpp b/source/val/BasicBlock.cpp
index eb54f2a..3e95618 100644
--- a/source/val/BasicBlock.cpp
+++ b/source/val/BasicBlock.cpp
@@ -26,6 +26,7 @@
#include "BasicBlock.h"
+#include <algorithm>
#include <utility>
#include <vector>
@@ -76,12 +77,16 @@
return;
}
-void BasicBlock::SetSuccessorsUnsafe(std::vector<BasicBlock*>&& others) {
- successors_ = std::move(others);
+bool BasicBlock::dominates(const BasicBlock& other) const {
+ return (this == &other) ||
+ !(other.dom_end() ==
+ std::find(other.dom_begin(), other.dom_end(), this));
}
-void BasicBlock::SetPredecessorsUnsafe(std::vector<BasicBlock*>&& others) {
- predecessors_ = std::move(others);
+bool BasicBlock::postdominates(const BasicBlock& other) const {
+ return (this == &other) ||
+ !(other.pdom_end() ==
+ std::find(other.pdom_begin(), other.pdom_end(), this));
}
BasicBlock::DominatorIterator::DominatorIterator() : current_(nullptr) {}
diff --git a/source/val/BasicBlock.h b/source/val/BasicBlock.h
index 9be655c..21b0e39 100644
--- a/source/val/BasicBlock.h
+++ b/source/val/BasicBlock.h
@@ -121,20 +121,20 @@
/// Adds @p next BasicBlocks as successors of this BasicBlock
void RegisterSuccessors(const std::vector<BasicBlock*>& next = {});
- /// Set the successors to this block, without updating other internal state,
- /// and without updating the other blocks.
- void SetSuccessorsUnsafe(std::vector<BasicBlock*>&& others);
-
- /// Set the predecessors to this block, without updating other internal state,
- /// and without updating the other blocks.
- void SetPredecessorsUnsafe(std::vector<BasicBlock*>&& others);
-
/// Returns true if the id of the BasicBlock matches
bool operator==(const BasicBlock& other) const { return other.id_ == id_; }
/// Returns true if the id of the BasicBlock matches
bool operator==(const uint32_t& other_id) const { return other_id == id_; }
+ /// Returns true if this block dominates the other block.
+ /// Assumes dominators have been computed.
+ bool dominates(const BasicBlock& other) const;
+
+ /// Returns true if this block postdominates the other block.
+ /// Assumes dominators have been computed.
+ bool postdominates(const BasicBlock& other) const;
+
/// @brief A BasicBlock dominator iterator class
///
/// This iterator will iterate over the (post)dominators of the block
diff --git a/source/val/Function.cpp b/source/val/Function.cpp
index d13915e..0240d68 100644
--- a/source/val/Function.cpp
+++ b/source/val/Function.cpp
@@ -29,42 +29,79 @@
#include <cassert>
#include <algorithm>
+#include <unordered_set>
+#include <unordered_map>
#include <utility>
#include "val/BasicBlock.h"
#include "val/Construct.h"
-#include "val/ValidationState.h"
+#include "validate.h"
using std::ignore;
using std::list;
using std::make_pair;
using std::pair;
-using std::string;
using std::tie;
using std::vector;
-namespace libspirv {
namespace {
-void printDot(const BasicBlock& other, const ValidationState_t& module) {
- string block_string;
- if (other.successors()->empty()) {
- block_string += "end ";
- } else {
- for (auto block : *other.successors()) {
- block_string += module.getIdOrName(block->id()) + " ";
+using libspirv::BasicBlock;
+
+// Computes a minimal set of root nodes required to traverse, in the forward
+// direction, the CFG represented by the given vector of blocks, and successor
+// and predecessor functions. When considering adding two nodes, each having
+// predecessors, favour using the one that appears earlier on the input blocks
+// list.
+std::vector<BasicBlock*> TraversalRoots(const std::vector<BasicBlock*>& blocks,
+ libspirv::get_blocks_func succ_func,
+ libspirv::get_blocks_func pred_func) {
+ // The set of nodes which have been visited from any of the roots so far.
+ std::unordered_set<const BasicBlock*> visited;
+
+ auto mark_visited = [&visited](const BasicBlock* b) { visited.insert(b); };
+ auto ignore_block = [](const BasicBlock*) {};
+ auto ignore_blocks = [](const BasicBlock*, const BasicBlock*) {};
+
+ auto traverse_from_root = [&mark_visited, &succ_func, &ignore_block,
+ &ignore_blocks](const BasicBlock* entry) {
+ DepthFirstTraversal(entry, succ_func, mark_visited, ignore_block,
+ ignore_blocks);
+ };
+
+ std::vector<BasicBlock*> result;
+
+ // First collect nodes without predecessors.
+ for (auto block : blocks) {
+ if (pred_func(block)->empty()) {
+ assert(visited.count(block) == 0 && "Malformed graph!");
+ result.push_back(block);
+ traverse_from_root(block);
}
}
- printf("%10s -> {%s\b}\n", module.getIdOrName(other.id()).c_str(),
- block_string.c_str());
+
+ // Now collect other stranded nodes. These must be in unreachable cycles.
+ for (auto block : blocks) {
+ if (visited.count(block) == 0) {
+ result.push_back(block);
+ traverse_from_root(block);
+ }
+ }
+
+ return result;
}
-} /// namespace
+
+} // anonymous namespace
+
+namespace libspirv {
+
+// Universal Limit of ResultID + 1
+static const uint32_t kInvalidId = 0x400000;
Function::Function(uint32_t function_id, uint32_t result_type_id,
SpvFunctionControlMask function_control,
- uint32_t function_type_id, ValidationState_t& module)
- : module_(module),
- id_(function_id),
+ uint32_t function_type_id)
+ : id_(function_id),
function_type_id_(function_type_id),
result_type_id_(result_type_id),
function_control_(function_control),
@@ -74,8 +111,6 @@
current_block_(nullptr),
pseudo_entry_block_(0),
pseudo_exit_block_(kInvalidId),
- pseudo_entry_blocks_({&pseudo_entry_block_}),
- pseudo_exit_blocks_({&pseudo_exit_block_}),
cfg_constructs_(),
variable_ids_(),
parameter_ids_() {}
@@ -86,9 +121,6 @@
spv_result_t Function::RegisterFunctionParameter(uint32_t parameter_id,
uint32_t type_id) {
- assert(module_.in_function_body() == true &&
- "RegisterFunctionParameter can only be called when parsing the binary "
- "outside of another function");
assert(current_block_ == nullptr &&
"RegisterFunctionParameter can only be called when parsing the binary "
"ouside of a block");
@@ -104,18 +136,17 @@
RegisterBlock(merge_id, false);
RegisterBlock(continue_id, false);
BasicBlock& merge_block = blocks_.at(merge_id);
- BasicBlock& continue_block = blocks_.at(continue_id);
+ BasicBlock& continue_target_block = blocks_.at(continue_id);
assert(current_block_ &&
"RegisterLoopMerge must be called when called within a block");
current_block_->set_type(kBlockTypeLoop);
merge_block.set_type(kBlockTypeMerge);
- continue_block.set_type(kBlockTypeContinue);
- cfg_constructs_.emplace_back(ConstructType::kLoop, current_block_,
- &merge_block);
- Construct& loop_construct = cfg_constructs_.back();
- cfg_constructs_.emplace_back(ConstructType::kContinue, &continue_block);
- Construct& continue_construct = cfg_constructs_.back();
+ continue_target_block.set_type(kBlockTypeContinue);
+ Construct& loop_construct =
+ AddConstruct({ConstructType::kLoop, current_block_, &merge_block});
+ Construct& continue_construct =
+ AddConstruct({ConstructType::kContinue, &continue_target_block});
continue_construct.set_corresponding_constructs({&loop_construct});
loop_construct.set_corresponding_constructs({&continue_construct});
@@ -128,30 +159,11 @@
current_block_->set_type(kBlockTypeHeader);
merge_block.set_type(kBlockTypeMerge);
- cfg_constructs_.emplace_back(ConstructType::kSelection, current_block(),
- &merge_block);
+ AddConstruct({ConstructType::kSelection, current_block(), &merge_block});
+
return SPV_SUCCESS;
}
-void Function::PrintDotGraph() const {
- if (first_block()) {
- string func_name(module_.getIdOrName(id_));
- printf("digraph %s {\n", func_name.c_str());
- PrintBlocks();
- printf("}\n");
- }
-}
-
-void Function::PrintBlocks() const {
- if (first_block()) {
- printf("%10s -> %s\n", module_.getIdOrName(id_).c_str(),
- module_.getIdOrName(first_block()->id()).c_str());
- for (const auto& block : blocks_) {
- printDot(block.second, module_);
- }
- }
-}
-
spv_result_t Function::RegisterSetFunctionDeclType(FunctionDecl type) {
assert(declaration_type_ == FunctionDecl::kFunctionDeclUnknown);
declaration_type_ = type;
@@ -159,12 +171,6 @@
}
spv_result_t Function::RegisterBlock(uint32_t block_id, bool is_definition) {
- assert(module_.in_function_body() == true &&
- "RegisterBlocks can only be called when parsing a binary inside of a "
- "function");
- assert(module_.current_layout_section() !=
- ModuleLayoutSection::kLayoutFunctionDeclarations &&
- "RegisterBlocks cannot be called within a function declaration");
assert(
declaration_type_ == FunctionDecl::kFunctionDeclDefinition &&
"RegisterBlocks can only be called after declaration_type_ is defined");
@@ -191,13 +197,9 @@
void Function::RegisterBlockEnd(vector<uint32_t> next_list,
SpvOp branch_instruction) {
- assert(module_.in_function_body() == true &&
- "RegisterBlockEnd can only be called when parsing a binary in a "
- "function");
assert(
current_block_ &&
"RegisterBlockEnd can only be called when parsing a binary in a block");
-
vector<BasicBlock*> next_blocks;
next_blocks.reserve(next_list.size());
@@ -212,6 +214,22 @@
next_blocks.push_back(&inserted_block->second);
}
+ if (current_block_->is_type(kBlockTypeLoop)) {
+ // For each loop header, record the set of its successors, and include
+ // its continue target if the continue target is not the loop header
+ // itself.
+ std::vector<BasicBlock*>& next_blocks_plus_continue_target =
+ loop_header_successors_plus_continue_target_map_[current_block_];
+ next_blocks_plus_continue_target = next_blocks;
+ auto continue_target = FindConstructForEntryBlock(current_block_)
+ .corresponding_constructs()
+ .back()
+ ->entry_block();
+ if (continue_target != current_block_) {
+ next_blocks_plus_continue_target.push_back(continue_target);
+ }
+ }
+
current_block_->RegisterBranchInstruction(branch_instruction);
current_block_->RegisterSuccessors(next_blocks);
current_block_ = nullptr;
@@ -222,16 +240,7 @@
if (!end_has_been_registered_) {
end_has_been_registered_ = true;
- // Compute the successors of the pseudo-entry block, and
- // the predecessors of the pseudo exit block.
- vector<BasicBlock*> sources;
- vector<BasicBlock*> sinks;
- for (const auto b : ordered_blocks_) {
- if (b->predecessors()->empty()) sources.push_back(b);
- if (b->successors()->empty()) sinks.push_back(b);
- }
- pseudo_entry_block_.SetSuccessorsUnsafe(std::move(sources));
- pseudo_exit_block_.SetPredecessorsUnsafe(std::move(sinks));
+ ComputeAugmentedCFG();
}
}
@@ -249,16 +258,6 @@
const BasicBlock* Function::current_block() const { return current_block_; }
BasicBlock* Function::current_block() { return current_block_; }
-BasicBlock* Function::pseudo_entry_block() { return &pseudo_entry_block_; }
-const BasicBlock* Function::pseudo_entry_block() const {
- return &pseudo_entry_block_;
-}
-
-BasicBlock* Function::pseudo_exit_block() { return &pseudo_exit_block_; }
-const BasicBlock* Function::pseudo_exit_block() const {
- return &pseudo_exit_block_;
-}
-
const list<Construct>& Function::constructs() const { return cfg_constructs_; }
list<Construct>& Function::constructs() { return cfg_constructs_; }
@@ -299,4 +298,89 @@
tie(out, defined) = const_cast<const Function*>(this)->GetBlock(block_id);
return make_pair(const_cast<BasicBlock*>(out), defined);
}
+
+Function::GetBlocksFunction Function::AugmentedCFGSuccessorsFunction() const {
+ return [this](const BasicBlock* block) {
+ auto where = augmented_successors_map_.find(block);
+ return where == augmented_successors_map_.end() ? block->successors()
+ : &(*where).second;
+ };
+}
+
+Function::GetBlocksFunction
+Function::AugmentedCFGSuccessorsFunctionIncludingHeaderToContinueEdge() const {
+ return [this](const BasicBlock* block) {
+ auto where = loop_header_successors_plus_continue_target_map_.find(block);
+ return where == loop_header_successors_plus_continue_target_map_.end()
+ ? AugmentedCFGSuccessorsFunction()(block)
+ : &(*where).second;
+ };
+}
+
+Function::GetBlocksFunction Function::AugmentedCFGPredecessorsFunction() const {
+ return [this](const BasicBlock* block) {
+ auto where = augmented_predecessors_map_.find(block);
+ return where == augmented_predecessors_map_.end() ? block->predecessors()
+ : &(*where).second;
+ };
+}
+
+void Function::ComputeAugmentedCFG() {
+ // Compute the successors of the pseudo-entry block, and
+ // the predecessors of the pseudo exit block.
+ auto succ_func = [](const BasicBlock* b) { return b->successors(); };
+ auto pred_func = [](const BasicBlock* b) { return b->predecessors(); };
+ auto sources = TraversalRoots(ordered_blocks_, succ_func, pred_func);
+
+ // For the predecessor traversals, reverse the order of blocks. This
+ // will affect the post-dominance calculation as follows:
+ // - Suppose you have blocks A and B, with A appearing before B in
+ // the list of blocks.
+ // - Also, A branches only to B, and B branches only to A.
+ // - We want to compute A as dominating B, and B as post-dominating B.
+ // By using reversed blocks for predecessor traversal roots discovery,
+ // we'll add an edge from B to the pseudo-exit node, rather than from A.
+ // All this is needed to correctly process the dominance/post-dominance
+ // constraint when A is a loop header that points to itself as its
+ // own continue target, and B is the latch block for the loop.
+ std::vector<BasicBlock*> reversed_blocks(ordered_blocks_.rbegin(),
+ ordered_blocks_.rend());
+ auto sinks = TraversalRoots(reversed_blocks, pred_func, succ_func);
+
+ // Wire up the pseudo entry block.
+ augmented_successors_map_[&pseudo_entry_block_] = sources;
+ for (auto block : sources) {
+ auto& augmented_preds = augmented_predecessors_map_[block];
+ const auto& preds = *block->predecessors();
+ augmented_preds.reserve(1 + preds.size());
+ augmented_preds.push_back(&pseudo_entry_block_);
+ augmented_preds.insert(augmented_preds.end(), preds.begin(), preds.end());
+ }
+
+ // Wire up the pseudo exit block.
+ augmented_predecessors_map_[&pseudo_exit_block_] = sinks;
+ for (auto block : sinks) {
+ auto& augmented_succ = augmented_successors_map_[block];
+ const auto& succ = *block->successors();
+ augmented_succ.reserve(1 + succ.size());
+ augmented_succ.push_back(&pseudo_exit_block_);
+ augmented_succ.insert(augmented_succ.end(), succ.begin(), succ.end());
+ }
+};
+
+Construct& Function::AddConstruct(const Construct& new_construct) {
+ cfg_constructs_.push_back(new_construct);
+ auto& result = cfg_constructs_.back();
+ entry_block_to_construct_[new_construct.entry_block()] = &result;
+ return result;
+}
+
+Construct& Function::FindConstructForEntryBlock(const BasicBlock* entry_block) {
+ auto where = entry_block_to_construct_.find(entry_block);
+ assert(where != entry_block_to_construct_.end());
+ auto construct_ptr = (*where).second;
+ assert(construct_ptr);
+ return *construct_ptr;
+}
+
} /// namespace libspirv
diff --git a/source/val/Function.h b/source/val/Function.h
index a8175e9..1fbe113 100644
--- a/source/val/Function.h
+++ b/source/val/Function.h
@@ -28,6 +28,7 @@
#define LIBSPIRV_VAL_FUNCTION_H_
#include <list>
+#include <functional>
#include <unordered_map>
#include <unordered_set>
#include <vector>
@@ -35,6 +36,7 @@
#include "spirv-tools/libspirv.h"
#include "spirv/1.1/spirv.h"
#include "val/BasicBlock.h"
+#include "val/Construct.h"
namespace libspirv {
@@ -44,17 +46,13 @@
kFunctionDeclDefinition /// < Function definition
};
-class Construct;
-class ValidationState_t;
-
/// This class manages all function declaration and definitions in a module. It
/// handles the state and id information while parsing a function in the SPIR-V
/// binary.
class Function {
public:
Function(uint32_t id, uint32_t result_type_id,
- SpvFunctionControlMask function_control, uint32_t function_type_id,
- ValidationState_t& module);
+ SpvFunctionControlMask function_control, uint32_t function_type_id);
/// Registers a function parameter in the current function
/// @return Returns SPV_SUCCESS if the call was successful
@@ -152,31 +150,41 @@
/// Returns the block that is currently being parsed in the binary
const BasicBlock* current_block() const;
- /// Returns the pseudo exit block
- BasicBlock* pseudo_entry_block();
+ // For dominance calculations, we want to analyze all the
+ // blocks in the function, even in degenerate control flow cases
+ // including unreachable blocks. We therefore make an "augmented CFG"
+ // which is the same as the ordinary CFG but adds:
+ // - A pseudo-entry node.
+ // - A pseudo-exit node.
+ // - A minimal set of edges so that a forward traversal from the
+ // pseudo-entry node will visit all nodes.
+ // - A minimal set of edges so that a backward traversal from the
+ // pseudo-exit node will visit all nodes.
+ // In particular, the pseudo-entry node is the unique source of the
+ // augmented CFG, and the psueo-exit node is the unique sink of the
+ // augmented CFG.
/// Returns the pseudo exit block
- const BasicBlock* pseudo_entry_block() const;
+ BasicBlock* pseudo_entry_block() { return &pseudo_entry_block_; }
/// Returns the pseudo exit block
- BasicBlock* pseudo_exit_block();
+ const BasicBlock* pseudo_entry_block() const { return &pseudo_entry_block_; }
/// Returns the pseudo exit block
- const BasicBlock* pseudo_exit_block() const;
+ BasicBlock* pseudo_exit_block() { return &pseudo_exit_block_; }
- /// Returns a vector with just the pseudo entry block.
- /// This serves as the predecessors of each source node in the CFG when
- /// computing dominators.
- const std::vector<BasicBlock*>* pseudo_entry_blocks() const {
- return &pseudo_entry_blocks_;
- }
+ /// Returns the pseudo exit block
+ const BasicBlock* pseudo_exit_block() const { return &pseudo_exit_block_; }
- /// Returns a vector with just the pseudo exit block.
- /// This serves as the successors of each sink node in the CFG when computing
- /// dominators.
- const std::vector<BasicBlock*>* pseudo_exit_blocks() const {
- return &pseudo_exit_blocks_;
- }
+ using GetBlocksFunction =
+ std::function<const std::vector<BasicBlock*>*(const BasicBlock*)>;
+ /// Returns the block successors function for the augmented CFG.
+ GetBlocksFunction AugmentedCFGSuccessorsFunction() const;
+ /// Like AugmentedCFGSuccessorsFunction, but also includes a forward edge from
+ /// a loop header block to its continue target, if they are different blocks.
+ GetBlocksFunction AugmentedCFGSuccessorsFunctionIncludingHeaderToContinueEdge() const;
+ /// Returns the block predecessors function for the augmented CFG.
+ GetBlocksFunction AugmentedCFGPredecessorsFunction() const;
/// Prints a GraphViz digraph of the CFG of the current funciton
void PrintDotGraph() const;
@@ -185,8 +193,17 @@
void PrintBlocks() const;
private:
- /// Parent module
- ValidationState_t& module_;
+ // Computes the representation of the augmented CFG.
+ // Populates augmented_successors_map_ and augmented_predecessors_map_.
+ void ComputeAugmentedCFG();
+
+ // Adds a copy of the given Construct, and tracks it by its entry block.
+ // Returns a reference to the stored construct.
+ Construct& AddConstruct(const Construct& new_construct);
+
+ // Returns a reference to the construct corresponding to the given entry
+ // block.
+ Construct& FindConstructForEntryBlock(const BasicBlock* entry_block);
/// The result id of the OpLabel that defined this block
uint32_t id_;
@@ -218,28 +235,49 @@
/// The block that is currently being parsed
BasicBlock* current_block_;
- /// A pseudo entry block that, for the purposes of dominance analysis,
- /// is considered the predecessor to any ordinary block without predecessors.
- /// After the function end has been registered, its successor list consists
- /// of all ordinary blocks without predecessors. It has no predecessors.
- /// It does not appear in the predecessor or successor list of any
- /// ordinary block.
+ /// A pseudo entry node used in dominance analysis.
+ /// After the function end has been registered, the successor list of the
+ /// pseudo entry node is the minimal set of nodes such that all nodes in the
+ /// CFG can be reached by following successor lists. That is, the successors
+ /// will be:
+ /// - Any basic block without predecessors. This includes the entry
+ /// block to the function.
+ /// - A single node from each otherwise unreachable cycle in the CFG, if
+ /// such cycles exist.
+ /// The pseudo entry node does not appear in the predecessor or successor
+ /// list of any ordinary block.
+ /// It has no predecessors.
/// It has Id 0.
BasicBlock pseudo_entry_block_;
- /// A pseudo exit block that, for the purposes of dominance analysis,
- /// is considered the successor to any ordinary block without successors.
- /// After the function end has been registered, its predecessor list consists
- /// of all ordinary blocks without successors. It has no successors.
- /// It does not appear in the predecessor or successor list of any
- /// ordinary block.
+ /// A pseudo exit block used in dominance analysis.
+ /// After the function end has been registered, the predecessor list of the
+ /// pseudo exit node is the minimal set of nodes such that all nodes in the
+ /// CFG can be reached by following predecessor lists. That is, the
+ /// predecessors will be:
+ /// - Any basic block without successors. This includes any basic block
+ /// ending with an OpReturn, OpReturnValue or similar instructions.
+ /// - A single node from each otherwise unreachable cycle in the CFG, if
+ /// such cycles exist.
+ /// The pseudo exit node does not appear in the predecessor or successor
+ /// list of any ordinary block.
+ /// It has no successors.
BasicBlock pseudo_exit_block_;
- // A vector containing pseudo_entry_block_.
- const std::vector<BasicBlock*> pseudo_entry_blocks_;
+ // Maps a block to its successors in the augmented CFG, if that set is
+ // different from its successors in the ordinary CFG.
+ std::unordered_map<const BasicBlock*, std::vector<BasicBlock*>>
+ augmented_successors_map_;
+ // Maps a block to its predecessors in the augmented CFG, if that set is
+ // different from its predecessors in the ordinary CFG.
+ std::unordered_map<const BasicBlock*, std::vector<BasicBlock*>>
+ augmented_predecessors_map_;
- // A vector containing pseudo_exit_block_.
- const std::vector<BasicBlock*> pseudo_exit_blocks_;
+ // Maps a structured loop header to its CFG successors and also its
+ // continue target if that continue target is not the loop header
+ // itself. This might have duplicates.
+ std::unordered_map<const BasicBlock*, std::vector<BasicBlock*>>
+ loop_header_successors_plus_continue_target_map_;
/// The constructs that are available in this function
std::list<Construct> cfg_constructs_;
@@ -249,6 +287,9 @@
/// The function parameter ids of the functions
std::vector<uint32_t> parameter_ids_;
+
+ /// Maps a construct's entry block to the construct.
+ std::unordered_map<const BasicBlock*, Construct*> entry_block_to_construct_;
};
} /// namespace libspirv
diff --git a/source/val/Id.cpp b/source/val/Id.cpp
new file mode 100644
index 0000000..2a0e01a
--- /dev/null
+++ b/source/val/Id.cpp
@@ -0,0 +1,62 @@
+// Copyright (c) 2015-2016 The Khronos Group Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+// https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#include "val/Id.h"
+
+namespace libspirv {
+#define OPERATOR(OP) \
+ bool operator OP(const Id& lhs, const Id& rhs) { \
+ return lhs.id_ OP rhs.id_; \
+ } \
+ bool operator OP(const Id& lhs, uint32_t rhs) { return lhs.id_ OP rhs; }
+
+OPERATOR(<)
+OPERATOR(==)
+#undef OPERATOR
+
+Id::Id(const uint32_t result_id)
+ : id_(result_id),
+ type_id_(0),
+ opcode_(SpvOpNop),
+ defining_function_(nullptr),
+ defining_block_(nullptr),
+ uses_(),
+ words_(0) {}
+
+Id::Id(const spv_parsed_instruction_t* inst, Function* function,
+ BasicBlock* block)
+ : id_(inst->result_id),
+ type_id_(inst->type_id),
+ opcode_(static_cast<SpvOp>(inst->opcode)),
+ defining_function_(function),
+ defining_block_(block),
+ uses_(),
+ words_(inst->words, inst->words + inst->num_words) {}
+
+void Id::RegisterUse(const BasicBlock* block) {
+ if (block) { uses_.insert(block); }
+}
+} // namespace libspirv
diff --git a/source/val/Id.h b/source/val/Id.h
new file mode 100644
index 0000000..c6e36cc
--- /dev/null
+++ b/source/val/Id.h
@@ -0,0 +1,135 @@
+// Copyright (c) 2015-2016 The Khronos Group Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+// https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#ifndef LIBSPIRV_VAL_ID_H_
+#define LIBSPIRV_VAL_ID_H_
+
+#include <cstdint>
+
+#include <functional>
+#include <set>
+
+#include "spirv-tools/libspirv.h"
+#include "val/Function.h"
+
+namespace libspirv {
+
+class BasicBlock;
+
+/// Represents a definition of any ID
+///
+/// This class represents the a definition of an Id in a module. This object can
+/// be formed as a complete, or incomplete Id. A complete Id allows you to
+/// reference all of the properties of this class and forms a fully defined
+/// object in the module. The incomplete Id is defined only by its integer value
+/// in a module and can only be used to search in a data structure.
+class Id {
+ public:
+ /// This constructor creates an incomplete Id. This constructor can be used to
+ /// create Id that are used to find other Ids
+ explicit Id(const uint32_t result_id = 0);
+
+ /// This constructor creates a complete Id.
+ explicit Id(const spv_parsed_instruction_t* inst,
+ Function* function = nullptr, BasicBlock* block = nullptr);
+
+ /// Registers a use of the Id
+ void RegisterUse(const BasicBlock* block = nullptr);
+
+ /// returns the id of the Id
+ operator uint32_t() const { return id_; }
+
+ uint32_t id() const { return id_; }
+ uint32_t type_id() const { return type_id_; }
+ SpvOp opcode() const { return opcode_; }
+
+ /// returns the Function where the id was defined. nullptr if it was defined
+ /// outside of a Function
+ const Function* defining_function() const { return defining_function_; }
+
+ /// returns the BasicBlock where the id was defined. nullptr if it was defined
+ /// outside of a BasicBlock
+ const BasicBlock* defining_block() const { return defining_block_; }
+
+ /// Returns the set of blocks where this Id was used
+ const std::set<const BasicBlock*>& uses() const { return uses_; }
+
+ /// The words used to define the Id
+ const std::vector<uint32_t>& words() const { return words_; }
+
+ private:
+ /// The integer that identifies the Id
+ uint32_t id_;
+
+ /// The type of the Id
+ uint32_t type_id_;
+
+ /// The opcode used to define the Id
+ SpvOp opcode_;
+
+ /// The function in which the Id was defined
+ Function* defining_function_;
+
+ /// The block in which the Id was defined
+ BasicBlock* defining_block_;
+
+ /// The blocks in which the Id was used
+ std::set<const BasicBlock*> uses_;
+
+ /// The words of the instuction that defined the Id
+ std::vector<uint32_t> words_;
+
+#define OPERATOR(OP) \
+ friend bool operator OP(const Id& lhs, const Id& rhs); \
+ friend bool operator OP(const Id& lhs, uint32_t rhs)
+ OPERATOR(<);
+ OPERATOR(==);
+#undef OPERATOR
+};
+
+#define OPERATOR(OP) \
+ bool operator OP(const Id& lhs, const Id& rhs); \
+ bool operator OP(const Id& lhs, uint32_t rhs)
+
+OPERATOR(<);
+OPERATOR(==);
+#undef OPERATOR
+
+} // namespace libspirv
+
+// custom specialization of std::hash for Id
+namespace std {
+template <>
+struct hash<libspirv::Id> {
+ typedef libspirv::Id argument_type;
+ typedef std::size_t result_type;
+ result_type operator()(const argument_type& id) const {
+ return hash<uint32_t>()(id);
+ }
+};
+} /// namespace std
+
+#endif // LIBSPIRV_VAL_ID_H_
diff --git a/source/val/ValidationState.cpp b/source/val/ValidationState.cpp
index 217b488..eba0d7f 100644
--- a/source/val/ValidationState.cpp
+++ b/source/val/ValidationState.cpp
@@ -33,7 +33,10 @@
#include "val/Function.h"
using std::list;
+using std::make_pair;
+using std::pair;
using std::string;
+using std::unordered_map;
using std::vector;
namespace libspirv {
@@ -248,7 +251,18 @@
}
bool ValidationState_t::IsDefinedId(uint32_t id) const {
- return usedefs_.FindDef(id).first;
+ return all_definitions_.find(Id{id}) != end(all_definitions_);
+}
+
+const Id* ValidationState_t::FindDef(uint32_t id) const {
+ if (all_definitions_.count(Id{id}) == 0) {
+ return nullptr;
+ } else {
+ /// We are in a const function, so we cannot use defs.operator[]().
+ /// Luckily we know the key exists, so defs_.at() won't throw an
+ /// exception.
+ return &all_definitions_.at(id);
+ }
}
// Increments the instruction count. Used for diagnostic
@@ -327,9 +341,7 @@
memory_model_ = mm;
}
-SpvMemoryModel ValidationState_t::memory_model() const {
- return memory_model_;
-}
+SpvMemoryModel ValidationState_t::memory_model() const { return memory_model_; }
spv_result_t ValidationState_t::RegisterFunction(
uint32_t id, uint32_t ret_type_id, SpvFunctionControlMask function_control,
@@ -339,7 +351,7 @@
"of another function");
in_function_ = true;
module_functions_.emplace_back(id, ret_type_id, function_control,
- function_type_id, *this);
+ function_type_id);
// TODO(umar): validate function type and type_id
@@ -358,4 +370,26 @@
return SPV_SUCCESS;
}
+void ValidationState_t::AddId(const spv_parsed_instruction_t& inst) {
+ if (in_function_body()) {
+ if (in_block()) {
+ all_definitions_[inst.result_id] =
+ Id{&inst, ¤t_function(), current_function().current_block()};
+ } else {
+ all_definitions_[inst.result_id] = Id{&inst, ¤t_function()};
+ }
+ } else {
+ all_definitions_[inst.result_id] = Id{&inst};
+ }
+}
+
+void ValidationState_t::RegisterUseId(uint32_t used_id) {
+ auto used = all_definitions_.find(used_id);
+ if (used != end(all_definitions_)) {
+ if (in_function_body())
+ used->second.RegisterUse(current_function().current_block());
+ else
+ used->second.RegisterUse(nullptr);
+ }
+}
} /// namespace libspirv
diff --git a/source/val/ValidationState.h b/source/val/ValidationState.h
index af5e54c..6a43c90 100644
--- a/source/val/ValidationState.h
+++ b/source/val/ValidationState.h
@@ -39,24 +39,11 @@
#include "spirv-tools/libspirv.h"
#include "spirv/1.1/spirv.h"
#include "spirv_definition.h"
+#include "val/Function.h"
+#include "val/Id.h"
namespace libspirv {
-// Universal Limit of ResultID + 1
-static const uint32_t kInvalidId = 0x400000;
-
-// Info about a result ID.
-typedef struct spv_id_info_t {
- /// Id value.
- uint32_t id;
- /// Type id, or 0 if no type.
- uint32_t type_id;
- /// Opcode of the instruction defining the id.
- SpvOp opcode;
- /// Binary words of the instruction defining the id.
- std::vector<uint32_t> words;
-} spv_id_info_t;
-
/// This enum represents the sections of a SPIRV module. See section 2.4
/// of the SPIRV spec for additional details of the order. The enumerant values
/// are in the same order as the vector returned by GetModuleOrder
@@ -75,8 +62,6 @@
kLayoutFunctionDefinitions /// < Section 2.4 #11
};
-class Function;
-
/// This class manages the state of the SPIR-V validation as it is being parsed.
class ValidationState_t {
public:
@@ -138,41 +123,6 @@
/// instruction
bool in_block() const;
- /// Keeps track of ID definitions and uses.
- class UseDefTracker {
- public:
- void AddDef(const spv_id_info_t& def) { defs_[def.id] = def; }
-
- void AddUse(uint32_t id) { uses_.insert(id); }
-
- /// Finds id's def, if it exists. If found, returns <true, def>. Otherwise,
- /// returns <false, something>.
- std::pair<bool, spv_id_info_t> FindDef(uint32_t id) const {
- if (defs_.count(id) == 0) {
- return std::make_pair(false, spv_id_info_t{});
- } else {
- /// We are in a const function, so we cannot use defs.operator[]().
- /// Luckily we know the key exists, so defs_.at() won't throw an
- /// exception.
- return std::make_pair(true, defs_.at(id));
- }
- }
-
- /// Returns uses of IDs lacking defs.
- std::unordered_set<uint32_t> FindUsesWithoutDefs() const {
- auto diff = uses_;
- for (const auto d : defs_) diff.erase(d.first);
- return diff;
- }
-
- private:
- std::unordered_set<uint32_t> uses_;
- std::unordered_map<uint32_t, spv_id_info_t> defs_;
- };
-
- UseDefTracker& usedefs() { return usedefs_; }
- const UseDefTracker& usedefs() const { return usedefs_; }
-
/// Returns a list of entry point function ids
std::vector<uint32_t>& entry_points() { return entry_points_; }
const std::vector<uint32_t>& entry_points() const { return entry_points_; }
@@ -210,7 +160,23 @@
AssemblyGrammar& grammar() { return grammar_; }
+ /// Adds an id to the module
+ void AddId(const spv_parsed_instruction_t& inst);
+
+ /// Register Id use
+ void RegisterUseId(uint32_t used_id);
+
+ /// Finds id's def, if it exists. If found, returns the definition otherwise
+ /// nullptr
+ const Id* FindDef(uint32_t id) const;
+
+ const std::unordered_map<uint32_t, Id>& all_definitions() const {
+ return all_definitions_;
+ }
+
private:
+ ValidationState_t(const ValidationState_t&);
+
spv_diagnostic* diagnostic_;
/// Tracks the number of instructions evaluated by the validator
int instruction_counter_;
@@ -231,8 +197,7 @@
spv_capability_mask_t
module_capabilities_; /// Module's declared capabilities.
- /// Definitions and uses of all the IDs in the module.
- UseDefTracker usedefs_;
+ std::unordered_map<uint32_t, Id> all_definitions_;
/// IDs that are entry points, ie, arguments to OpEntryPoint.
std::vector<uint32_t> entry_points_;
diff --git a/source/validate.cpp b/source/validate.cpp
index 6e10a8a..836bea1 100644
--- a/source/validate.cpp
+++ b/source/validate.cpp
@@ -59,7 +59,7 @@
using libspirv::CfgPass;
using libspirv::InstructionPass;
using libspirv::ModuleLayoutPass;
-using libspirv::SsaPass;
+using libspirv::IdPass;
using libspirv::ValidationState_t;
spv_result_t spvValidateIDs(
@@ -67,15 +67,11 @@
const spv_opcode_table opcodeTable, const spv_operand_table operandTable,
const spv_ext_inst_table extInstTable, const ValidationState_t& state,
spv_position position, spv_diagnostic* pDiagnostic) {
- auto undefd = state.usedefs().FindUsesWithoutDefs();
- for (auto id : undefd) {
- DIAGNOSTIC << "Undefined ID: " << id;
- }
position->index = SPV_INDEX_INSTRUCTION;
spvCheckReturn(spvValidateInstructionIDs(pInsts, count, opcodeTable,
operandTable, extInstTable, state,
position, pDiagnostic));
- return undefd.empty() ? SPV_SUCCESS : SPV_ERROR_INVALID_ID;
+ return SPV_SUCCESS;
}
namespace {
@@ -127,18 +123,6 @@
}
}
-// Collects use-def info about an instruction's IDs.
-void ProcessIds(ValidationState_t& _, const spv_parsed_instruction_t& inst) {
- if (inst.result_id) {
- _.usedefs().AddDef(
- {inst.result_id, inst.type_id, static_cast<SpvOp>(inst.opcode),
- std::vector<uint32_t>(inst.words, inst.words + inst.num_words)});
- }
- for (auto op = inst.operands; op != inst.operands + inst.num_operands; ++op) {
- if (spvIsIdType(op->type)) _.usedefs().AddUse(inst.words[op->offset]);
- }
-}
-
spv_result_t ProcessInstruction(void* user_data,
const spv_parsed_instruction_t* inst) {
ValidationState_t& _ = *(reinterpret_cast<ValidationState_t*>(user_data));
@@ -148,15 +132,53 @@
DebugInstructionPass(_, inst);
// TODO(umar): Perform data rules pass
- ProcessIds(_, *inst);
+ spvCheckReturn(IdPass(_, inst));
spvCheckReturn(ModuleLayoutPass(_, inst));
spvCheckReturn(CfgPass(_, inst));
- spvCheckReturn(SsaPass(_, inst));
spvCheckReturn(InstructionPass(_, inst));
return SPV_SUCCESS;
}
+void printDot(const ValidationState_t& _, const libspirv::BasicBlock& other) {
+ string block_string;
+ if (other.successors()->empty()) {
+ block_string += "end ";
+ } else {
+ for (auto block : *other.successors()) {
+ block_string += _.getIdOrName(block->id()) + " ";
+ }
+ }
+ printf("%10s -> {%s\b}\n", _.getIdOrName(other.id()).c_str(),
+ block_string.c_str());
+}
+
+void PrintBlocks(ValidationState_t& _, libspirv::Function func) {
+ assert(func.first_block());
+
+ printf("%10s -> %s\n", _.getIdOrName(func.id()).c_str(),
+ _.getIdOrName(func.first_block()->id()).c_str());
+ for (const auto& block : func.ordered_blocks()) {
+ printDot(_, *block);
+ }
+}
+
+#ifdef __clang__
+#define UNUSED(func) [[gnu::unused]] func
+#elif defined(__GNUC__)
+#define UNUSED(func) func __attribute__((unused)); func
+#elif defined(_MSC_VER)
+#define UNUSED(func) func
+#endif
+
+UNUSED(void PrintDotGraph(ValidationState_t& _, libspirv::Function func)) {
+ if (func.first_block()) {
+ string func_name(_.getIdOrName(func.id()));
+ printf("digraph %s {\n", func_name.c_str());
+ PrintBlocks(_, func);
+ printf("}\n");
+ }
+}
} // anonymous namespace
spv_result_t spvValidate(const spv_const_context context,
@@ -196,7 +218,7 @@
vector<uint32_t> ids = vstate.UnresolvedForwardIds();
transform(begin(ids), end(ids), ostream_iterator<string>(ss, " "),
- bind(&ValidationState_t::getIdName, vstate, _1));
+ bind(&ValidationState_t::getIdName, std::ref(vstate), _1));
auto id_str = ss.str();
return vstate.diag(SPV_ERROR_INVALID_ID)
@@ -207,6 +229,7 @@
// CFG checks are performed after the binary has been parsed
// and the CFGPass has collected information about the control flow
spvCheckReturn(PerformCfgChecks(vstate));
+ spvCheckReturn(CheckIdDefinitionDominateUse(vstate));
// NOTE: Copy each instruction for easier processing
std::vector<spv_instruction_t> instructions;
diff --git a/source/validate.h b/source/validate.h
index 35ec9a6..c6ffac0 100644
--- a/source/validate.h
+++ b/source/validate.h
@@ -27,37 +27,49 @@
#ifndef LIBSPIRV_VALIDATE_H_
#define LIBSPIRV_VALIDATE_H_
-#include <algorithm>
-#include <array>
#include <functional>
-#include <list>
-#include <map>
-#include <string>
-#include <unordered_map>
-#include <unordered_set>
#include <utility>
#include <vector>
-#include "assembly_grammar.h"
-#include "binary.h"
-#include "diagnostic.h"
#include "instruction.h"
#include "spirv-tools/libspirv.h"
-#include "spirv_definition.h"
#include "table.h"
-#include "val/BasicBlock.h"
-
-// Structures
namespace libspirv {
class ValidationState_t;
+class BasicBlock;
/// A function that returns a vector of BasicBlocks given a BasicBlock. Used to
/// get the successor and predecessor nodes of a CFG block
using get_blocks_func =
std::function<const std::vector<BasicBlock*>*(const BasicBlock*)>;
+/// @brief Depth first traversal starting from the \p entry BasicBlock
+///
+/// This function performs a depth first traversal from the \p entry
+/// BasicBlock and calls the pre/postorder functions when it needs to process
+/// the node in pre order, post order. It also calls the backedge function
+/// when a back edge is encountered.
+///
+/// @param[in] entry The root BasicBlock of a CFG
+/// @param[in] successor_func A function which will return a pointer to the
+/// successor nodes
+/// @param[in] preorder A function that will be called for every block in a
+/// CFG following preorder traversal semantics
+/// @param[in] postorder A function that will be called for every block in a
+/// CFG following postorder traversal semantics
+/// @param[in] backedge A function that will be called when a backedge is
+/// encountered during a traversal
+/// NOTE: The @p successor_func and predecessor_func each return a pointer to a
+/// collection such that iterators to that collection remain valid for the
+/// lifetime of the algorithm.
+void DepthFirstTraversal(
+ const BasicBlock* entry, get_blocks_func successor_func,
+ std::function<void(const BasicBlock*)> preorder,
+ std::function<void(const BasicBlock*)> postorder,
+ std::function<void(const BasicBlock*, const BasicBlock*)> backedge);
+
/// @brief Calculates dominator edges for a set of blocks
///
/// Computes dominators using the algorithm of Cooper, Harvey, and Kennedy
@@ -89,6 +101,18 @@
/// @return SPV_SUCCESS if no errors are found. SPV_ERROR_INVALID_CFG otherwise
spv_result_t PerformCfgChecks(ValidationState_t& _);
+/// @brief This function checks all ID definitions dominate their use in the
+/// CFG.
+///
+/// This function will iterate over all ID definitions that are defined in the
+/// functions of a module and make sure that the definitions appear in a
+/// block that dominates their use.
+///
+/// @param[in] _ the validation state of the module
+///
+/// @return SPV_SUCCESS if no errors are found. SPV_ERROR_INVALID_ID otherwise
+spv_result_t CheckIdDefinitionDominateUse(const ValidationState_t& _);
+
/// @brief Updates the immediate dominator for each of the block edges
///
/// Updates the immediate dominator of the blocks for each of the edges
@@ -115,9 +139,8 @@
spv_result_t CfgPass(ValidationState_t& _,
const spv_parsed_instruction_t* inst);
-/// Performs SSA validation of a module
-spv_result_t SsaPass(ValidationState_t& _,
- const spv_parsed_instruction_t* inst);
+/// Performs Id and SSA validation of a module
+spv_result_t IdPass(ValidationState_t& _, const spv_parsed_instruction_t* inst);
/// Performs instruction validation.
spv_result_t InstructionPass(ValidationState_t& _,
diff --git a/source/validate_cfg.cpp b/source/validate_cfg.cpp
index a95f2cd..6342a91 100644
--- a/source/validate_cfg.cpp
+++ b/source/validate_cfg.cpp
@@ -30,8 +30,9 @@
#include <algorithm>
#include <functional>
-#include <set>
+#include <map>
#include <string>
+#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
@@ -47,12 +48,14 @@
using std::get;
using std::ignore;
using std::make_pair;
+using std::make_tuple;
using std::numeric_limits;
using std::pair;
using std::set;
using std::string;
using std::tie;
using std::transform;
+using std::tuple;
using std::unordered_map;
using std::unordered_set;
using std::vector;
@@ -86,26 +89,8 @@
return false;
}
-/// @brief Depth first traversal starting from the \p entry BasicBlock
-///
-/// This function performs a depth first traversal from the \p entry
-/// BasicBlock and calls the pre/postorder functions when it needs to process
-/// the node in pre order, post order. It also calls the backedge function
-/// when a back edge is encountered.
-///
-/// @param[in] entry The root BasicBlock of a CFG
-/// @param[in] successor_func A function which will return a pointer to the
-/// successor nodes
-/// @param[in] preorder A function that will be called for every block in a
-/// CFG following preorder traversal semantics
-/// @param[in] postorder A function that will be called for every block in a
-/// CFG following postorder traversal semantics
-/// @param[in] backedge A function that will be called when a backedge is
-/// encountered during a traversal
-/// NOTE: The @p successor_func and predecessor_func each return a pointer to a
-/// collection such that iterators to that collection remain valid for the
-/// lifetime
-/// of the algorithm
+} // namespace
+
void DepthFirstTraversal(const BasicBlock* entry,
get_blocks_func successor_func,
function<void(cbb_ptr)> preorder,
@@ -143,8 +128,6 @@
}
}
-} // namespace
-
vector<pair<BasicBlock*, BasicBlock*>> CalculateDominators(
const vector<cbb_ptr>& postorder, get_blocks_func predecessor_func) {
struct block_detail {
@@ -164,10 +147,12 @@
changed = false;
for (auto b = postorder.rbegin() + 1; b != postorder.rend(); ++b) {
const vector<BasicBlock*>& predecessors = *predecessor_func(*b);
- // first processed/reachable predecessor
+ // Find the first processed/reachable predecessor that is reachable
+ // in the forward traversal.
auto res = find_if(begin(predecessors), end(predecessors),
[&idoms, undefined_dom](BasicBlock* pred) {
- return idoms[pred].dominator != undefined_dom;
+ return idoms.count(pred) &&
+ idoms[pred].dominator != undefined_dom;
});
if (res == end(predecessors)) continue;
const BasicBlock* idom = *res;
@@ -176,6 +161,10 @@
// all other predecessors
for (const auto* p : predecessors) {
if (idom == p) continue;
+ // Only consider nodes reachable in the forward traversal.
+ // Otherwise the intersection doesn't make sense and will never
+ // terminate.
+ if (!idoms.count(p)) continue;
if (idoms[p].dominator != undefined_dom) {
size_t finger1 = idoms[p].postorder_index;
size_t finger2 = idom_idx;
@@ -248,7 +237,6 @@
uint32_t back_edge_block_id;
uint32_t loop_header_block_id;
tie(back_edge_block_id, loop_header_block_id) = edge;
-
auto is_this_header = [=](Construct& c) {
return c.type() == ConstructType::kLoop &&
c.entry_block()->id() == loop_header_block_id;
@@ -268,22 +256,10 @@
}
}
-/// Constructs an error message for construct validation errors
-string ConstructErrorString(const Construct& construct,
- const string& header_string,
- const string& exit_string,
- bool post_dominate = false) {
- string construct_name;
- string header_name;
- string exit_name;
- string dominate_text;
- if (post_dominate) {
- dominate_text = "is not post dominated by";
- } else {
- dominate_text = "does not dominate";
- }
+tuple<string, string, string> ConstructNames(ConstructType type) {
+ string construct_name, header_name, exit_name;
- switch (construct.type()) {
+ switch (type) {
case ConstructType::kSelection:
construct_name = "selection";
header_name = "selection header";
@@ -301,12 +277,31 @@
break;
case ConstructType::kCase:
construct_name = "case";
- header_name = "case block";
- exit_name = "exit block"; // TODO(umar): there has to be a better name
+ header_name = "case entry block";
+ exit_name = "case exit block";
break;
default:
assert(1 == 0 && "Not defined type");
}
+
+ return make_tuple(construct_name, header_name, exit_name);
+}
+
+/// Constructs an error message for construct validation errors
+string ConstructErrorString(const Construct& construct,
+ const string& header_string,
+ const string& exit_string,
+ bool post_dominate = false) {
+ string construct_name, header_name, exit_name, dominate_text;
+ if (post_dominate) {
+ dominate_text = "is not post dominated by";
+ } else {
+ dominate_text = "does not dominate";
+ }
+
+ tie(construct_name, header_name, exit_name) =
+ ConstructNames(construct.type());
+
// TODO(umar): Add header block for continue constructs to error message
return "The " + construct_name + " construct with the " + header_name + " " +
header_string + " " + dominate_text + " the " + exit_name + " " +
@@ -318,7 +313,9 @@
const vector<pair<uint32_t, uint32_t>>& back_edges) {
/// Check all backedges target only loop headers and have exactly one
/// back-edge branching to it
- set<uint32_t> loop_headers;
+
+ // Map a loop header to blocks with back-edges to the loop header.
+ std::map<uint32_t, std::unordered_set<uint32_t>> loop_latch_blocks;
for (auto back_edge : back_edges) {
uint32_t back_edge_block;
uint32_t header_block;
@@ -329,14 +326,20 @@
<< _.getIdName(header_block)
<< ") can only be formed between a block and a loop header.";
}
- bool success;
- tie(ignore, success) = loop_headers.insert(header_block);
- if (!success) {
- // TODO(umar): List the back-edge blocks that are branching to loop
- // header
+ loop_latch_blocks[header_block].insert(back_edge_block);
+ }
+
+ // Check the loop headers have exactly one back-edge branching to it
+ for (BasicBlock* loop_header : function.ordered_blocks()) {
+ if (!loop_header->reachable()) continue;
+ if (!loop_header->is_type(kBlockTypeLoop)) continue;
+ auto loop_header_id = loop_header->id();
+ auto num_latch_blocks = loop_latch_blocks[loop_header_id].size();
+ if (num_latch_blocks != 1) {
return _.diag(SPV_ERROR_INVALID_CFG)
- << "Loop header " << _.getIdName(header_block)
- << " targeted by multiple back-edges";
+ << "Loop header " << _.getIdName(loop_header_id)
+ << " is targeted by " << num_latch_blocks
+ << " back-edge blocks but the standard requires exactly one";
}
}
@@ -345,15 +348,27 @@
auto header = construct.entry_block();
auto merge = construct.exit_block();
- // if the merge block is reachable then it's dominated by the header
- if (merge->reachable() &&
+ if (header->reachable() && !merge) {
+ string construct_name, header_name, exit_name;
+ tie(construct_name, header_name, exit_name) =
+ ConstructNames(construct.type());
+ return _.diag(SPV_ERROR_INTERNAL)
+ << "Construct " + construct_name + " with " + header_name + " " +
+ _.getIdName(header->id()) + " does not have a " +
+ exit_name + ". This may be a bug in the validator.";
+ }
+
+ // If the merge block is reachable then it's dominated by the header.
+ if (merge && merge->reachable() &&
find(merge->dom_begin(), merge->dom_end(), header) ==
merge->dom_end()) {
return _.diag(SPV_ERROR_INVALID_CFG)
<< ConstructErrorString(construct, _.getIdName(header->id()),
_.getIdName(merge->id()));
}
- if (construct.type() == ConstructType::kContinue) {
+ // Check post-dominance for continue constructs. But dominance and
+ // post-dominance only make sense when the construct is reachable.
+ if (header->reachable() && construct.type() == ConstructType::kContinue) {
if (find(header->pdom_begin(), header->pdom_end(), merge) ==
merge->pdom_end()) {
return _.diag(SPV_ERROR_INVALID_CFG)
@@ -389,70 +404,49 @@
<< _.getIdName(function.id());
}
- // Prepare for dominance calculations. We want to analyze all the
- // blocks in the function, even in degenerate control flow cases
- // including unreachable blocks. For this calculation, we create an
- // agumented CFG by adding a pseudo-entry node that is considered the
- // predecessor of each source in the original CFG, and a pseudo-exit
- // node that is considered the successor to each sink in the original
- // CFG. The augmented CFG is guaranteed to have a single source node
- // (i.e. not having predecessors) and a single exit node (i.e. not
- // having successors). However, there might be isolated strongly
- // connected components that are not reachable by following successors
- // from the pseudo entry node, and not reachable by following
- // predecessors from the pseudo exit node.
-
- auto* pseudo_entry = function.pseudo_entry_block();
- auto* pseudo_exit = function.pseudo_exit_block();
- // We need vectors to use as the predecessors (in the augmented CFG)
- // for the source nodes of the original CFG. It must have a stable
- // address for the duration of the calculation.
- auto* pseudo_entry_vec = function.pseudo_entry_blocks();
- // Similarly, we need a vector to be used as the successors (in the
- // augmented CFG) for sinks in the original CFG.
- auto* pseudo_exit_vec = function.pseudo_exit_blocks();
- // Returns the predecessors of a block in the augmented CFG.
- auto augmented_predecessor_fn = [pseudo_entry, pseudo_entry_vec](
- const BasicBlock* block) {
- auto predecessors = block->predecessors();
- return (block != pseudo_entry && predecessors->empty()) ? pseudo_entry_vec
- : predecessors;
- };
- // Returns the successors of a block in the augmented CFG.
- auto augmented_successor_fn = [pseudo_exit,
- pseudo_exit_vec](const BasicBlock* block) {
- auto successors = block->successors();
- return (block != pseudo_exit && successors->empty()) ? pseudo_exit_vec
- : successors;
- };
-
// Set each block's immediate dominator and immediate postdominator,
// and find all back-edges.
+ //
+ // We want to analyze all the blocks in the function, even in degenerate
+ // control flow cases including unreachable blocks. So use the augmented
+ // CFG to ensure we cover all the blocks.
vector<const BasicBlock*> postorder;
vector<const BasicBlock*> postdom_postorder;
vector<pair<uint32_t, uint32_t>> back_edges;
+ auto ignore_block = [](cbb_ptr) {};
+ auto ignore_edge = [](cbb_ptr, cbb_ptr) {};
if (!function.ordered_blocks().empty()) {
/// calculate dominators
- DepthFirstTraversal(pseudo_entry, augmented_successor_fn, [](cbb_ptr) {},
+ DepthFirstTraversal(function.first_block(),
+ function.AugmentedCFGSuccessorsFunction(),
+ ignore_block,
[&](cbb_ptr b) { postorder.push_back(b); },
- [&](cbb_ptr from, cbb_ptr to) {
- back_edges.emplace_back(from->id(), to->id());
- });
- auto edges =
- libspirv::CalculateDominators(postorder, augmented_predecessor_fn);
+ ignore_edge);
+ auto edges = libspirv::CalculateDominators(
+ postorder, function.AugmentedCFGPredecessorsFunction());
for (auto edge : edges) {
edge.first->SetImmediateDominator(edge.second);
}
/// calculate post dominators
- DepthFirstTraversal(pseudo_exit, augmented_predecessor_fn, [](cbb_ptr) {},
+ DepthFirstTraversal(function.pseudo_exit_block(),
+ function.AugmentedCFGPredecessorsFunction(),
+ ignore_block,
[&](cbb_ptr b) { postdom_postorder.push_back(b); },
- [&](cbb_ptr, cbb_ptr) {});
+ ignore_edge);
auto postdom_edges = libspirv::CalculateDominators(
- postdom_postorder, augmented_successor_fn);
+ postdom_postorder, function.AugmentedCFGSuccessorsFunction());
for (auto edge : postdom_edges) {
edge.first->SetImmediatePostDominator(edge.second);
}
+ /// calculate back edges.
+ DepthFirstTraversal(
+ function.pseudo_entry_block(),
+ function
+ .AugmentedCFGSuccessorsFunctionIncludingHeaderToContinueEdge(),
+ ignore_block, ignore_block, [&](cbb_ptr from, cbb_ptr to) {
+ back_edges.emplace_back(from->id(), to->id());
+ });
}
UpdateContinueConstructExitBlocks(function, back_edges);
@@ -462,7 +456,7 @@
if (blocks.empty() == false) {
for (auto block = begin(blocks) + 1; block != end(blocks); ++block) {
if (auto idom = (*block)->immediate_dominator()) {
- if (idom != pseudo_entry &&
+ if (idom != function.pseudo_entry_block() &&
block == std::find(begin(blocks), block, idom)) {
return _.diag(SPV_ERROR_INVALID_CFG)
<< "Block " << _.getIdName((*block)->id())
diff --git a/source/validate_id.cpp b/source/validate_id.cpp
index e98b339..b94e861 100644
--- a/source/validate_id.cpp
+++ b/source/validate_id.cpp
@@ -28,6 +28,7 @@
#include <cassert>
+#include <algorithm>
#include <iostream>
#include <unordered_map>
#include <vector>
@@ -43,7 +44,9 @@
action; \
}
-using UseDefTracker = libspirv::ValidationState_t::UseDefTracker;
+using libspirv::ValidationState_t;
+using std::function;
+using std::ignore;
namespace {
@@ -55,7 +58,7 @@
const spv_instruction_t* pInsts, const uint64_t instCountArg,
const SpvMemoryModel memoryModelArg,
const SpvAddressingModel addressingModelArg,
- const UseDefTracker& usedefs,
+ const ValidationState_t& module,
const std::vector<uint32_t>& entry_points, spv_position positionArg,
spv_diagnostic* pDiagnosticArg)
: opcodeTable(opcodeTableArg),
@@ -67,7 +70,7 @@
addressingModel(addressingModelArg),
position(positionArg),
pDiagnostic(pDiagnosticArg),
- usedefs_(usedefs),
+ module_(module),
entry_points_(entry_points) {}
bool isValid(const spv_instruction_t* inst);
@@ -85,7 +88,7 @@
const SpvAddressingModel addressingModel;
spv_position position;
spv_diagnostic* pDiagnostic;
- UseDefTracker usedefs_;
+ const ValidationState_t& module_;
std::vector<uint32_t> entry_points_;
};
@@ -106,20 +109,20 @@
bool idUsage::isValid<SpvOpMemberName>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto typeIndex = 1;
- auto type = usedefs_.FindDef(inst->words[typeIndex]);
- if (!type.first || SpvOpTypeStruct != type.second.opcode) {
+ auto type = module_.FindDef(inst->words[typeIndex]);
+ if (!type || SpvOpTypeStruct != type->opcode()) {
DIAG(typeIndex) << "OpMemberName Type <id> '" << inst->words[typeIndex]
<< "' is not a struct type.";
return false;
}
auto memberIndex = 2;
auto member = inst->words[memberIndex];
- auto memberCount = (uint32_t)(type.second.words.size() - 2);
+ auto memberCount = (uint32_t)(type->words().size() - 2);
spvCheck(memberCount <= member, DIAG(memberIndex)
<< "OpMemberName Member <id> '"
<< inst->words[memberIndex]
<< "' index is larger than Type <id> '"
- << type.second.id << "'s member count.";
+ << type->id() << "'s member count.";
return false);
return true;
}
@@ -128,8 +131,8 @@
bool idUsage::isValid<SpvOpLine>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto fileIndex = 1;
- auto file = usedefs_.FindDef(inst->words[fileIndex]);
- if (!file.first || SpvOpString != file.second.opcode) {
+ auto file = module_.FindDef(inst->words[fileIndex]);
+ if (!file || SpvOpString != file->opcode()) {
DIAG(fileIndex) << "OpLine Target <id> '" << inst->words[fileIndex]
<< "' is not an OpString.";
return false;
@@ -141,8 +144,8 @@
bool idUsage::isValid<SpvOpMemberDecorate>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto structTypeIndex = 1;
- auto structType = usedefs_.FindDef(inst->words[structTypeIndex]);
- if (!structType.first || SpvOpTypeStruct != structType.second.opcode) {
+ auto structType = module_.FindDef(inst->words[structTypeIndex]);
+ if (!structType || SpvOpTypeStruct != structType->opcode()) {
DIAG(structTypeIndex) << "OpMemberDecorate Structure type <id> '"
<< inst->words[structTypeIndex]
<< "' is not a struct type.";
@@ -150,7 +153,7 @@
}
auto memberIndex = 2;
auto member = inst->words[memberIndex];
- auto memberCount = static_cast<uint32_t>(structType.second.words.size() - 2);
+ auto memberCount = static_cast<uint32_t>(structType->words().size() - 2);
spvCheck(memberCount < member, DIAG(memberIndex)
<< "OpMemberDecorate Structure type <id> '"
<< inst->words[memberIndex]
@@ -163,9 +166,8 @@
bool idUsage::isValid<SpvOpGroupDecorate>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto decorationGroupIndex = 1;
- auto decorationGroup = usedefs_.FindDef(inst->words[decorationGroupIndex]);
- if (!decorationGroup.first ||
- SpvOpDecorationGroup != decorationGroup.second.opcode) {
+ auto decorationGroup = module_.FindDef(inst->words[decorationGroupIndex]);
+ if (!decorationGroup || SpvOpDecorationGroup != decorationGroup->opcode()) {
DIAG(decorationGroupIndex) << "OpGroupDecorate Decoration group <id> '"
<< inst->words[decorationGroupIndex]
<< "' is not a decoration group.";
@@ -190,8 +192,8 @@
bool idUsage::isValid<SpvOpEntryPoint>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto entryPointIndex = 2;
- auto entryPoint = usedefs_.FindDef(inst->words[entryPointIndex]);
- if (!entryPoint.first || SpvOpFunction != entryPoint.second.opcode) {
+ auto entryPoint = module_.FindDef(inst->words[entryPointIndex]);
+ if (!entryPoint || SpvOpFunction != entryPoint->opcode()) {
DIAG(entryPointIndex) << "OpEntryPoint Entry Point <id> '"
<< inst->words[entryPointIndex]
<< "' is not a function.";
@@ -202,16 +204,16 @@
if (executionModel != SpvExecutionModelKernel) {
// TODO: Check the entry point signature is void main(void), may be subject
// to change
- auto entryPointType = usedefs_.FindDef(entryPoint.second.words[4]);
- if (!entryPointType.first || 3 != entryPointType.second.words.size()) {
+ auto entryPointType = module_.FindDef(entryPoint->words()[4]);
+ if (!entryPointType || 3 != entryPointType->words().size()) {
DIAG(entryPointIndex) << "OpEntryPoint Entry Point <id> '"
<< inst->words[entryPointIndex]
<< "'s function parameter count is not zero.";
return false;
}
}
- auto returnType = usedefs_.FindDef(entryPoint.second.type_id);
- if (!returnType.first || SpvOpTypeVoid != returnType.second.opcode) {
+ auto returnType = module_.FindDef(entryPoint->type_id());
+ if (!returnType || SpvOpTypeVoid != returnType->opcode()) {
DIAG(entryPointIndex) << "OpEntryPoint Entry Point <id> '"
<< inst->words[entryPointIndex]
<< "'s function return type is not void.";
@@ -241,9 +243,8 @@
bool idUsage::isValid<SpvOpTypeVector>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto componentIndex = 2;
- auto componentType = usedefs_.FindDef(inst->words[componentIndex]);
- if (!componentType.first ||
- !spvOpcodeIsScalarType(componentType.second.opcode)) {
+ auto componentType = module_.FindDef(inst->words[componentIndex]);
+ if (!componentType || !spvOpcodeIsScalarType(componentType->opcode())) {
DIAG(componentIndex) << "OpTypeVector Component Type <id> '"
<< inst->words[componentIndex]
<< "' is not a scalar type.";
@@ -256,8 +257,8 @@
bool idUsage::isValid<SpvOpTypeMatrix>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto columnTypeIndex = 2;
- auto columnType = usedefs_.FindDef(inst->words[columnTypeIndex]);
- if (!columnType.first || SpvOpTypeVector != columnType.second.opcode) {
+ auto columnType = module_.FindDef(inst->words[columnTypeIndex]);
+ if (!columnType || SpvOpTypeVector != columnType->opcode()) {
DIAG(columnTypeIndex) << "OpTypeMatrix Column Type <id> '"
<< inst->words[columnTypeIndex]
<< "' is not a vector.";
@@ -297,36 +298,35 @@
bool idUsage::isValid<SpvOpTypeArray>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto elementTypeIndex = 2;
- auto elementType = usedefs_.FindDef(inst->words[elementTypeIndex]);
- if (!elementType.first ||
- !spvOpcodeGeneratesType(elementType.second.opcode)) {
+ auto elementType = module_.FindDef(inst->words[elementTypeIndex]);
+ if (!elementType || !spvOpcodeGeneratesType(elementType->opcode())) {
DIAG(elementTypeIndex) << "OpTypeArray Element Type <id> '"
<< inst->words[elementTypeIndex]
<< "' is not a type.";
return false;
}
auto lengthIndex = 3;
- auto length = usedefs_.FindDef(inst->words[lengthIndex]);
- if (!length.first || !spvOpcodeIsConstant(length.second.opcode)) {
+ auto length = module_.FindDef(inst->words[lengthIndex]);
+ if (!length || !spvOpcodeIsConstant(length->opcode())) {
DIAG(lengthIndex) << "OpTypeArray Length <id> '" << inst->words[lengthIndex]
<< "' is not a scalar constant type.";
return false;
}
// NOTE: Check the initialiser value of the constant
- auto constInst = length.second.words;
+ auto constInst = length->words();
auto constResultTypeIndex = 1;
- auto constResultType = usedefs_.FindDef(constInst[constResultTypeIndex]);
- if (!constResultType.first || SpvOpTypeInt != constResultType.second.opcode) {
+ auto constResultType = module_.FindDef(constInst[constResultTypeIndex]);
+ if (!constResultType || SpvOpTypeInt != constResultType->opcode()) {
DIAG(lengthIndex) << "OpTypeArray Length <id> '" << inst->words[lengthIndex]
<< "' is not a constant integer type.";
return false;
}
- switch (length.second.opcode) {
+ switch (length->opcode()) {
case SpvOpSpecConstant:
case SpvOpConstant:
- if (aboveZero(length.second.words, constResultType.second.words)) break;
+ if (aboveZero(length->words(), constResultType->words())) break;
// Else fall through!
case SpvOpConstantNull: {
DIAG(lengthIndex) << "OpTypeArray Length <id> '"
@@ -347,9 +347,8 @@
bool idUsage::isValid<SpvOpTypeRuntimeArray>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto elementTypeIndex = 2;
- auto elementType = usedefs_.FindDef(inst->words[elementTypeIndex]);
- if (!elementType.first ||
- !spvOpcodeGeneratesType(elementType.second.opcode)) {
+ auto elementType = module_.FindDef(inst->words[elementTypeIndex]);
+ if (!elementType || !spvOpcodeGeneratesType(elementType->opcode())) {
DIAG(elementTypeIndex) << "OpTypeRuntimeArray Element Type <id> '"
<< inst->words[elementTypeIndex]
<< "' is not a type.";
@@ -363,9 +362,8 @@
const spv_opcode_desc) {
for (size_t memberTypeIndex = 2; memberTypeIndex < inst->words.size();
++memberTypeIndex) {
- auto memberType = usedefs_.FindDef(inst->words[memberTypeIndex]);
- if (!memberType.first ||
- !spvOpcodeGeneratesType(memberType.second.opcode)) {
+ auto memberType = module_.FindDef(inst->words[memberTypeIndex]);
+ if (!memberType || !spvOpcodeGeneratesType(memberType->opcode())) {
DIAG(memberTypeIndex) << "OpTypeStruct Member Type <id> '"
<< inst->words[memberTypeIndex]
<< "' is not a type.";
@@ -379,8 +377,8 @@
bool idUsage::isValid<SpvOpTypePointer>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto typeIndex = 3;
- auto type = usedefs_.FindDef(inst->words[typeIndex]);
- if (!type.first || !spvOpcodeGeneratesType(type.second.opcode)) {
+ auto type = module_.FindDef(inst->words[typeIndex]);
+ if (!type || !spvOpcodeGeneratesType(type->opcode())) {
DIAG(typeIndex) << "OpTypePointer Type <id> '" << inst->words[typeIndex]
<< "' is not a type.";
return false;
@@ -392,16 +390,16 @@
bool idUsage::isValid<SpvOpTypeFunction>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto returnTypeIndex = 2;
- auto returnType = usedefs_.FindDef(inst->words[returnTypeIndex]);
- if (!returnType.first || !spvOpcodeGeneratesType(returnType.second.opcode)) {
+ auto returnType = module_.FindDef(inst->words[returnTypeIndex]);
+ if (!returnType || !spvOpcodeGeneratesType(returnType->opcode())) {
DIAG(returnTypeIndex) << "OpTypeFunction Return Type <id> '"
<< inst->words[returnTypeIndex] << "' is not a type.";
return false;
}
for (size_t paramTypeIndex = 3; paramTypeIndex < inst->words.size();
++paramTypeIndex) {
- auto paramType = usedefs_.FindDef(inst->words[paramTypeIndex]);
- if (!paramType.first || !spvOpcodeGeneratesType(paramType.second.opcode)) {
+ auto paramType = module_.FindDef(inst->words[paramTypeIndex]);
+ if (!paramType || !spvOpcodeGeneratesType(paramType->opcode())) {
DIAG(paramTypeIndex) << "OpTypeFunction Parameter Type <id> '"
<< inst->words[paramTypeIndex] << "' is not a type.";
return false;
@@ -421,8 +419,8 @@
bool idUsage::isValid<SpvOpConstantTrue>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto resultTypeIndex = 1;
- auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
- if (!resultType.first || SpvOpTypeBool != resultType.second.opcode) {
+ auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+ if (!resultType || SpvOpTypeBool != resultType->opcode()) {
DIAG(resultTypeIndex) << "OpConstantTrue Result Type <id> '"
<< inst->words[resultTypeIndex]
<< "' is not a boolean type.";
@@ -435,8 +433,8 @@
bool idUsage::isValid<SpvOpConstantFalse>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto resultTypeIndex = 1;
- auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
- if (!resultType.first || SpvOpTypeBool != resultType.second.opcode) {
+ auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+ if (!resultType || SpvOpTypeBool != resultType->opcode()) {
DIAG(resultTypeIndex) << "OpConstantFalse Result Type <id> '"
<< inst->words[resultTypeIndex]
<< "' is not a boolean type.";
@@ -449,8 +447,8 @@
bool idUsage::isValid<SpvOpConstantComposite>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto resultTypeIndex = 1;
- auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
- if (!resultType.first || !spvOpcodeIsComposite(resultType.second.opcode)) {
+ auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+ if (!resultType || !spvOpcodeIsComposite(resultType->opcode())) {
DIAG(resultTypeIndex) << "OpConstantComposite Result Type <id> '"
<< inst->words[resultTypeIndex]
<< "' is not a composite type.";
@@ -458,163 +456,155 @@
}
auto constituentCount = inst->words.size() - 3;
- switch (resultType.second.opcode) {
+ switch (resultType->opcode()) {
case SpvOpTypeVector: {
- auto componentCount = resultType.second.words[3];
+ auto componentCount = resultType->words()[3];
spvCheck(
componentCount != constituentCount,
// TODO: Output ID's on diagnostic
DIAG(inst->words.size() - 1)
<< "OpConstantComposite Constituent <id> count does not match "
"Result Type <id> '"
- << resultType.second.id << "'s vector component count.";
+ << resultType->id() << "'s vector component count.";
return false);
- auto componentType = usedefs_.FindDef(resultType.second.words[2]);
- assert(componentType.first);
+ auto componentType = module_.FindDef(resultType->words()[2]);
+ assert(componentType);
for (size_t constituentIndex = 3; constituentIndex < inst->words.size();
constituentIndex++) {
- auto constituent = usedefs_.FindDef(inst->words[constituentIndex]);
- if (!constituent.first ||
- !spvOpcodeIsConstant(constituent.second.opcode)) {
+ auto constituent = module_.FindDef(inst->words[constituentIndex]);
+ if (!constituent || !spvOpcodeIsConstant(constituent->opcode())) {
DIAG(constituentIndex) << "OpConstantComposite Constituent <id> '"
<< inst->words[constituentIndex]
<< "' is not a constant.";
return false;
}
- auto constituentResultType =
- usedefs_.FindDef(constituent.second.type_id);
- if (!constituentResultType.first ||
- componentType.second.opcode !=
- constituentResultType.second.opcode) {
+ auto constituentResultType = module_.FindDef(constituent->type_id());
+ if (!constituentResultType ||
+ componentType->opcode() != constituentResultType->opcode()) {
DIAG(constituentIndex) << "OpConstantComposite Constituent <id> '"
<< inst->words[constituentIndex]
<< "'s type does not match Result Type <id> '"
- << resultType.second.id
+ << resultType->id()
<< "'s vector element type.";
return false;
}
}
} break;
case SpvOpTypeMatrix: {
- auto columnCount = resultType.second.words[3];
+ auto columnCount = resultType->words()[3];
spvCheck(
columnCount != constituentCount,
// TODO: Output ID's on diagnostic
DIAG(inst->words.size() - 1)
<< "OpConstantComposite Constituent <id> count does not match "
"Result Type <id> '"
- << resultType.second.id << "'s matrix column count.";
+ << resultType->id() << "'s matrix column count.";
return false);
- auto columnType = usedefs_.FindDef(resultType.second.words[2]);
- assert(columnType.first);
- auto componentCount = columnType.second.words[3];
- auto componentType = usedefs_.FindDef(columnType.second.words[2]);
- assert(componentType.first);
+ auto columnType = module_.FindDef(resultType->words()[2]);
+ assert(columnType);
+ auto componentCount = columnType->words()[3];
+ auto componentType = module_.FindDef(columnType->words()[2]);
+ assert(componentType);
for (size_t constituentIndex = 3; constituentIndex < inst->words.size();
constituentIndex++) {
- auto constituent = usedefs_.FindDef(inst->words[constituentIndex]);
- if (!constituent.first ||
- SpvOpConstantComposite != constituent.second.opcode) {
+ auto constituent = module_.FindDef(inst->words[constituentIndex]);
+ if (!constituent || SpvOpConstantComposite != constituent->opcode()) {
DIAG(constituentIndex) << "OpConstantComposite Constituent <id> '"
<< inst->words[constituentIndex]
<< "' is not a constant composite.";
return false;
}
- auto vector = usedefs_.FindDef(constituent.second.type_id);
- assert(vector.first);
- spvCheck(columnType.second.opcode != vector.second.opcode,
+ auto vector = module_.FindDef(constituent->type_id());
+ assert(vector);
+ spvCheck(columnType->opcode() != vector->opcode(),
DIAG(constituentIndex)
<< "OpConstantComposite Constituent <id> '"
<< inst->words[constituentIndex]
<< "' type does not match Result Type <id> '"
- << resultType.second.id << "'s matrix column type.";
+ << resultType->id() << "'s matrix column type.";
return false);
- auto vectorComponentType = usedefs_.FindDef(vector.second.words[2]);
- assert(vectorComponentType.first);
- spvCheck(componentType.second.id != vectorComponentType.second.id,
+ auto vectorComponentType = module_.FindDef(vector->words()[2]);
+ assert(vectorComponentType);
+ spvCheck(componentType->id() != vectorComponentType->id(),
DIAG(constituentIndex)
<< "OpConstantComposite Constituent <id> '"
<< inst->words[constituentIndex]
<< "' component type does not match Result Type <id> '"
- << resultType.second.id
- << "'s matrix column component type.";
+ << resultType->id() << "'s matrix column component type.";
return false);
spvCheck(
- componentCount != vector.second.words[3],
+ componentCount != vector->words()[3],
DIAG(constituentIndex)
<< "OpConstantComposite Constituent <id> '"
<< inst->words[constituentIndex]
<< "' vector component count does not match Result Type <id> '"
- << resultType.second.id << "'s vector component count.";
+ << resultType->id() << "'s vector component count.";
return false);
}
} break;
case SpvOpTypeArray: {
- auto elementType = usedefs_.FindDef(resultType.second.words[2]);
- assert(elementType.first);
- auto length = usedefs_.FindDef(resultType.second.words[3]);
- assert(length.first);
- spvCheck(length.second.words[3] != constituentCount,
+ auto elementType = module_.FindDef(resultType->words()[2]);
+ assert(elementType);
+ auto length = module_.FindDef(resultType->words()[3]);
+ assert(length);
+ spvCheck(length->words()[3] != constituentCount,
DIAG(inst->words.size() - 1)
<< "OpConstantComposite Constituent count does not match "
"Result Type <id> '"
- << resultType.second.id << "'s array length.";
+ << resultType->id() << "'s array length.";
return false);
for (size_t constituentIndex = 3; constituentIndex < inst->words.size();
constituentIndex++) {
- auto constituent = usedefs_.FindDef(inst->words[constituentIndex]);
- if (!constituent.first ||
- !spvOpcodeIsConstant(constituent.second.opcode)) {
+ auto constituent = module_.FindDef(inst->words[constituentIndex]);
+ if (!constituent || !spvOpcodeIsConstant(constituent->opcode())) {
DIAG(constituentIndex) << "OpConstantComposite Constituent <id> '"
<< inst->words[constituentIndex]
<< "' is not a constant.";
return false;
}
- auto constituentType = usedefs_.FindDef(constituent.second.type_id);
- assert(constituentType.first);
- spvCheck(elementType.second.id != constituentType.second.id,
+ auto constituentType = module_.FindDef(constituent->type_id());
+ assert(constituentType);
+ spvCheck(elementType->id() != constituentType->id(),
DIAG(constituentIndex)
<< "OpConstantComposite Constituent <id> '"
<< inst->words[constituentIndex]
<< "'s type does not match Result Type <id> '"
- << resultType.second.id << "'s array element type.";
+ << resultType->id() << "'s array element type.";
return false);
}
} break;
case SpvOpTypeStruct: {
- auto memberCount = resultType.second.words.size() - 2;
+ auto memberCount = resultType->words().size() - 2;
spvCheck(memberCount != constituentCount,
DIAG(resultTypeIndex)
<< "OpConstantComposite Constituent <id> '"
<< inst->words[resultTypeIndex]
<< "' count does not match Result Type <id> '"
- << resultType.second.id << "'s struct member count.";
+ << resultType->id() << "'s struct member count.";
return false);
for (uint32_t constituentIndex = 3, memberIndex = 2;
constituentIndex < inst->words.size();
constituentIndex++, memberIndex++) {
- auto constituent = usedefs_.FindDef(inst->words[constituentIndex]);
- if (!constituent.first ||
- !spvOpcodeIsConstant(constituent.second.opcode)) {
+ auto constituent = module_.FindDef(inst->words[constituentIndex]);
+ if (!constituent || !spvOpcodeIsConstant(constituent->opcode())) {
DIAG(constituentIndex) << "OpConstantComposite Constituent <id> '"
<< inst->words[constituentIndex]
<< "' is not a constant.";
return false;
}
- auto constituentType = usedefs_.FindDef(constituent.second.type_id);
- assert(constituentType.first);
+ auto constituentType = module_.FindDef(constituent->type_id());
+ assert(constituentType);
- auto memberType =
- usedefs_.FindDef(resultType.second.words[memberIndex]);
- assert(memberType.first);
- spvCheck(memberType.second.id != constituentType.second.id,
+ auto memberType = module_.FindDef(resultType->words()[memberIndex]);
+ assert(memberType);
+ spvCheck(memberType->id() != constituentType->id(),
DIAG(constituentIndex)
<< "OpConstantComposite Constituent <id> '"
<< inst->words[constituentIndex]
<< "' type does not match the Result Type <id> '"
- << resultType.second.id << "'s member type.";
+ << resultType->id() << "'s member type.";
return false);
}
} break;
@@ -627,8 +617,8 @@
bool idUsage::isValid<SpvOpConstantSampler>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto resultTypeIndex = 1;
- auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
- if (!resultType.first || SpvOpTypeSampler != resultType.second.opcode) {
+ auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+ if (!resultType || SpvOpTypeSampler != resultType->opcode()) {
DIAG(resultTypeIndex) << "OpConstantSampler Result Type <id> '"
<< inst->words[resultTypeIndex]
<< "' is not a sampler type.";
@@ -638,10 +628,10 @@
}
// True if instruction defines a type that can have a null value, as defined by
-// the SPIR-V spec. Tracks composite-type components through usedefs to check
+// the SPIR-V spec. Tracks composite-type components through module to check
// nullability transitively.
bool IsTypeNullable(const std::vector<uint32_t>& instruction,
- const UseDefTracker& usedefs) {
+ const ValidationState_t& module) {
uint16_t opcode;
uint16_t word_count;
spvOpcodeSplit(instruction[0], &word_count, &opcode);
@@ -658,15 +648,14 @@
case SpvOpTypeArray:
case SpvOpTypeMatrix:
case SpvOpTypeVector: {
- auto base_type = usedefs.FindDef(instruction[2]);
- return base_type.first && IsTypeNullable(base_type.second.words, usedefs);
+ auto base_type = module.FindDef(instruction[2]);
+ return base_type && IsTypeNullable(base_type->words(), module);
}
case SpvOpTypeStruct: {
for (size_t elementIndex = 2; elementIndex < instruction.size();
++elementIndex) {
- auto element = usedefs.FindDef(instruction[elementIndex]);
- if (!element.first || !IsTypeNullable(element.second.words, usedefs))
- return false;
+ auto element = module.FindDef(instruction[elementIndex]);
+ if (!element || !IsTypeNullable(element->words(), module)) return false;
}
return true;
}
@@ -679,8 +668,8 @@
bool idUsage::isValid<SpvOpConstantNull>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto resultTypeIndex = 1;
- auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
- if (!resultType.first || !IsTypeNullable(resultType.second.words, usedefs_)) {
+ auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+ if (!resultType || !IsTypeNullable(resultType->words(), module_)) {
DIAG(resultTypeIndex) << "OpConstantNull Result Type <id> '"
<< inst->words[resultTypeIndex]
<< "' cannot have a null value.";
@@ -693,8 +682,8 @@
bool idUsage::isValid<SpvOpSpecConstantTrue>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto resultTypeIndex = 1;
- auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
- if (!resultType.first || SpvOpTypeBool != resultType.second.opcode) {
+ auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+ if (!resultType || SpvOpTypeBool != resultType->opcode()) {
DIAG(resultTypeIndex) << "OpSpecConstantTrue Result Type <id> '"
<< inst->words[resultTypeIndex]
<< "' is not a boolean type.";
@@ -707,8 +696,8 @@
bool idUsage::isValid<SpvOpSpecConstantFalse>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto resultTypeIndex = 1;
- auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
- if (!resultType.first || SpvOpTypeBool != resultType.second.opcode) {
+ auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+ if (!resultType || SpvOpTypeBool != resultType->opcode()) {
DIAG(resultTypeIndex) << "OpSpecConstantFalse Result Type <id> '"
<< inst->words[resultTypeIndex]
<< "' is not a boolean type.";
@@ -732,8 +721,8 @@
bool idUsage::isValid<SpvOpVariable>(const spv_instruction_t* inst,
const spv_opcode_desc opcodeEntry) {
auto resultTypeIndex = 1;
- auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
- if (!resultType.first || SpvOpTypePointer != resultType.second.opcode) {
+ auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+ if (!resultType || SpvOpTypePointer != resultType->opcode()) {
DIAG(resultTypeIndex) << "OpVariable Result Type <id> '"
<< inst->words[resultTypeIndex]
<< "' is not a pointer type.";
@@ -741,8 +730,8 @@
}
if (opcodeEntry->numTypes < inst->words.size()) {
auto initialiserIndex = 4;
- auto initialiser = usedefs_.FindDef(inst->words[initialiserIndex]);
- if (!initialiser.first || !spvOpcodeIsConstant(initialiser.second.opcode)) {
+ auto initialiser = module_.FindDef(inst->words[initialiserIndex]);
+ if (!initialiser || !spvOpcodeIsConstant(initialiser->opcode())) {
DIAG(initialiserIndex) << "OpVariable Initializer <id> '"
<< inst->words[initialiserIndex]
<< "' is not a constant.";
@@ -756,34 +745,32 @@
bool idUsage::isValid<SpvOpLoad>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto resultTypeIndex = 1;
- auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
- spvCheck(!resultType.first, DIAG(resultTypeIndex)
- << "OpLoad Result Type <id> '"
- << inst->words[resultTypeIndex]
- << "' is not defind.";
+ auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+ spvCheck(!resultType, DIAG(resultTypeIndex) << "OpLoad Result Type <id> '"
+ << inst->words[resultTypeIndex]
+ << "' is not defind.";
return false);
auto pointerIndex = 3;
- auto pointer = usedefs_.FindDef(inst->words[pointerIndex]);
- if (!pointer.first ||
- (addressingModel == SpvAddressingModelLogical &&
- !spvOpcodeReturnsLogicalPointer(pointer.second.opcode))) {
+ auto pointer = module_.FindDef(inst->words[pointerIndex]);
+ if (!pointer || (addressingModel == SpvAddressingModelLogical &&
+ !spvOpcodeReturnsLogicalPointer(pointer->opcode()))) {
DIAG(pointerIndex) << "OpLoad Pointer <id> '" << inst->words[pointerIndex]
<< "' is not a pointer.";
return false;
}
- auto pointerType = usedefs_.FindDef(pointer.second.type_id);
- if (!pointerType.first || pointerType.second.opcode != SpvOpTypePointer) {
+ auto pointerType = module_.FindDef(pointer->type_id());
+ if (!pointerType || pointerType->opcode() != SpvOpTypePointer) {
DIAG(pointerIndex) << "OpLoad type for pointer <id> '"
<< inst->words[pointerIndex]
<< "' is not a pointer type.";
return false;
}
- auto pointeeType = usedefs_.FindDef(pointerType.second.words[3]);
- if (!pointeeType.first || resultType.second.id != pointeeType.second.id) {
+ auto pointeeType = module_.FindDef(pointerType->words()[3]);
+ if (!pointeeType || resultType->id() != pointeeType->id()) {
DIAG(resultTypeIndex) << "OpLoad Result Type <id> '"
<< inst->words[resultTypeIndex]
- << "' does not match Pointer <id> '"
- << pointer.second.id << "'s type.";
+ << "' does not match Pointer <id> '" << pointer->id()
+ << "'s type.";
return false;
}
return true;
@@ -793,47 +780,46 @@
bool idUsage::isValid<SpvOpStore>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto pointerIndex = 1;
- auto pointer = usedefs_.FindDef(inst->words[pointerIndex]);
- if (!pointer.first ||
- (addressingModel == SpvAddressingModelLogical &&
- !spvOpcodeReturnsLogicalPointer(pointer.second.opcode))) {
+ auto pointer = module_.FindDef(inst->words[pointerIndex]);
+ if (!pointer || (addressingModel == SpvAddressingModelLogical &&
+ !spvOpcodeReturnsLogicalPointer(pointer->opcode()))) {
DIAG(pointerIndex) << "OpStore Pointer <id> '" << inst->words[pointerIndex]
<< "' is not a pointer.";
return false;
}
- auto pointerType = usedefs_.FindDef(pointer.second.type_id);
- if (!pointer.first || pointerType.second.opcode != SpvOpTypePointer) {
+ auto pointerType = module_.FindDef(pointer->type_id());
+ if (!pointer || pointerType->opcode() != SpvOpTypePointer) {
DIAG(pointerIndex) << "OpStore type for pointer <id> '"
<< inst->words[pointerIndex]
<< "' is not a pointer type.";
return false;
}
- auto type = usedefs_.FindDef(pointerType.second.words[3]);
- assert(type.first);
- spvCheck(SpvOpTypeVoid == type.second.opcode, DIAG(pointerIndex)
- << "OpStore Pointer <id> '"
- << inst->words[pointerIndex]
- << "'s type is void.";
+ auto type = module_.FindDef(pointerType->words()[3]);
+ assert(type);
+ spvCheck(SpvOpTypeVoid == type->opcode(), DIAG(pointerIndex)
+ << "OpStore Pointer <id> '"
+ << inst->words[pointerIndex]
+ << "'s type is void.";
return false);
auto objectIndex = 2;
- auto object = usedefs_.FindDef(inst->words[objectIndex]);
- if (!object.first || !object.second.type_id) {
+ auto object = module_.FindDef(inst->words[objectIndex]);
+ if (!object || !object->type_id()) {
DIAG(objectIndex) << "OpStore Object <id> '" << inst->words[objectIndex]
<< "' is not an object.";
return false;
}
- auto objectType = usedefs_.FindDef(object.second.type_id);
- assert(objectType.first);
- spvCheck(SpvOpTypeVoid == objectType.second.opcode,
+ auto objectType = module_.FindDef(object->type_id());
+ assert(objectType);
+ spvCheck(SpvOpTypeVoid == objectType->opcode(),
DIAG(objectIndex) << "OpStore Object <id> '"
<< inst->words[objectIndex] << "'s type is void.";
return false);
- spvCheck(type.second.id != objectType.second.id,
+ spvCheck(type->id() != objectType->id(),
DIAG(pointerIndex)
<< "OpStore Pointer <id> '" << inst->words[pointerIndex]
- << "'s type does not match Object <id> '" << objectType.second.id
+ << "'s type does not match Object <id> '" << objectType->id()
<< "'s type.";
return false);
return true;
@@ -843,23 +829,23 @@
bool idUsage::isValid<SpvOpCopyMemory>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto targetIndex = 1;
- auto target = usedefs_.FindDef(inst->words[targetIndex]);
- if (!target.first) return false;
+ auto target = module_.FindDef(inst->words[targetIndex]);
+ if (!target) return false;
auto sourceIndex = 2;
- auto source = usedefs_.FindDef(inst->words[sourceIndex]);
- if (!source.first) return false;
- auto targetPointerType = usedefs_.FindDef(target.second.type_id);
- assert(targetPointerType.first);
- auto targetType = usedefs_.FindDef(targetPointerType.second.words[3]);
- assert(targetType.first);
- auto sourcePointerType = usedefs_.FindDef(source.second.type_id);
- assert(sourcePointerType.first);
- auto sourceType = usedefs_.FindDef(sourcePointerType.second.words[3]);
- assert(sourceType.first);
- spvCheck(targetType.second.id != sourceType.second.id,
+ auto source = module_.FindDef(inst->words[sourceIndex]);
+ if (!source) return false;
+ auto targetPointerType = module_.FindDef(target->type_id());
+ assert(targetPointerType);
+ auto targetType = module_.FindDef(targetPointerType->words()[3]);
+ assert(targetType);
+ auto sourcePointerType = module_.FindDef(source->type_id());
+ assert(sourcePointerType);
+ auto sourceType = module_.FindDef(sourcePointerType->words()[3]);
+ assert(sourceType);
+ spvCheck(targetType->id() != sourceType->id(),
DIAG(sourceIndex)
<< "OpCopyMemory Target <id> '" << inst->words[sourceIndex]
- << "'s type does not match Source <id> '" << sourceType.second.id
+ << "'s type does not match Source <id> '" << sourceType->id()
<< "'s type.";
return false);
return true;
@@ -869,47 +855,45 @@
bool idUsage::isValid<SpvOpCopyMemorySized>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto targetIndex = 1;
- auto target = usedefs_.FindDef(inst->words[targetIndex]);
- if (!target.first) return false;
+ auto target = module_.FindDef(inst->words[targetIndex]);
+ if (!target) return false;
auto sourceIndex = 2;
- auto source = usedefs_.FindDef(inst->words[sourceIndex]);
- if (!source.first) return false;
+ auto source = module_.FindDef(inst->words[sourceIndex]);
+ if (!source) return false;
auto sizeIndex = 3;
- auto size = usedefs_.FindDef(inst->words[sizeIndex]);
- if (!size.first) return false;
- auto targetPointerType = usedefs_.FindDef(target.second.type_id);
- spvCheck(!targetPointerType.first ||
- SpvOpTypePointer != targetPointerType.second.opcode,
- DIAG(targetIndex) << "OpCopyMemorySized Target <id> '"
- << inst->words[targetIndex]
- << "' is not a pointer.";
- return false);
- auto sourcePointerType = usedefs_.FindDef(source.second.type_id);
- spvCheck(!sourcePointerType.first ||
- SpvOpTypePointer != sourcePointerType.second.opcode,
- DIAG(sourceIndex) << "OpCopyMemorySized Source <id> '"
- << inst->words[sourceIndex]
- << "' is not a pointer.";
- return false);
- switch (size.second.opcode) {
+ auto size = module_.FindDef(inst->words[sizeIndex]);
+ if (!size) return false;
+ auto targetPointerType = module_.FindDef(target->type_id());
+ spvCheck(
+ !targetPointerType || SpvOpTypePointer != targetPointerType->opcode(),
+ DIAG(targetIndex) << "OpCopyMemorySized Target <id> '"
+ << inst->words[targetIndex] << "' is not a pointer.";
+ return false);
+ auto sourcePointerType = module_.FindDef(source->type_id());
+ spvCheck(
+ !sourcePointerType || SpvOpTypePointer != sourcePointerType->opcode(),
+ DIAG(sourceIndex) << "OpCopyMemorySized Source <id> '"
+ << inst->words[sourceIndex] << "' is not a pointer.";
+ return false);
+ switch (size->opcode()) {
// TODO: The following opcode's are assumed to be valid, refer to the
// following bug https://cvs.khronos.org/bugzilla/show_bug.cgi?id=13871 for
// clarification
case SpvOpConstant:
case SpvOpSpecConstant: {
- auto sizeType = usedefs_.FindDef(size.second.type_id);
- assert(sizeType.first);
- spvCheck(SpvOpTypeInt != sizeType.second.opcode,
+ auto sizeType = module_.FindDef(size->type_id());
+ assert(sizeType);
+ spvCheck(SpvOpTypeInt != sizeType->opcode(),
DIAG(sizeIndex) << "OpCopyMemorySized Size <id> '"
<< inst->words[sizeIndex]
<< "'s type is not an integer type.";
return false);
} break;
case SpvOpVariable: {
- auto pointerType = usedefs_.FindDef(size.second.type_id);
- assert(pointerType.first);
- auto sizeType = usedefs_.FindDef(pointerType.second.type_id);
- spvCheck(!sizeType.first || SpvOpTypeInt != sizeType.second.opcode,
+ auto pointerType = module_.FindDef(size->type_id());
+ assert(pointerType);
+ auto sizeType = module_.FindDef(pointerType->type_id());
+ spvCheck(!sizeType || SpvOpTypeInt != sizeType->opcode(),
DIAG(sizeIndex) << "OpCopyMemorySized Size <id> '"
<< inst->words[sizeIndex]
<< "'s variable type is not an integer type.";
@@ -960,23 +944,23 @@
bool idUsage::isValid<SpvOpFunction>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto resultTypeIndex = 1;
- auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
- if (!resultType.first) return false;
+ auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+ if (!resultType) return false;
auto functionTypeIndex = 4;
- auto functionType = usedefs_.FindDef(inst->words[functionTypeIndex]);
- if (!functionType.first || SpvOpTypeFunction != functionType.second.opcode) {
+ auto functionType = module_.FindDef(inst->words[functionTypeIndex]);
+ if (!functionType || SpvOpTypeFunction != functionType->opcode()) {
DIAG(functionTypeIndex) << "OpFunction Function Type <id> '"
<< inst->words[functionTypeIndex]
<< "' is not a function type.";
return false;
}
- auto returnType = usedefs_.FindDef(functionType.second.words[2]);
- assert(returnType.first);
- spvCheck(returnType.second.id != resultType.second.id,
+ auto returnType = module_.FindDef(functionType->words()[2]);
+ assert(returnType);
+ spvCheck(returnType->id() != resultType->id(),
DIAG(resultTypeIndex) << "OpFunction Result Type <id> '"
<< inst->words[resultTypeIndex]
<< "' does not match the Function Type <id> '"
- << resultType.second.id << "'s return type.";
+ << resultType->id() << "'s return type.";
return false);
return true;
}
@@ -985,8 +969,8 @@
bool idUsage::isValid<SpvOpFunctionParameter>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto resultTypeIndex = 1;
- auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
- if (!resultType.first) return false;
+ auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+ if (!resultType) return false;
// NOTE: Find OpFunction & ensure OpFunctionParameter is not out of place.
size_t paramIndex = 0;
assert(firstInst < inst && "Invalid instruction pointer");
@@ -997,17 +981,17 @@
paramIndex++;
}
}
- auto functionType = usedefs_.FindDef(inst->words[4]);
- assert(functionType.first);
- if (paramIndex >= functionType.second.words.size() - 3) {
+ auto functionType = module_.FindDef(inst->words[4]);
+ assert(functionType);
+ if (paramIndex >= functionType->words().size() - 3) {
DIAG(0) << "Too many OpFunctionParameters for " << inst->words[2]
- << ": expected " << functionType.second.words.size() - 3
+ << ": expected " << functionType->words().size() - 3
<< " based on the function's type";
return false;
}
- auto paramType = usedefs_.FindDef(functionType.second.words[paramIndex + 3]);
- assert(paramType.first);
- spvCheck(resultType.second.id != paramType.second.id,
+ auto paramType = module_.FindDef(functionType->words()[paramIndex + 3]);
+ assert(paramType);
+ spvCheck(resultType->id() != paramType->id(),
DIAG(resultTypeIndex)
<< "OpFunctionParameter Result Type <id> '"
<< inst->words[resultTypeIndex]
@@ -1021,27 +1005,27 @@
bool idUsage::isValid<SpvOpFunctionCall>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto resultTypeIndex = 1;
- auto resultType = usedefs_.FindDef(inst->words[resultTypeIndex]);
- if (!resultType.first) return false;
+ auto resultType = module_.FindDef(inst->words[resultTypeIndex]);
+ if (!resultType) return false;
auto functionIndex = 3;
- auto function = usedefs_.FindDef(inst->words[functionIndex]);
- if (!function.first || SpvOpFunction != function.second.opcode) {
+ auto function = module_.FindDef(inst->words[functionIndex]);
+ if (!function || SpvOpFunction != function->opcode()) {
DIAG(functionIndex) << "OpFunctionCall Function <id> '"
<< inst->words[functionIndex] << "' is not a function.";
return false;
}
- auto returnType = usedefs_.FindDef(function.second.type_id);
- assert(returnType.first);
- spvCheck(returnType.second.id != resultType.second.id,
+ auto returnType = module_.FindDef(function->type_id());
+ assert(returnType);
+ spvCheck(returnType->id() != resultType->id(),
DIAG(resultTypeIndex) << "OpFunctionCall Result Type <id> '"
<< inst->words[resultTypeIndex]
<< "'s type does not match Function <id> '"
- << returnType.second.id << "'s return type.";
+ << returnType->id() << "'s return type.";
return false);
- auto functionType = usedefs_.FindDef(function.second.words[4]);
- assert(functionType.first);
+ auto functionType = module_.FindDef(function->words()[4]);
+ assert(functionType);
auto functionCallArgCount = inst->words.size() - 4;
- auto functionParamCount = functionType.second.words.size() - 3;
+ auto functionParamCount = functionType->words().size() - 3;
spvCheck(
functionParamCount != functionCallArgCount,
DIAG(inst->words.size() - 1)
@@ -1050,19 +1034,17 @@
return false);
for (size_t argumentIndex = 4, paramIndex = 3;
argumentIndex < inst->words.size(); argumentIndex++, paramIndex++) {
- auto argument = usedefs_.FindDef(inst->words[argumentIndex]);
- if (!argument.first) return false;
- auto argumentType = usedefs_.FindDef(argument.second.type_id);
- assert(argumentType.first);
- auto parameterType =
- usedefs_.FindDef(functionType.second.words[paramIndex]);
- assert(parameterType.first);
- spvCheck(argumentType.second.id != parameterType.second.id,
+ auto argument = module_.FindDef(inst->words[argumentIndex]);
+ if (!argument) return false;
+ auto argumentType = module_.FindDef(argument->type_id());
+ assert(argumentType);
+ auto parameterType = module_.FindDef(functionType->words()[paramIndex]);
+ assert(parameterType);
+ spvCheck(argumentType->id() != parameterType->id(),
DIAG(argumentIndex) << "OpFunctionCall Argument <id> '"
<< inst->words[argumentIndex]
<< "'s type does not match Function <id> '"
- << parameterType.second.id
- << "'s parameter type.";
+ << parameterType->id() << "'s parameter type.";
return false);
}
return true;
@@ -1698,22 +1680,22 @@
bool idUsage::isValid<SpvOpReturnValue>(const spv_instruction_t* inst,
const spv_opcode_desc) {
auto valueIndex = 1;
- auto value = usedefs_.FindDef(inst->words[valueIndex]);
- if (!value.first || !value.second.type_id) {
+ auto value = module_.FindDef(inst->words[valueIndex]);
+ if (!value || !value->type_id()) {
DIAG(valueIndex) << "OpReturnValue Value <id> '" << inst->words[valueIndex]
<< "' does not represent a value.";
return false;
}
- auto valueType = usedefs_.FindDef(value.second.type_id);
- if (!valueType.first || SpvOpTypeVoid == valueType.second.opcode) {
- DIAG(valueIndex) << "OpReturnValue value's type <id> '"
- << value.second.type_id << "' is missing or void.";
+ auto valueType = module_.FindDef(value->type_id());
+ if (!valueType || SpvOpTypeVoid == valueType->opcode()) {
+ DIAG(valueIndex) << "OpReturnValue value's type <id> '" << value->type_id()
+ << "' is missing or void.";
return false;
}
if (addressingModel == SpvAddressingModelLogical &&
- SpvOpTypePointer == valueType.second.opcode) {
+ SpvOpTypePointer == valueType->opcode()) {
DIAG(valueIndex)
- << "OpReturnValue value's type <id> '" << value.second.type_id
+ << "OpReturnValue value's type <id> '" << value->type_id()
<< "' is a pointer, which is invalid in the Logical addressing model.";
return false;
}
@@ -1726,8 +1708,8 @@
spvCheck(SpvOpFunction != function->opcode,
DIAG(valueIndex) << "OpReturnValue is not in a basic block.";
return false);
- auto returnType = usedefs_.FindDef(function->words[1]);
- spvCheck(!returnType.first || returnType.second.id != valueType.second.id,
+ auto returnType = module_.FindDef(function->words[1]);
+ spvCheck(!returnType || returnType->id() != valueType->id(),
DIAG(valueIndex)
<< "OpReturnValue Value <id> '" << inst->words[valueIndex]
<< "'s type does not match OpFunction's return type.";
@@ -2338,8 +2320,176 @@
#undef TODO
#undef CASE
}
+// This function takes the opcode of an instruction and returns
+// a function object that will return true if the index
+// of the operand can be forwarad declared. This function will
+// used in the SSA validation stage of the pipeline
+function<bool(unsigned)> getCanBeForwardDeclaredFunction(SpvOp opcode) {
+ function<bool(unsigned index)> out;
+ switch (opcode) {
+ case SpvOpExecutionMode:
+ case SpvOpEntryPoint:
+ case SpvOpName:
+ case SpvOpMemberName:
+ case SpvOpSelectionMerge:
+ case SpvOpDecorate:
+ case SpvOpMemberDecorate:
+ case SpvOpBranch:
+ case SpvOpLoopMerge:
+ out = [](unsigned) { return true; };
+ break;
+ case SpvOpGroupDecorate:
+ case SpvOpGroupMemberDecorate:
+ case SpvOpBranchConditional:
+ case SpvOpSwitch:
+ out = [](unsigned index) { return index != 0; };
+ break;
+
+ case SpvOpFunctionCall:
+ out = [](unsigned index) { return index == 2; };
+ break;
+
+ case SpvOpPhi:
+ out = [](unsigned index) { return index > 1; };
+ break;
+
+ case SpvOpEnqueueKernel:
+ out = [](unsigned index) { return index == 8; };
+ break;
+
+ case SpvOpGetKernelNDrangeSubGroupCount:
+ case SpvOpGetKernelNDrangeMaxSubGroupSize:
+ out = [](unsigned index) { return index == 3; };
+ break;
+
+ case SpvOpGetKernelWorkGroupSize:
+ case SpvOpGetKernelPreferredWorkGroupSizeMultiple:
+ out = [](unsigned index) { return index == 2; };
+ break;
+
+ default:
+ out = [](unsigned) { return false; };
+ break;
+ }
+ return out;
+}
} // anonymous namespace
+namespace libspirv {
+
+/// This function checks all ID definitions dominate their use in the CFG.
+///
+/// This function will iterate over all ID definitions that are defined in the
+/// functions of a module and make sure that the definitions appear in a
+/// block that dominates their use.
+///
+/// NOTE: This function does NOT check module scoped functions which are
+/// checked during the initial binary parse in the IdPass below
+spv_result_t CheckIdDefinitionDominateUse(const ValidationState_t& _) {
+ for (const auto& definition : _.all_definitions()) {
+ // Check only those blocks defined in a function
+ if (const Function* func = definition.second.defining_function()) {
+ if (const BasicBlock* block = definition.second.defining_block()) {
+ // If the Id is defined within a block then make sure all references to
+ // that Id appear in a blocks that are dominated by the defining block
+ for (auto use : definition.second.uses()) {
+ if (!use->reachable()) continue;
+ if (use->dom_end() == find(use->dom_begin(), use->dom_end(), block)) {
+ return _.diag(SPV_ERROR_INVALID_ID)
+ << "ID " << _.getIdName(definition.first)
+ << " defined in block " << _.getIdName(block->id())
+ << " does not dominate its use in block "
+ << _.getIdName(use->id());
+ }
+ }
+ } else {
+ // If the Ids defined within a function but not in a block(i.e. function
+ // parameters, block ids), then make sure all references to that Id
+ // appear within the same function
+ bool found = false;
+ for (auto use : definition.second.uses()) {
+ tie(ignore, found) = func->GetBlock(use->id());
+ if (!found) {
+ return _.diag(SPV_ERROR_INVALID_ID)
+ << "ID " << _.getIdName(definition.first)
+ << " used in block " << _.getIdName(use->id())
+ << " is used outside of it's defining function "
+ << _.getIdName(func->id());
+ }
+ }
+ }
+ }
+ // NOTE: Ids defined outside of functions must appear before they are used
+ // This check is being performed in the IdPass function
+ }
+ // TODO(dneto): We don't track check of IDs by phi nodes. We should check
+ // that for each (variable, predecessor) pair in an OpPhi, that the variable
+ // is defined in a block that dominates that predecessor block.
+ return SPV_SUCCESS;
+}
+
+// Performs SSA validation on the IDs of an instruction. The
+// can_have_forward_declared_ids functor should return true if the
+// instruction operand's ID can be forward referenced.
+spv_result_t IdPass(ValidationState_t& _,
+ const spv_parsed_instruction_t* inst) {
+ auto can_have_forward_declared_ids =
+ getCanBeForwardDeclaredFunction(static_cast<SpvOp>(inst->opcode));
+
+ for (unsigned i = 0; i < inst->num_operands; i++) {
+ const spv_parsed_operand_t& operand = inst->operands[i];
+ const spv_operand_type_t& type = operand.type;
+ const uint32_t* operand_ptr = inst->words + operand.offset;
+
+ auto ret = SPV_ERROR_INTERNAL;
+ switch (type) {
+ case SPV_OPERAND_TYPE_RESULT_ID:
+ // NOTE: Multiple Id definitions are being checked by the binary parser
+ // NOTE: result Id is added *after* all of the other Ids have been
+ // checked to avoid premature use in the same instruction
+ _.RemoveIfForwardDeclared(*operand_ptr);
+ ret = SPV_SUCCESS;
+ break;
+ case SPV_OPERAND_TYPE_ID:
+ case SPV_OPERAND_TYPE_TYPE_ID:
+ case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
+ case SPV_OPERAND_TYPE_SCOPE_ID:
+ if (_.IsDefinedId(*operand_ptr)) {
+ if (inst->opcode == SpvOpPhi && i > 1) {
+ // For now, ignore uses of IDs as arguments to OpPhi, since
+ // the job of an OpPhi is to allow a block to use an ID from a
+ // block that doesn't dominate the use.
+ // We only track usage by a particular block, rather than
+ // which instruction and operand number is using the value, so
+ // we have to just bluntly avod tracking the use here.
+ // Fixes https://github.com/KhronosGroup/SPIRV-Tools/issues/286
+ } else {
+ _.RegisterUseId(*operand_ptr);
+ }
+ ret = SPV_SUCCESS;
+ } else if (can_have_forward_declared_ids(i)) {
+ ret = _.ForwardDeclareId(*operand_ptr);
+ } else {
+ ret = _.diag(SPV_ERROR_INVALID_ID) << "ID "
+ << _.getIdName(*operand_ptr)
+ << " has not been defined";
+ }
+ break;
+ default:
+ ret = SPV_SUCCESS;
+ break;
+ }
+ if (SPV_SUCCESS != ret) {
+ return ret;
+ }
+ }
+ if (inst->result_id) {
+ _.AddId(*inst);
+ }
+ return SPV_SUCCESS;
+}
+} // namespace libspirv
+
spv_result_t spvValidateInstructionIDs(const spv_instruction_t* pInsts,
const uint64_t instCount,
const spv_opcode_table opcodeTable,
@@ -2349,8 +2499,8 @@
spv_position position,
spv_diagnostic* pDiag) {
idUsage idUsage(opcodeTable, operandTable, extInstTable, pInsts, instCount,
- state.memory_model(), state.addressing_model(),
- state.usedefs(), state.entry_points(), position, pDiag);
+ state.memory_model(), state.addressing_model(), state,
+ state.entry_points(), position, pDiag);
for (uint64_t instIndex = 0; instIndex < instCount; ++instIndex) {
spvCheck(!idUsage.isValid(&pInsts[instIndex]), return SPV_ERROR_INVALID_ID);
position->index += pInsts[instIndex].words.size();
diff --git a/source/validate_ssa.cpp b/source/validate_ssa.cpp
deleted file mode 100644
index 914d5ca..0000000
--- a/source/validate_ssa.cpp
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright (c) 2015-2016 The Khronos Group Inc.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and/or associated documentation files (the
-// "Materials"), to deal in the Materials without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Materials, and to
-// permit persons to whom the Materials are furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be included
-// in all copies or substantial portions of the Materials.
-//
-// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
-// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
-// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
-// https://www.khronos.org/registry/
-//
-// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
-
-#include "validate.h"
-
-#include <functional>
-
-#include "opcode.h"
-#include "val/ValidationState.h"
-
-using std::function;
-using libspirv::ValidationState_t;
-
-namespace {
-// This function takes the opcode of an instruction and returns
-// a function object that will return true if the index
-// of the operand can be forwarad declared. This function will
-// used in the SSA validation stage of the pipeline
-function<bool(unsigned)> getCanBeForwardDeclaredFunction(SpvOp opcode) {
- function<bool(unsigned index)> out;
- switch (opcode) {
- case SpvOpExecutionMode:
- case SpvOpEntryPoint:
- case SpvOpName:
- case SpvOpMemberName:
- case SpvOpSelectionMerge:
- case SpvOpDecorate:
- case SpvOpMemberDecorate:
- case SpvOpBranch:
- case SpvOpLoopMerge:
- out = [](unsigned) { return true; };
- break;
- case SpvOpGroupDecorate:
- case SpvOpGroupMemberDecorate:
- case SpvOpBranchConditional:
- case SpvOpSwitch:
- out = [](unsigned index) { return index != 0; };
- break;
-
- case SpvOpFunctionCall:
- out = [](unsigned index) { return index == 2; };
- break;
-
- case SpvOpPhi:
- out = [](unsigned index) { return index > 1; };
- break;
-
- case SpvOpEnqueueKernel:
- out = [](unsigned index) { return index == 8; };
- break;
-
- case SpvOpGetKernelNDrangeSubGroupCount:
- case SpvOpGetKernelNDrangeMaxSubGroupSize:
- out = [](unsigned index) { return index == 3; };
- break;
-
- case SpvOpGetKernelWorkGroupSize:
- case SpvOpGetKernelPreferredWorkGroupSizeMultiple:
- out = [](unsigned index) { return index == 2; };
- break;
-
- default:
- out = [](unsigned) { return false; };
- break;
- }
- return out;
-}
-} // namespace
-
-namespace libspirv {
-
-// Performs SSA validation on the IDs of an instruction. The
-// can_have_forward_declared_ids functor should return true if the
-// instruction operand's ID can be forward referenced.
-//
-// TODO(umar): Use dominators to correctly validate SSA. For example, the result
-// id from a 'then' block cannot dominate its usage in the 'else' block. This
-// is not yet performed by this function.
-spv_result_t SsaPass(ValidationState_t& _,
- const spv_parsed_instruction_t* inst) {
- auto can_have_forward_declared_ids =
- getCanBeForwardDeclaredFunction(static_cast<SpvOp>(inst->opcode));
-
- for (unsigned i = 0; i < inst->num_operands; i++) {
- const spv_parsed_operand_t& operand = inst->operands[i];
- const spv_operand_type_t& type = operand.type;
- const uint32_t* operand_ptr = inst->words + operand.offset;
-
- auto ret = SPV_ERROR_INTERNAL;
- switch (type) {
- case SPV_OPERAND_TYPE_RESULT_ID:
- _.RemoveIfForwardDeclared(*operand_ptr);
- ret = SPV_SUCCESS;
- break;
- case SPV_OPERAND_TYPE_ID:
- case SPV_OPERAND_TYPE_TYPE_ID:
- case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
- case SPV_OPERAND_TYPE_SCOPE_ID:
- if (_.IsDefinedId(*operand_ptr)) {
- ret = SPV_SUCCESS;
- } else if (can_have_forward_declared_ids(i)) {
- ret = _.ForwardDeclareId(*operand_ptr);
- } else {
- ret = _.diag(SPV_ERROR_INVALID_ID) << "ID "
- << _.getIdName(*operand_ptr)
- << " has not been defined";
- }
- break;
- default:
- ret = SPV_SUCCESS;
- break;
- }
- if (SPV_SUCCESS != ret) {
- return ret;
- }
- }
- return SPV_SUCCESS;
-}
-} // namespace libspirv
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 971e5ac..1ac7a6b 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -30,118 +30,122 @@
# SRCS src_file.h src_file.cpp
# LIBS lib1 lib2
# )
-function(add_spvtools_unittest)
- set(one_value_args TARGET)
- set(multi_value_args SRCS LIBS)
- cmake_parse_arguments(
- ARG "" "${one_value_args}" "${multi_value_args}" ${ARGN})
- add_executable(${ARG_TARGET} ${ARG_SRCS})
- spvtools_default_compile_options(${ARG_TARGET})
- if(${COMPILER_IS_LIKE_GNU})
- target_compile_options(${ARG_TARGET} PRIVATE -Wno-undef)
- endif()
- if(${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC")
- # Disable C4503 "decorated name length exceeded" warning,
- # triggered by some heavily templated types.
- # We don't care much about that in test code.
- # Important to do since we have warnings-as-errors.
- target_compile_options(${ARG_TARGET} PRIVATE /wd4503)
- endif()
- target_include_directories(${ARG_TARGET} PRIVATE
- ${spirv-tools_SOURCE_DIR}
- ${spirv-tools_SOURCE_DIR}/include
- ${SPIRV_HEADER_INCLUDE_DIR}
- ${gtest_SOURCE_DIR}/include
- ${gmock_SOURCE_DIR}/include
- )
- target_link_libraries(${ARG_TARGET} PRIVATE ${ARG_LIBS})
- target_link_libraries(${ARG_TARGET} PRIVATE gmock_main)
- add_test(NAME spirv-tools-${ARG_TARGET} COMMAND ${ARG_TARGET})
-endfunction()
-if (NOT ${SPIRV_SKIP_EXECUTABLES})
- if (TARGET gmock)
+if (NOT "${SPIRV_SKIP_TESTS}")
+ if (TARGET gmock_main)
message(STATUS "Found Google Mock, building tests.")
-
- set(TEST_SOURCES
- ${CMAKE_CURRENT_SOURCE_DIR}/TestFixture.h
- ${CMAKE_CURRENT_SOURCE_DIR}/UnitSPIRV.h
-
- ${CMAKE_CURRENT_SOURCE_DIR}/AssemblyContext.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/AssemblyFormat.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/BinaryDestroy.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/BinaryEndianness.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/BinaryHeaderGet.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/BinaryParse.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/BinaryToText.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/BinaryToText.Literal.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/Comment.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/DiagnosticDestroy.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/DiagnosticPrint.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/DiagnosticStream.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/ExtInstGLSLstd450.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/ExtInst.OpenCL.std.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/FixWord.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/GeneratorMagicNumber.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/HexFloat.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/ImmediateInt.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/LibspirvMacros.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/NamedId.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/NameMapper.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/OpcodeMake.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/OpcodeRequiresCapabilities.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/OpcodeSplit.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/OpcodeTableGet.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/OperandCapabilities.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/Operand.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/OperandPattern.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/SoftwareVersion.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextAdvance.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextDestroy.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextLiteral.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextStartsNewInst.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Annotation.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Barrier.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Constant.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.ControlFlow.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Debug.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.DeviceSideEnqueue.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Extension.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Function.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Group.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Image.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Literal.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Memory.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Miscellaneous.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.ModeSetting.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.PipeStorage.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.TypeDeclaration.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.SubgroupDispatch.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TextWordGet.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/UnitSPIRV.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/ValidateFixtures.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/Validate.Capability.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/Validate.CFG.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/Validate.Layout.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/Validate.Storage.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/Validate.SSA.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/ValidateID.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/ValidationState.cpp
- )
- add_spvtools_unittest(
- TARGET UnitSPIRV
- SRCS ${TEST_SOURCES}
- LIBS ${SPIRV_TOOLS})
-
- add_spvtools_unittest(
- TARGET cpp_interface
- SRCS cpp_interface.cpp
- LIBS SPIRV-Tools-opt ${SPIRV_TOOLS})
-
- add_subdirectory(opt)
else()
message(STATUS "Did not find googletest, tests will not be built."
"To enable tests place googletest in '<spirv-dir>/external/googletest'.")
endif()
endif()
+
+function(add_spvtools_unittest)
+ if (NOT "${SPIRV_SKIP_TESTS}" AND TARGET gmock_main)
+ set(one_value_args TARGET)
+ set(multi_value_args SRCS LIBS)
+ cmake_parse_arguments(
+ ARG "" "${one_value_args}" "${multi_value_args}" ${ARGN})
+ add_executable(${ARG_TARGET} ${ARG_SRCS})
+ spvtools_default_compile_options(${ARG_TARGET})
+ if(${COMPILER_IS_LIKE_GNU})
+ target_compile_options(${ARG_TARGET} PRIVATE -Wno-undef)
+ endif()
+ if(${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC")
+ # Disable C4503 "decorated name length exceeded" warning,
+ # triggered by some heavily templated types.
+ # We don't care much about that in test code.
+ # Important to do since we have warnings-as-errors.
+ target_compile_options(${ARG_TARGET} PRIVATE /wd4503)
+ endif()
+ target_include_directories(${ARG_TARGET} PRIVATE
+ ${spirv-tools_SOURCE_DIR}
+ ${spirv-tools_SOURCE_DIR}/include
+ ${SPIRV_HEADER_INCLUDE_DIR}
+ ${gtest_SOURCE_DIR}/include
+ ${gmock_SOURCE_DIR}/include
+ )
+ target_link_libraries(${ARG_TARGET} PRIVATE ${ARG_LIBS})
+ target_link_libraries(${ARG_TARGET} PRIVATE gmock_main)
+ add_test(NAME spirv-tools-${ARG_TARGET} COMMAND ${ARG_TARGET})
+ endif()
+endfunction()
+
+set(TEST_SOURCES
+ ${CMAKE_CURRENT_SOURCE_DIR}/TestFixture.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/UnitSPIRV.h
+
+ ${CMAKE_CURRENT_SOURCE_DIR}/AssemblyContext.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/AssemblyFormat.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/BinaryDestroy.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/BinaryEndianness.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/BinaryHeaderGet.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/BinaryParse.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/BinaryToText.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/BinaryToText.Literal.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/Comment.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/DiagnosticDestroy.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/DiagnosticPrint.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/DiagnosticStream.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/ExtInstGLSLstd450.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/ExtInst.OpenCL.std.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/FixWord.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/GeneratorMagicNumber.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/HexFloat.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/ImmediateInt.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/LibspirvMacros.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/NamedId.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/NameMapper.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/OpcodeMake.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/OpcodeRequiresCapabilities.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/OpcodeSplit.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/OpcodeTableGet.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/OperandCapabilities.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/Operand.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/OperandPattern.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/SoftwareVersion.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TargetEnv.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextAdvance.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextDestroy.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextLiteral.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextStartsNewInst.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Annotation.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Barrier.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Constant.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.ControlFlow.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Debug.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.DeviceSideEnqueue.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Extension.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Function.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Group.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Image.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Literal.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Memory.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.Miscellaneous.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.ModeSetting.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.PipeStorage.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.TypeDeclaration.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextToBinary.SubgroupDispatch.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TextWordGet.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/UnitSPIRV.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/ValidateFixtures.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/Validate.Capability.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/Validate.CFG.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/Validate.Layout.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/Validate.Storage.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/Validate.SSA.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/ValidateID.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/ValidationState.cpp
+)
+add_spvtools_unittest(
+ TARGET UnitSPIRV
+ SRCS ${TEST_SOURCES}
+ LIBS ${SPIRV_TOOLS})
+
+add_spvtools_unittest(
+ TARGET cpp_interface
+ SRCS cpp_interface.cpp
+ LIBS SPIRV-Tools-opt ${SPIRV_TOOLS})
+
+add_subdirectory(opt)
diff --git a/test/NameMapper.cpp b/test/NameMapper.cpp
index e3332cf..fdb709b 100644
--- a/test/NameMapper.cpp
+++ b/test/NameMapper.cpp
@@ -134,6 +134,13 @@
{"OpName %1 \"abcdefg\"", 1, "abcdefg"},
{"OpName %1 \"Hello world!\"", 1, "Hello_world_"},
{"OpName %1 \"0123456789\"", 1, "0123456789"},
+ {"OpName %1 \"_\"", 1, "_"},
+ // An empty string is not valid for SPIR-V assembly IDs.
+ {"OpName %1 \"\"", 1, "_"},
+ // Test uniqueness when presented with things mapping to "_"
+ {"OpName %1 \"\" OpName %2 \"\"", 1, "_"},
+ {"OpName %1 \"\" OpName %2 \"\"", 2, "__0"},
+ {"OpName %1 \"\" OpName %2 \"\" OpName %3 \"_\"", 3, "__1"},
// Test uniqueness of names that are forced to be
// numbers.
{"OpName %1 \"2\" OpName %2 \"2\"", 1, "2"},
diff --git a/test/OpcodeTableGet.cpp b/test/OpcodeTableGet.cpp
index b9fe3c1..5e220b6 100644
--- a/test/OpcodeTableGet.cpp
+++ b/test/OpcodeTableGet.cpp
@@ -30,24 +30,21 @@
namespace {
-using GetTargetTest = ::testing::TestWithParam<spv_target_env>;
+using GetTargetOpcodeTableGetTest = ::testing::TestWithParam<spv_target_env>;
using ::testing::ValuesIn;
-using std::vector;
-TEST_P(GetTargetTest, SanityCheck) {
+TEST_P(GetTargetOpcodeTableGetTest, SanityCheck) {
spv_opcode_table table;
ASSERT_EQ(SPV_SUCCESS, spvOpcodeTableGet(&table, GetParam()));
ASSERT_NE(0u, table->count);
ASSERT_NE(nullptr, table->entries);
}
-TEST_P(GetTargetTest, InvalidPointerTable) {
+TEST_P(GetTargetOpcodeTableGetTest, InvalidPointerTable) {
ASSERT_EQ(SPV_ERROR_INVALID_POINTER, spvOpcodeTableGet(nullptr, GetParam()));
}
-INSTANTIATE_TEST_CASE_P(OpcodeTableGet, GetTargetTest,
- ValuesIn(vector<spv_target_env>{SPV_ENV_UNIVERSAL_1_0,
- SPV_ENV_UNIVERSAL_1_1,
- SPV_ENV_VULKAN_1_0}), );
+INSTANTIATE_TEST_CASE_P(OpcodeTableGet, GetTargetOpcodeTableGetTest,
+ ValuesIn(spvtest::AllTargetEnvironments()));
} // anonymous namespace
diff --git a/test/TargetEnv.cpp b/test/TargetEnv.cpp
new file mode 100644
index 0000000..235e6d7
--- /dev/null
+++ b/test/TargetEnv.cpp
@@ -0,0 +1,100 @@
+// Copyright (c) 2016 Google Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+// https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#include <gmock/gmock.h>
+
+#include "UnitSPIRV.h"
+
+#include "source/spirv_target_env.h"
+
+namespace {
+
+using ::testing::AnyOf;
+using ::testing::Eq;
+using ::testing::ValuesIn;
+using ::testing::StartsWith;
+
+using TargetEnvTest = ::testing::TestWithParam<spv_target_env>;
+TEST_P(TargetEnvTest, CreateContext) {
+ spv_target_env env = GetParam();
+ spv_context context = spvContextCreate(env);
+ ASSERT_NE(nullptr, context);
+ spvContextDestroy(context); // Avoid leaking
+}
+
+TEST_P(TargetEnvTest, ValidDescription) {
+ const char* description = spvTargetEnvDescription(GetParam());
+ ASSERT_NE(nullptr, description);
+ ASSERT_THAT(description, StartsWith("SPIR-V "));
+}
+
+TEST_P(TargetEnvTest, ValidSpirvVersion) {
+ auto spirv_version = spvVersionForTargetEnv(GetParam());
+ ASSERT_THAT(spirv_version, AnyOf(0x10000, 0x10100));
+}
+
+INSTANTIATE_TEST_CASE_P(AllTargetEnvs, TargetEnvTest,
+ ValuesIn(spvtest::AllTargetEnvironments()));
+
+TEST(GetContextTest, InvalidTargetEnvProducesNull) {
+ spv_context context = spvContextCreate((spv_target_env)10);
+ EXPECT_EQ(context, nullptr);
+}
+
+// A test case for parsing an environment string.
+struct ParseCase {
+ const char* input;
+ bool success; // Expect to successfully parse?
+ spv_target_env env; // The parsed environment, if successful.
+};
+
+using TargetParseTest = ::testing::TestWithParam<ParseCase>;
+
+TEST_P(TargetParseTest, InvalidTargetEnvProducesNull) {
+ spv_target_env env;
+ bool parsed = spvParseTargetEnv(GetParam().input, &env);
+ EXPECT_THAT(parsed, Eq(GetParam().success));
+ EXPECT_THAT(env, Eq(GetParam().env));
+}
+
+INSTANTIATE_TEST_CASE_P(TargetParsing, TargetParseTest,
+ ValuesIn(std::vector<ParseCase>{
+ {"spv1.0", true, SPV_ENV_UNIVERSAL_1_0},
+ {"spv1.1", true, SPV_ENV_UNIVERSAL_1_1},
+ {"vulkan1.0", true, SPV_ENV_VULKAN_1_0},
+ {"opencl2.1", true, SPV_ENV_OPENCL_2_1},
+ {"opencl2.2", true, SPV_ENV_OPENCL_2_2},
+ {"opengl4.0", true, SPV_ENV_OPENGL_4_0},
+ {"opengl4.1", true, SPV_ENV_OPENGL_4_1},
+ {"opengl4.2", true, SPV_ENV_OPENGL_4_2},
+ {"opengl4.3", true, SPV_ENV_OPENGL_4_3},
+ {"opengl4.5", true, SPV_ENV_OPENGL_4_5},
+ {nullptr, false, SPV_ENV_UNIVERSAL_1_0},
+ {"", false, SPV_ENV_UNIVERSAL_1_0},
+ {"abc", false, SPV_ENV_UNIVERSAL_1_0},
+ }));
+
+} // anonymous namespace
diff --git a/test/UnitSPIRV.h b/test/UnitSPIRV.h
index 93bf791..5456672 100644
--- a/test/UnitSPIRV.h
+++ b/test/UnitSPIRV.h
@@ -216,5 +216,13 @@
return result;
}
+// Returns a vector of all valid target environment enums.
+inline std::vector<spv_target_env> AllTargetEnvironments() {
+ return {SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, SPV_ENV_OPENCL_2_1,
+ SPV_ENV_OPENCL_2_2, SPV_ENV_VULKAN_1_0, SPV_ENV_OPENGL_4_0,
+ SPV_ENV_OPENGL_4_1, SPV_ENV_OPENGL_4_2, SPV_ENV_OPENGL_4_3,
+ SPV_ENV_OPENGL_4_5};
+}
+
} // namespace spvtest
#endif // LIBSPIRV_TEST_UNITSPIRV_H_
diff --git a/test/Validate.CFG.cpp b/test/Validate.CFG.cpp
index 4027946..6a1920e 100644
--- a/test/Validate.CFG.cpp
+++ b/test/Validate.CFG.cpp
@@ -91,8 +91,13 @@
: label_(label), body_(), type_(type), successors_() {}
/// Sets the instructions which will appear in the body of the block
- Block& setBody(std::string body) {
- body_ = body;
+ Block& SetBody(std::string body) {
+ body_ = body;
+ return *this;
+ }
+
+ Block& AppendBody(std::string body) {
+ body_ += body;
return *this;
}
@@ -189,6 +194,87 @@
::testing::Values(SpvCapabilityShader,
SpvCapabilityKernel));
+TEST_P(ValidateCFG, LoopReachableFromEntryButNeverLeadingToReturn) {
+ // In this case, the loop is reachable from a node without a predecessor,
+ // but never reaches a node with a return.
+ //
+ // This motivates the need for the pseudo-exit node to have a node
+ // from a cycle in its predecessors list. Otherwise the validator's
+ // post-dominance calculation will go into an infinite loop.
+ //
+ // For more motivation, see
+ // https://github.com/KhronosGroup/SPIRV-Tools/issues/279
+ string str = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+
+ OpName %entry "entry"
+ OpName %loop "loop"
+ OpName %exit "exit"
+
+%voidt = OpTypeVoid
+%funct = OpTypeFunction %voidt
+
+%main = OpFunction %voidt None %funct
+%entry = OpLabel
+ OpBranch %loop
+%loop = OpLabel
+ OpLoopMerge %exit %loop None
+ OpBranch %loop
+%exit = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << str;
+}
+
+TEST_P(ValidateCFG, LoopUnreachableFromEntryButLeadingToReturn) {
+ // In this case, the loop is not reachable from a node without a
+ // predecessor, but eventually reaches a node with a return.
+ //
+ // This motivates the need for the pseudo-entry node to have a node
+ // from a cycle in its successors list. Otherwise the validator's
+ // dominance calculation will go into an infinite loop.
+ //
+ // For more motivation, see
+ // https://github.com/KhronosGroup/SPIRV-Tools/issues/279
+ // Before that fix, we'd have an infinite loop when calculating
+ // post-dominators.
+ string str = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+
+ OpName %entry "entry"
+ OpName %loop "loop"
+ OpName %cont "cont"
+ OpName %exit "exit"
+
+%voidt = OpTypeVoid
+%funct = OpTypeFunction %voidt
+%boolt = OpTypeBool
+%false = OpConstantFalse %boolt
+
+%main = OpFunction %voidt None %funct
+%entry = OpLabel
+ OpReturn
+
+%loop = OpLabel
+ OpLoopMerge %exit %cont None
+ OpBranch %cont
+
+%cont = OpLabel
+ OpBranchConditional %false %loop %exit
+
+%exit = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << str
+ << getDiagnosticString();
+}
+
TEST_P(ValidateCFG, Simple) {
bool is_shader = GetParam() == SpvCapabilityShader;
Block entry("entry");
@@ -196,9 +282,9 @@
Block cont("cont");
Block merge("merge", SpvOpReturn);
- entry.setBody("%cond = OpSLessThan %intt %one %two\n");
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
if (is_shader) {
- loop.setBody("OpLoopMerge %merge %cont None\n");
+ loop.SetBody("OpLoopMerge %merge %cont None\n");
}
string str = header(GetParam()) + nameOps("loop", "entry", "cont", "merge",
@@ -220,7 +306,7 @@
Block cont("cont");
Block exit("exit", SpvOpReturn);
- entry.setBody("%var = OpVariable %ptrt Function\n");
+ entry.SetBody("%var = OpVariable %ptrt Function\n");
string str = header(GetParam()) + nameOps(make_pair("func", "Main")) +
types_consts() + " %func = OpFunction %voidt None %funct\n";
@@ -239,7 +325,7 @@
Block exit("exit", SpvOpReturn);
// This operation should only be performed in the entry block
- cont.setBody("%var = OpVariable %ptrt Function\n");
+ cont.SetBody("%var = OpVariable %ptrt Function\n");
string str = header(GetParam()) + nameOps(make_pair("func", "Main")) +
types_consts() + " %func = OpFunction %voidt None %funct\n";
@@ -257,6 +343,29 @@
"Variables can only be defined in the first block of a function"));
}
+TEST_P(ValidateCFG, BlockSelfLoopIsOk) {
+ bool is_shader = GetParam() == SpvCapabilityShader;
+ Block entry("entry");
+ Block loop("loop", SpvOpBranchConditional);
+ Block merge("merge", SpvOpReturn);
+
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
+ if (is_shader) loop.SetBody("OpLoopMerge %merge %loop None\n");
+
+ string str = header(GetParam()) +
+ nameOps("loop", "merge", make_pair("func", "Main")) +
+ types_consts() + "%func = OpFunction %voidt None %funct\n";
+
+ str += entry >> loop;
+ // loop branches to itself, but does not trigger an error.
+ str += loop >> vector<Block>({merge, loop});
+ str += merge;
+ str += "OpFunctionEnd\n";
+
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
+}
+
TEST_P(ValidateCFG, BlockAppearsBeforeDominatorBad) {
bool is_shader = GetParam() == SpvCapabilityShader;
Block entry("entry");
@@ -264,8 +373,8 @@
Block branch("branch", SpvOpBranchConditional);
Block merge("merge", SpvOpReturn);
- entry.setBody("%cond = OpSLessThan %intt %one %two\n");
- if (is_shader) branch.setBody("OpSelectionMerge %merge None\n");
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
+ if (is_shader) branch.SetBody("OpSelectionMerge %merge None\n");
string str = header(GetParam()) +
nameOps("cont", "branch", make_pair("func", "Main")) +
@@ -291,11 +400,11 @@
Block selection("selection", SpvOpBranchConditional);
Block merge("merge", SpvOpReturn);
- entry.setBody("%cond = OpSLessThan %intt %one %two\n");
- if (is_shader) loop.setBody(" OpLoopMerge %merge %loop None\n");
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
+ if (is_shader) loop.SetBody(" OpLoopMerge %merge %loop None\n");
// cannot share the same merge
- if (is_shader) selection.setBody("OpSelectionMerge %merge None\n");
+ if (is_shader) selection.SetBody("OpSelectionMerge %merge None\n");
string str = header(GetParam()) +
nameOps("merge", make_pair("func", "Main")) + types_consts() +
@@ -325,11 +434,11 @@
Block selection("selection", SpvOpBranchConditional);
Block merge("merge", SpvOpReturn);
- entry.setBody("%cond = OpSLessThan %intt %one %two\n");
- if (is_shader) selection.setBody(" OpSelectionMerge %merge None\n");
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
+ if (is_shader) selection.SetBody(" OpSelectionMerge %merge None\n");
// cannot share the same merge
- if (is_shader) loop.setBody(" OpLoopMerge %merge %loop None\n");
+ if (is_shader) loop.SetBody(" OpLoopMerge %merge %loop None\n");
string str = header(GetParam()) +
nameOps("merge", make_pair("func", "Main")) + types_consts() +
@@ -377,8 +486,8 @@
Block bad("bad", SpvOpBranchConditional);
Block exit("exit", SpvOpReturn);
- entry.setBody("%cond = OpSLessThan %intt %one %two\n");
- bad.setBody(" OpLoopMerge %entry %exit None\n");
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
+ bad.SetBody(" OpLoopMerge %entry %exit None\n");
string str = header(GetParam()) +
nameOps("entry", "bad", make_pair("func", "Main")) +
@@ -403,8 +512,8 @@
Block merge("merge");
Block end("end", SpvOpReturn);
- entry.setBody("%cond = OpSLessThan %intt %one %two\n");
- bad.setBody("OpLoopMerge %merge %cont None\n");
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
+ bad.SetBody("OpLoopMerge %merge %cont None\n");
string str = header(GetParam()) +
nameOps("entry", "bad", make_pair("func", "Main")) +
@@ -433,8 +542,8 @@
Block merge("merge");
Block end("end", SpvOpReturn);
- entry.setBody("%cond = OpSLessThan %intt %one %two\n");
- bad.setBody("OpSelectionMerge %merge None\n");
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
+ bad.SetBody("OpSelectionMerge %merge None\n");
string str = header(GetParam()) +
nameOps("entry", "bad", make_pair("func", "Main")) +
@@ -462,8 +571,8 @@
Block middle("middle", SpvOpBranchConditional);
Block end("end", SpvOpReturn);
- entry.setBody("%cond = OpSLessThan %intt %one %two\n");
- middle.setBody("OpSelectionMerge %end None\n");
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
+ middle.SetBody("OpSelectionMerge %end None\n");
Block entry2("entry2");
Block middle2("middle2");
@@ -499,9 +608,9 @@
Block f("f");
Block merge("merge", SpvOpReturn);
- entry.setBody("%cond = OpSLessThan %intt %one %two\n");
+ head.SetBody("%cond = OpSLessThan %intt %one %two\n");
- if (is_shader) head.setBody("OpSelectionMerge %merge None\n");
+ if (is_shader) head.AppendBody("OpSelectionMerge %merge None\n");
string str = header(GetParam()) +
nameOps("head", "merge", make_pair("func", "Main")) +
@@ -514,7 +623,6 @@
str += "OpFunctionEnd\n";
CompileSuccessfully(str);
-
if (is_shader) {
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(
@@ -535,8 +643,8 @@
Block f("f", SpvOpReturn);
Block merge("merge", SpvOpReturn);
- entry.setBody("%cond = OpSLessThan %intt %one %two\n");
- if (is_shader) branch.setBody("OpSelectionMerge %merge None\n");
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
+ if (is_shader) branch.AppendBody("OpSelectionMerge %merge None\n");
string str = header(GetParam()) +
nameOps("branch", "merge", make_pair("func", "Main")) +
@@ -561,8 +669,8 @@
Block f("f", SpvOpReturn);
Block merge("merge", SpvOpUnreachable);
- entry.setBody("%cond = OpSLessThan %intt %one %two\n");
- if (is_shader) branch.setBody("OpSelectionMerge %merge None\n");
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
+ if (is_shader) branch.AppendBody("OpSelectionMerge %merge None\n");
string str = header(GetParam()) +
nameOps("branch", "merge", make_pair("func", "Main")) +
@@ -606,8 +714,8 @@
Block merge("merge");
Block exit("exit", SpvOpReturn);
- entry.setBody("%cond = OpSLessThan %intt %one %two\n");
- if (is_shader) unreachable.setBody("OpSelectionMerge %merge None\n");
+ unreachable.SetBody("%cond = OpSLessThan %intt %one %two\n");
+ if (is_shader) unreachable.AppendBody("OpSelectionMerge %merge None\n");
string str = header(GetParam()) +
nameOps("unreachable", "exit", make_pair("func", "Main")) +
types_consts() + "%func = OpFunction %voidt None %funct\n";
@@ -638,8 +746,8 @@
Block loop("loop", SpvOpBranchConditional);
Block exit("exit", SpvOpReturn);
- entry.setBody("%cond = OpSLessThan %intt %one %two\n");
- if (is_shader) loop.setBody("OpLoopMerge %exit %loop None\n");
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
+ if (is_shader) loop.AppendBody("OpLoopMerge %exit %loop None\n");
string str = header(GetParam()) + string(types_consts()) +
"%func = OpFunction %voidt None %funct\n";
@@ -664,10 +772,10 @@
Block loop1_merge("loop1_merge");
Block exit("exit", SpvOpReturn);
- entry.setBody("%cond = OpSLessThan %intt %one %two\n");
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
if (is_shader) {
- loop1.setBody("OpLoopMerge %loop1_merge %loop2 None\n");
- loop2.setBody("OpLoopMerge %loop2_merge %loop2 None\n");
+ loop1.SetBody("OpLoopMerge %loop1_merge %loop2 None\n");
+ loop2.SetBody("OpLoopMerge %loop2_merge %loop2 None\n");
}
string str = header(GetParam()) + nameOps("loop2", "loop2_merge") +
@@ -694,11 +802,11 @@
vector<Block> merge_blocks;
Block inner("inner");
- entry.setBody("%cond = OpSLessThan %intt %one %two\n");
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
if_blocks.emplace_back("if0", SpvOpBranchConditional);
- if (is_shader) if_blocks[0].setBody("OpSelectionMerge %if_merge0 None\n");
+ if (is_shader) if_blocks[0].SetBody("OpSelectionMerge %if_merge0 None\n");
merge_blocks.emplace_back("if_merge0", SpvOpReturn);
for (int i = 1; i < N; i++) {
@@ -706,7 +814,7 @@
ss << i;
if_blocks.emplace_back("if" + ss.str(), SpvOpBranchConditional);
if (is_shader)
- if_blocks[i].setBody("OpSelectionMerge %if_merge" + ss.str() + " None\n");
+ if_blocks[i].SetBody("OpSelectionMerge %if_merge" + ss.str() + " None\n");
merge_blocks.emplace_back("if_merge" + ss.str(), SpvOpBranch);
}
string str = header(GetParam()) + string(types_consts()) +
@@ -737,10 +845,10 @@
Block be_block("be_block");
Block exit("exit", SpvOpReturn);
- entry.setBody("%cond = OpSLessThan %intt %one %two\n");
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
if (is_shader) {
- loop1.setBody("OpLoopMerge %exit %loop2_merge None\n");
- loop2.setBody("OpLoopMerge %loop2_merge %loop2 None\n");
+ loop1.SetBody("OpLoopMerge %exit %loop2_merge None\n");
+ loop2.SetBody("OpLoopMerge %loop2_merge %loop2 None\n");
}
string str = header(GetParam()) +
@@ -775,8 +883,8 @@
Block f("f");
Block exit("exit", SpvOpReturn);
- entry.setBody("%cond = OpSLessThan %intt %one %two\n");
- if (is_shader) split.setBody("OpSelectionMerge %exit None\n");
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
+ if (is_shader) split.SetBody("OpSelectionMerge %exit None\n");
string str = header(GetParam()) + nameOps("split", "f") + types_consts() +
"%func = OpFunction %voidt None %funct\n";
@@ -806,8 +914,8 @@
Block split("split", SpvOpBranchConditional);
Block exit("exit", SpvOpReturn);
- entry.setBody("%cond = OpSLessThan %intt %one %two\n");
- if (is_shader) split.setBody("OpSelectionMerge %exit None\n");
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
+ if (is_shader) split.SetBody("OpSelectionMerge %exit None\n");
string str = header(GetParam()) + nameOps("split") + types_consts() +
"%func = OpFunction %voidt None %funct\n";
@@ -829,22 +937,24 @@
}
}
-TEST_P(ValidateCFG, MultipleBackEdgesToLoopHeaderBad) {
+TEST_P(ValidateCFG, MultipleBackEdgeBlocksToLoopHeaderBad) {
bool is_shader = GetParam() == SpvCapabilityShader;
Block entry("entry");
Block loop("loop", SpvOpBranchConditional);
- Block cont("cont", SpvOpBranchConditional);
+ Block back0("back0");
+ Block back1("back1");
Block merge("merge", SpvOpReturn);
- entry.setBody("%cond = OpSLessThan %intt %one %two\n");
- if (is_shader) loop.setBody("OpLoopMerge %merge %loop None\n");
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
+ if (is_shader) loop.SetBody("OpLoopMerge %merge %back0 None\n");
- string str = header(GetParam()) + nameOps("cont", "loop") + types_consts() +
- "%func = OpFunction %voidt None %funct\n";
+ string str = header(GetParam()) + nameOps("loop", "back0", "back1") +
+ types_consts() + "%func = OpFunction %voidt None %funct\n";
str += entry >> loop;
- str += loop >> vector<Block>({cont, merge});
- str += cont >> vector<Block>({loop, loop});
+ str += loop >> vector<Block>({back0, back1});
+ str += back0 >> loop;
+ str += back1 >> loop;
str += merge;
str += "OpFunctionEnd";
@@ -853,7 +963,9 @@
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
MatchesRegex(
- "Loop header .\\[loop\\] targeted by multiple back-edges"));
+ "Loop header .\\[loop\\] is targeted by 2 back-edge blocks "
+ "but the standard requires exactly one"))
+ << str;
} else {
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
@@ -868,8 +980,8 @@
Block merge("merge", SpvOpReturn);
Block exit("exit", SpvOpReturn);
- entry.setBody("%cond = OpSLessThan %intt %one %two\n");
- if (is_shader) loop.setBody("OpLoopMerge %merge %cheader None\n");
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
+ if (is_shader) loop.SetBody("OpLoopMerge %merge %cheader None\n");
string str = header(GetParam()) + nameOps("cheader", "be_block") +
types_consts() + "%func = OpFunction %voidt None %funct\n";
@@ -901,8 +1013,8 @@
Block cont("cont", SpvOpBranchConditional);
Block merge("merge", SpvOpReturn);
- entry.setBody("%cond = OpSLessThan %intt %one %two\n");
- if (is_shader) loop.setBody("OpLoopMerge %merge %loop None\n");
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
+ if (is_shader) loop.SetBody("OpLoopMerge %merge %loop None\n");
string str = header(GetParam()) + nameOps("cont", "loop") + types_consts() +
"%func = OpFunction %voidt None %funct\n";
@@ -919,7 +1031,7 @@
EXPECT_THAT(getDiagnosticString(),
MatchesRegex("The continue construct with the continue target "
".\\[loop\\] is not post dominated by the "
- "back-edge block .\\[cont\\]"));
+ "back-edge block .\\[cont\\]")) << str;
} else {
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
@@ -933,8 +1045,8 @@
Block merge("merge");
Block exit("exit", SpvOpReturn);
- entry.setBody("%cond = OpSLessThan %intt %one %two\n");
- if (is_shader) loop.setBody("OpLoopMerge %merge %loop None\n");
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
+ if (is_shader) loop.SetBody("OpLoopMerge %merge %loop None\n");
string str = header(GetParam()) + nameOps("cont", "loop") + types_consts() +
"%func = OpFunction %voidt None %funct\n";
@@ -987,11 +1099,11 @@
%main = OpFunction %void None %voidf
)";
- entry.setBody(
- "%idval = OpLoad %uvec3 %id\n"
- "%x = OpCompositeExtract %u32 %idval 0\n"
- "%selector = OpUMod %u32 %x %three\n"
- "OpSelectionMerge %phi None\n");
+ entry.SetBody(
+ "%idval = OpLoad %uvec3 %id\n"
+ "%x = OpCompositeExtract %u32 %idval 0\n"
+ "%selector = OpUMod %u32 %x %three\n"
+ "OpSelectionMerge %phi None\n");
str += entry >> vector<Block>({def, case0, case1, case2});
str += case1 >> phi;
str += def;
@@ -1001,7 +1113,198 @@
str += "OpFunctionEnd";
CompileSuccessfully(str);
- ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, LoopWithZeroBackEdgesBad) {
+ string str = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpName %loop "loop"
+%voidt = OpTypeVoid
+%funct = OpTypeFunction %voidt
+%main = OpFunction %voidt None %funct
+%loop = OpLabel
+ OpLoopMerge %exit %exit None
+ OpBranch %exit
+%exit = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+ EXPECT_THAT(
+ getDiagnosticString(),
+ MatchesRegex("Loop header .\\[loop\\] is targeted by "
+ "0 back-edge blocks but the standard requires exactly "
+ "one"));
+}
+
+TEST_F(ValidateCFG, LoopWithBackEdgeFromUnreachableContinueConstructGood) {
+ string str = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpName %loop "loop"
+%voidt = OpTypeVoid
+%funct = OpTypeFunction %voidt
+%floatt = OpTypeFloat 32
+%boolt = OpTypeBool
+%one = OpConstant %floatt 1
+%two = OpConstant %floatt 2
+%main = OpFunction %voidt None %funct
+%entry = OpLabel
+ OpBranch %loop
+%loop = OpLabel
+ OpLoopMerge %exit %cont None
+ OpBranch %16
+%16 = OpLabel
+%cond = OpFOrdLessThan %boolt %one %two
+ OpBranchConditional %cond %body %exit
+%body = OpLabel
+ OpReturn
+%cont = OpLabel ; Reachable only from OpLoopMerge ContinueTarget parameter
+ OpBranch %loop ; Should be considered a back-edge
+%exit = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(str);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
+}
+
+TEST_P(ValidateCFG,
+ NestedConstructWithUnreachableMergeBlockBranchingToOuterMergeBlock) {
+ // Test for https://github.com/KhronosGroup/SPIRV-Tools/issues/297
+ // The nested construct has an unreachable merge block. In the
+ // augmented CFG that merge block
+ // we still determine that the
+ bool is_shader = GetParam() == SpvCapabilityShader;
+ Block entry("entry", SpvOpBranchConditional);
+ Block inner_head("inner_head", SpvOpBranchConditional);
+ Block inner_true("inner_true", SpvOpReturn);
+ Block inner_false("inner_false", SpvOpReturn);
+ Block inner_merge("inner_merge");
+ Block exit("exit", SpvOpReturn);
+
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
+ if (is_shader) {
+ entry.AppendBody("OpSelectionMerge %exit None\n");
+ inner_head.SetBody("OpSelectionMerge %inner_merge None\n");
+ }
+
+ string str = header(GetParam()) + nameOps("entry", "inner_merge", "exit") +
+ types_consts() + "%func = OpFunction %voidt None %funct\n";
+
+ str += entry >> vector<Block>({inner_head, exit});
+ str += inner_head >> vector<Block>({inner_true, inner_false});
+ str += inner_true;
+ str += inner_false;
+ str += inner_merge >> exit;
+ str += exit;
+ str += "OpFunctionEnd";
+
+ CompileSuccessfully(str);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
+}
+
+TEST_P(ValidateCFG, ContinueTargetCanBeMergeBlockForNestedStructureGood) {
+ // This example is valid. It shows that the validator can't just add
+ // an edge from the loop head to the continue target. If that edge
+ // is added, then the "if_merge" block is both the continue target
+ // for the loop and also the merge block for the nested selection, but
+ // then it wouldn't be dominated by "if_head", the header block for the
+ // nested selection.
+ bool is_shader = GetParam() == SpvCapabilityShader;
+ Block entry("entry");
+ Block loop("loop");
+ Block if_head("if_head", SpvOpBranchConditional);
+ Block if_true("if_true");
+ Block if_merge("if_merge", SpvOpBranchConditional);
+ Block merge("merge", SpvOpReturn);
+
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
+ if (is_shader) {
+ loop.SetBody("OpLoopMerge %merge %if_merge None\n");
+ if_head.SetBody("OpSelectionMerge %if_merge None\n");
+ }
+
+ string str = header(GetParam()) + nameOps("entry", "loop", "if_head",
+ "if_true", "if_merge", "merge") +
+ types_consts() + "%func = OpFunction %voidt None %funct\n";
+
+ str += entry >> loop;
+ str += loop >> if_head;
+ str += if_head >> vector<Block>({if_true, if_merge});
+ str += if_true >> if_merge;
+ str += if_merge >> vector<Block>({loop, merge});
+ str += merge;
+ str += "OpFunctionEnd";
+
+ CompileSuccessfully(str);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
+}
+
+TEST_P(ValidateCFG, SingleLatchBlockMultipleBranchesToLoopHeader) {
+ // This test case ensures we allow both branches of a loop latch block
+ // to go back to the loop header. It still counts as a single back edge.
+ bool is_shader = GetParam() == SpvCapabilityShader;
+ Block entry("entry");
+ Block loop("loop", SpvOpBranchConditional);
+ Block latch("latch", SpvOpBranchConditional);
+ Block merge("merge", SpvOpReturn);
+
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
+ if (is_shader) {
+ loop.SetBody("OpLoopMerge %merge %latch None\n");
+ }
+
+ string str = header(GetParam()) + nameOps("entry", "loop", "latch", "merge") +
+ types_consts() + "%func = OpFunction %voidt None %funct\n";
+
+ str += entry >> loop;
+ str += loop >> vector<Block>({latch, merge});
+ str += latch >> vector<Block>({loop, loop}); // This is the key
+ str += merge;
+ str += "OpFunctionEnd";
+
+ CompileSuccessfully(str);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << str
+ << getDiagnosticString();
+}
+
+TEST_P(ValidateCFG, SingleLatchBlockHeaderContinueTargetIsItselfGood) {
+ // This test case ensures we don't count a Continue Target from a loop
+ // header to itself as a self-loop when computing back edges.
+ // Also, it detects that there is an edge from %latch to the pseudo-exit
+ // node, rather than from %loop. In particular, it detects that we
+ // have used the *reverse* textual order of blocks when computing
+ // predecessor traversal roots.
+ bool is_shader = GetParam() == SpvCapabilityShader;
+ Block entry("entry");
+ Block loop("loop");
+ Block latch("latch");
+ Block merge("merge", SpvOpReturn);
+
+ entry.SetBody("%cond = OpSLessThan %intt %one %two\n");
+ if (is_shader) {
+ loop.SetBody("OpLoopMerge %merge %loop None\n");
+ }
+
+ string str = header(GetParam()) + nameOps("entry", "loop", "latch", "merge") +
+ types_consts() + "%func = OpFunction %voidt None %funct\n";
+
+ str += entry >> loop;
+ str += loop >> latch;
+ str += latch >> loop;
+ str += merge;
+ str += "OpFunctionEnd";
+
+ CompileSuccessfully(str);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << str
+ << getDiagnosticString();
}
/// TODO(umar): Switch instructions
diff --git a/test/Validate.SSA.cpp b/test/Validate.SSA.cpp
index 09f0533..5e2267b 100644
--- a/test/Validate.SSA.cpp
+++ b/test/Validate.SSA.cpp
@@ -35,15 +35,16 @@
#include <utility>
using ::testing::HasSubstr;
+using ::testing::MatchesRegex;
using std::string;
using std::pair;
using std::stringstream;
namespace {
-using Validate = spvtest::ValidateBase<pair<string, bool>>;
+using ValidateSSA = spvtest::ValidateBase<pair<string, bool>>;
-TEST_F(Validate, Default) {
+TEST_F(ValidateSSA, Default) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -60,7 +61,7 @@
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
-TEST_F(Validate, IdUndefinedBad) {
+TEST_F(ValidateSSA, IdUndefinedBad) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -77,7 +78,7 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}
-TEST_F(Validate, IdRedefinedBad) {
+TEST_F(ValidateSSA, IdRedefinedBad) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -93,7 +94,7 @@
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
}
-TEST_F(Validate, DominateUsageBad) {
+TEST_F(ValidateSSA, DominateUsageBad) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -106,7 +107,50 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("not_dominant"));
}
-TEST_F(Validate, ForwardNameGood) {
+TEST_F(ValidateSSA, DominateUsageWithinBlockBad) {
+ char str[] = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpName %bad "bad"
+%voidt = OpTypeVoid
+%funct = OpTypeFunction %voidt
+%uintt = OpTypeInt 32 0
+%one = OpConstant %uintt 1
+%func = OpFunction %voidt None %funct
+%entry = OpLabel
+%sum = OpIAdd %uintt %one %bad
+%bad = OpCopyObject %uintt %sum
+ OpReturn
+ OpFunctionEnd
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(),
+ MatchesRegex("ID .\\[bad\\] has not been defined"));
+}
+
+TEST_F(ValidateSSA, DominateUsageSameInstructionBad) {
+ char str[] = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpName %sum "sum"
+%voidt = OpTypeVoid
+%funct = OpTypeFunction %voidt
+%uintt = OpTypeInt 32 0
+%one = OpConstant %uintt 1
+%func = OpFunction %voidt None %funct
+%entry = OpLabel
+%sum = OpIAdd %uintt %one %sum
+ OpReturn
+ OpFunctionEnd
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(),
+ MatchesRegex("ID .\\[sum\\] has not been defined"));
+}
+
+TEST_F(ValidateSSA, ForwardNameGood) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -122,7 +166,7 @@
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
-TEST_F(Validate, ForwardNameMissingTargetBad) {
+TEST_F(ValidateSSA, ForwardNameMissingTargetBad) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -133,7 +177,7 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("main"));
}
-TEST_F(Validate, ForwardMemberNameGood) {
+TEST_F(ValidateSSA, ForwardMemberNameGood) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -147,7 +191,7 @@
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
-TEST_F(Validate, ForwardMemberNameMissingTargetBad) {
+TEST_F(ValidateSSA, ForwardMemberNameMissingTargetBad) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -162,7 +206,7 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("size"));
}
-TEST_F(Validate, ForwardDecorateGood) {
+TEST_F(ValidateSSA, ForwardDecorateGood) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -175,7 +219,7 @@
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
-TEST_F(Validate, ForwardDecorateInvalidIDBad) {
+TEST_F(ValidateSSA, ForwardDecorateInvalidIDBad) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -196,7 +240,7 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}
-TEST_F(Validate, ForwardMemberDecorateGood) {
+TEST_F(ValidateSSA, ForwardMemberDecorateGood) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -210,7 +254,7 @@
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
-TEST_F(Validate, ForwardMemberDecorateInvalidIdBad) {
+TEST_F(ValidateSSA, ForwardMemberDecorateInvalidIdBad) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -226,7 +270,7 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}
-TEST_F(Validate, ForwardGroupDecorateGood) {
+TEST_F(ValidateSSA, ForwardGroupDecorateGood) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -243,7 +287,7 @@
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
-TEST_F(Validate, ForwardGroupDecorateMissingGroupBad) {
+TEST_F(ValidateSSA, ForwardGroupDecorateMissingGroupBad) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -262,7 +306,7 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}
-TEST_F(Validate, ForwardGroupDecorateMissingTargetBad) {
+TEST_F(ValidateSSA, ForwardGroupDecorateMissingTargetBad) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -281,7 +325,7 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}
-TEST_F(Validate, ForwardGroupDecorateDecorationGroupDominateBad) {
+TEST_F(ValidateSSA, ForwardGroupDecorateDecorationGroupDominateBad) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -300,7 +344,7 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("group"));
}
-TEST_F(Validate, ForwardDecorateInvalidIdBad) {
+TEST_F(ValidateSSA, ForwardDecorateInvalidIdBad) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -321,7 +365,7 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}
-TEST_F(Validate, FunctionCallGood) {
+TEST_F(ValidateSSA, FunctionCallGood) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -348,7 +392,7 @@
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
-TEST_F(Validate, ForwardFunctionCallGood) {
+TEST_F(ValidateSSA, ForwardFunctionCallGood) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -375,7 +419,7 @@
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
-TEST_F(Validate, ForwardBranchConditionalGood) {
+TEST_F(ValidateSSA, ForwardBranchConditionalGood) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -401,7 +445,7 @@
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
-TEST_F(Validate, ForwardBranchConditionalWithWeightsGood) {
+TEST_F(ValidateSSA, ForwardBranchConditionalWithWeightsGood) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -427,7 +471,7 @@
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
-TEST_F(Validate, ForwardBranchConditionalNonDominantConditionBad) {
+TEST_F(ValidateSSA, ForwardBranchConditionalNonDominantConditionBad) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -456,7 +500,7 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("conditional"));
}
-TEST_F(Validate, ForwardBranchConditionalMissingTargetBad) {
+TEST_F(ValidateSSA, ForwardBranchConditionalMissingTargetBad) {
char str[] = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
@@ -497,6 +541,11 @@
%intt = OpTypeInt 32 1
%uintt = OpTypeInt 32 0
%vfunct = OpTypeFunction %voidt
+%intptrt = OpTypePointer UniformConstant %intt
+%zero = OpConstant %intt 0
+%one = OpConstant %intt 1
+%ten = OpConstant %intt 10
+%false = OpConstantFalse %boolt
)";
const string kKernelTypesAndConstants = R"(
@@ -507,7 +556,6 @@
%ndt = OpTypeStruct %intt %arr3t %arr3t %arr3t
%eventt = OpTypeEvent
-%intptrt = OpTypePointer UniformConstant %int8t
%offset = OpConstant %intt 0
%local = OpConstant %intt 1
@@ -541,7 +589,7 @@
OpFunctionEnd
)";
-TEST_F(Validate, EnqueueKernelGood) {
+TEST_F(ValidateSSA, EnqueueKernelGood) {
string str = kHeader + kBasicTypes + kKernelTypesAndConstants +
kKernelDefinition + R"(
%main = OpFunction %voidt None %vfunct
@@ -558,7 +606,7 @@
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
-TEST_F(Validate, ForwardEnqueueKernelGood) {
+TEST_F(ValidateSSA, ForwardEnqueueKernelGood) {
string str = kHeader + kBasicTypes + kKernelTypesAndConstants + R"(
%main = OpFunction %voidt None %vfunct
%mainl = OpLabel
@@ -575,7 +623,7 @@
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
-TEST_F(Validate, EnqueueMissingFunctionBad) {
+TEST_F(ValidateSSA, EnqueueMissingFunctionBad) {
string str = kHeader + "OpName %kfunc \"kfunc\"" + kBasicTypes +
kKernelTypesAndConstants + R"(
%main = OpFunction %voidt None %vfunct
@@ -610,7 +658,7 @@
return out;
}
-TEST_F(Validate, ForwardEnqueueKernelMissingParameter1Bad) {
+TEST_F(ValidateSSA, ForwardEnqueueKernelMissingParameter1Bad) {
string str = forwardKernelNonDominantParameterBaseCode("missing") + R"(
%err = OpEnqueueKernel %missing %dqueue %flags %ndval
%nevent %event %revent %kfunc %firstp
@@ -623,7 +671,7 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}
-TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter2Bad) {
+TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter2Bad) {
string str = forwardKernelNonDominantParameterBaseCode("dqueue2") + R"(
%err = OpEnqueueKernel %uintt %dqueue2 %flags %ndval
%nevent %event %revent %kfunc
@@ -637,7 +685,7 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("dqueue2"));
}
-TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter3Bad) {
+TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter3Bad) {
string str = forwardKernelNonDominantParameterBaseCode("ndval2") + R"(
%err = OpEnqueueKernel %uintt %dqueue %flags %ndval2
%nevent %event %revent %kfunc %firstp
@@ -651,7 +699,7 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("ndval2"));
}
-TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter4Bad) {
+TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter4Bad) {
string str = forwardKernelNonDominantParameterBaseCode("nevent2") + R"(
%err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent2
%event %revent %kfunc %firstp %psize
@@ -665,7 +713,7 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("nevent2"));
}
-TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter5Bad) {
+TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter5Bad) {
string str = forwardKernelNonDominantParameterBaseCode("event2") + R"(
%err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
%event2 %revent %kfunc %firstp %psize
@@ -679,7 +727,7 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("event2"));
}
-TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter6Bad) {
+TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter6Bad) {
string str = forwardKernelNonDominantParameterBaseCode("revent2") + R"(
%err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
%event %revent2 %kfunc %firstp %psize
@@ -693,7 +741,7 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("revent2"));
}
-TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter8Bad) {
+TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter8Bad) {
string str = forwardKernelNonDominantParameterBaseCode("firstp2") + R"(
%err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
%event %revent %kfunc %firstp2 %psize
@@ -707,7 +755,7 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("firstp2"));
}
-TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter9Bad) {
+TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter9Bad) {
string str = forwardKernelNonDominantParameterBaseCode("psize2") + R"(
%err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
%event %revent %kfunc %firstp %psize2
@@ -721,7 +769,7 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("psize2"));
}
-TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter10Bad) {
+TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter10Bad) {
string str = forwardKernelNonDominantParameterBaseCode("palign2") + R"(
%err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
%event %revent %kfunc %firstp %psize
@@ -735,7 +783,7 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("palign2"));
}
-TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter11Bad) {
+TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter11Bad) {
string str = forwardKernelNonDominantParameterBaseCode("lsize2") + R"(
%err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
%event %revent %kfunc %firstp %psize
@@ -758,14 +806,14 @@
{"OpGetKernelWorkGroupSize", kNoNDrange},
{"OpGetKernelPreferredWorkGroupSizeMultiple", kNoNDrange}};
-INSTANTIATE_TEST_CASE_P(KernelArgs, Validate, ::testing::ValuesIn(cases),);
+INSTANTIATE_TEST_CASE_P(KernelArgs, ValidateSSA, ::testing::ValuesIn(cases), );
static const string return_instructions = R"(
OpReturn
OpFunctionEnd
)";
-TEST_P(Validate, GetKernelGood) {
+TEST_P(ValidateSSA, GetKernelGood) {
string instruction = GetParam().first;
bool with_ndrange = GetParam().second;
string ndrange_param = with_ndrange ? " %ndval " : " ";
@@ -781,7 +829,7 @@
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
-TEST_P(Validate, ForwardGetKernelGood) {
+TEST_P(ValidateSSA, ForwardGetKernelGood) {
string instruction = GetParam().first;
bool with_ndrange = GetParam().second;
string ndrange_param = with_ndrange ? " %ndval " : " ";
@@ -801,7 +849,7 @@
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
-TEST_P(Validate, ForwardGetKernelMissingDefinitionBad) {
+TEST_P(ValidateSSA, ForwardGetKernelMissingDefinitionBad) {
string instruction = GetParam().first;
bool with_ndrange = GetParam().second;
string ndrange_param = with_ndrange ? " %ndval " : " ";
@@ -818,7 +866,7 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}
-TEST_P(Validate, ForwardGetKernelNDrangeSubGroupCountMissingParameter1Bad) {
+TEST_P(ValidateSSA, ForwardGetKernelNDrangeSubGroupCountMissingParameter1Bad) {
string instruction = GetParam().first;
bool with_ndrange = GetParam().second;
string ndrange_param = with_ndrange ? " %ndval " : " ";
@@ -835,7 +883,8 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}
-TEST_P(Validate, ForwardGetKernelNDrangeSubGroupCountNonDominantParameter2Bad) {
+TEST_P(ValidateSSA,
+ ForwardGetKernelNDrangeSubGroupCountNonDominantParameter2Bad) {
string instruction = GetParam().first;
bool with_ndrange = GetParam().second;
string ndrange_param = with_ndrange ? " %ndval2 " : " ";
@@ -855,7 +904,8 @@
}
}
-TEST_P(Validate, ForwardGetKernelNDrangeSubGroupCountNonDominantParameter4Bad) {
+TEST_P(ValidateSSA,
+ ForwardGetKernelNDrangeSubGroupCountNonDominantParameter4Bad) {
string instruction = GetParam().first;
bool with_ndrange = GetParam().second;
string ndrange_param = with_ndrange ? " %ndval " : " ";
@@ -873,7 +923,8 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("firstp2"));
}
-TEST_P(Validate, ForwardGetKernelNDrangeSubGroupCountNonDominantParameter5Bad) {
+TEST_P(ValidateSSA,
+ ForwardGetKernelNDrangeSubGroupCountNonDominantParameter5Bad) {
string instruction = GetParam().first;
bool with_ndrange = GetParam().second;
string ndrange_param = with_ndrange ? " %ndval " : " ";
@@ -891,7 +942,8 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("psize2"));
}
-TEST_P(Validate, ForwardGetKernelNDrangeSubGroupCountNonDominantParameter6Bad) {
+TEST_P(ValidateSSA,
+ ForwardGetKernelNDrangeSubGroupCountNonDominantParameter6Bad) {
string instruction = GetParam().first;
bool with_ndrange = GetParam().second;
string ndrange_param = with_ndrange ? " %ndval " : " ";
@@ -911,12 +963,9 @@
}
}
-TEST_F(Validate, PhiGood) {
+TEST_F(ValidateSSA, PhiGood) {
string str = kHeader + kBasicTypes +
R"(
-%zero = OpConstant %intt 0
-%one = OpConstant %intt 1
-%ten = OpConstant %intt 10
%func = OpFunction %voidt None %vfunct
%preheader = OpLabel
%init = OpCopyObject %intt %zero
@@ -937,12 +986,9 @@
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
-TEST_F(Validate, PhiMissingTypeBad) {
+TEST_F(ValidateSSA, PhiMissingTypeBad) {
string str = kHeader + "OpName %missing \"missing\"" + kBasicTypes +
R"(
-%zero = OpConstant %intt 0
-%one = OpConstant %intt 1
-%ten = OpConstant %intt 10
%func = OpFunction %voidt None %vfunct
%preheader = OpLabel
%init = OpCopyObject %intt %zero
@@ -964,12 +1010,9 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}
-TEST_F(Validate, PhiMissingIdBad) {
+TEST_F(ValidateSSA, PhiMissingIdBad) {
string str = kHeader + "OpName %missing \"missing\"" + kBasicTypes +
R"(
-%zero = OpConstant %intt 0
-%one = OpConstant %intt 1
-%ten = OpConstant %intt 10
%func = OpFunction %voidt None %vfunct
%preheader = OpLabel
%init = OpCopyObject %intt %zero
@@ -991,12 +1034,9 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}
-TEST_F(Validate, PhiMissingLabelBad) {
+TEST_F(ValidateSSA, PhiMissingLabelBad) {
string str = kHeader + "OpName %missing \"missing\"" + kBasicTypes +
R"(
-%zero = OpConstant %intt 0
-%one = OpConstant %intt 1
-%ten = OpConstant %intt 10
%func = OpFunction %voidt None %vfunct
%preheader = OpLabel
%init = OpCopyObject %intt %zero
@@ -1018,5 +1058,247 @@
EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}
-// TODO(umar): OpGroupMemberDecorate
+TEST_F(ValidateSSA, IdDominatesItsUseGood) {
+ string str = kHeader + kBasicTypes +
+ R"(
+%func = OpFunction %voidt None %vfunct
+%entry = OpLabel
+%cond = OpSLessThan %intt %one %ten
+%eleven = OpIAdd %intt %one %ten
+ OpSelectionMerge %merge None
+ OpBranchConditional %cond %t %f
+%t = OpLabel
+%twelve = OpIAdd %intt %eleven %one
+ OpBranch %merge
+%f = OpLabel
+%twentytwo = OpIAdd %intt %eleven %ten
+ OpBranch %merge
+%merge = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
+
+TEST_F(ValidateSSA, IdDoesNotDominateItsUseBad) {
+ string str = kHeader +
+ "OpName %eleven \"eleven\"\n"
+ "OpName %true_block \"true_block\"\n"
+ "OpName %false_block \"false_block\"" +
+ kBasicTypes +
+ R"(
+%func = OpFunction %voidt None %vfunct
+%entry = OpLabel
+%cond = OpSLessThan %intt %one %ten
+ OpSelectionMerge %merge None
+ OpBranchConditional %cond %true_block %false_block
+%true_block = OpLabel
+%eleven = OpIAdd %intt %one %ten
+%twelve = OpIAdd %intt %eleven %one
+ OpBranch %merge
+%false_block = OpLabel
+%twentytwo = OpIAdd %intt %eleven %ten
+ OpBranch %merge
+%merge = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ EXPECT_THAT(
+ getDiagnosticString(),
+ MatchesRegex("ID .\\[eleven\\] defined in block .\\[true_block\\] does "
+ "not dominate its use in block .\\[false_block\\]"));
+}
+
+TEST_F(ValidateSSA, PhiUseDoesntDominateDefinitionGood) {
+ string str = kHeader + kBasicTypes +
+ R"(
+%func = OpFunction %voidt None %vfunct
+%entry = OpLabel
+%var_one = OpVariable %intptrt Function %one
+%one_val = OpLoad %intt %var_one
+ OpBranch %loop
+%loop = OpLabel
+%i = OpPhi %intt %one_val %entry %inew %cont
+%cond = OpSLessThan %intt %one %ten
+ OpLoopMerge %merge %cont None
+ OpBranchConditional %cond %body %merge
+%body = OpLabel
+ OpBranch %cont
+%cont = OpLabel
+%inew = OpIAdd %intt %i %one
+ OpBranch %loop
+%merge = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateSSA, PhiUseDoesntDominateUseOfPhiOperandUsedBeforeDefinitionBad) {
+ string str = kHeader
+ + "OpName %inew \"inew\""
+ + kBasicTypes +
+ R"(
+%func = OpFunction %voidt None %vfunct
+%entry = OpLabel
+%var_one = OpVariable %intptrt Function %one
+%one_val = OpLoad %intt %var_one
+ OpBranch %loop
+%loop = OpLabel
+%i = OpPhi %intt %one_val %entry %inew %cont
+%bad = OpIAdd %intt %inew %one
+%cond = OpSLessThan %intt %one %ten
+ OpLoopMerge %merge %cont None
+ OpBranchConditional %cond %body %merge
+%body = OpLabel
+ OpBranch %cont
+%cont = OpLabel
+%inew = OpIAdd %intt %i %one
+ OpBranch %loop
+%merge = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ EXPECT_THAT(
+ getDiagnosticString(),
+ MatchesRegex("ID .\\[inew\\] has not been defined"));
+}
+
+TEST_F(ValidateSSA, PhiUseMayComeFromNonDominatingBlockGood) {
+ string str = kHeader
+ + "OpName %if_true \"if_true\"\n"
+ + "OpName %exit \"exit\"\n"
+ + "OpName %copy \"copy\"\n"
+ + kBasicTypes +
+ R"(
+%func = OpFunction %voidt None %vfunct
+%entry = OpLabel
+ OpBranchConditional %false %if_true %exit
+
+%if_true = OpLabel
+%copy = OpCopyObject %boolt %false
+ OpBranch %exit
+
+; The use of %copy here is ok, even though it was defined
+; in a block that does not dominate %exit. That's the point
+; of an OpPhi.
+%exit = OpLabel
+%value = OpPhi %boolt %false %entry %copy %if_true
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
+}
+
+TEST_F(ValidateSSA, DISABLED_PhiVariableDefMustComeFromBlockDominatingThePredecessorBad) {
+ string str = kHeader
+ + "OpName %if_true \"if_true\"\n"
+ + "OpName %if_false \"if_false\"\n"
+ + "OpName %exit \"exit\"\n"
+ + "OpName %true_copy \"true_copy\"\n"
+ + "OpName %false_copy \"false_copy\"\n"
+ + kBasicTypes +
+ R"(
+%func = OpFunction %voidt None %vfunct
+%entry = OpLabel
+ OpBranchConditional %false %if_true %if_false
+
+%if_true = OpLabel
+%true_copy = OpCopyObject %boolt %false
+ OpBranch %exit
+
+%if_false = OpLabel
+%false_copy = OpCopyObject %boolt %false
+ OpBranch %exit
+
+; The (variable,Id) pairs are swapped.
+%exit = OpLabel
+%value = OpPhi %boolt %true_copy %if_false %false_copy %if_true
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ // TODO(dneto): Check for a good error message
+}
+
+TEST_F(ValidateSSA, DominanceCheckIgnoresUsesInUnreachableBlocksDefInBlockGood) {
+ string str = kHeader
+ + kBasicTypes +
+ R"(
+%func = OpFunction %voidt None %vfunct
+%entry = OpLabel
+%def = OpCopyObject %boolt %false
+ OpReturn
+
+%unreach = OpLabel
+%use = OpCopyObject %boolt %def
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(str);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
+}
+
+TEST_F(ValidateSSA, DominanceCheckIgnoresUsesInUnreachableBlocksDefIsParamGood) {
+ string str = kHeader + kBasicTypes +
+ R"(
+%void_fn_int = OpTypeFunction %voidt %intt
+%func = OpFunction %voidt None %void_fn_int
+%int_param = OpFunctionParameter %intt
+%entry = OpLabel
+ OpReturn
+
+%unreach = OpLabel
+%use = OpCopyObject %intt %int_param
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(str);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
+}
+
+TEST_F(ValidateSSA, UseFunctionParameterFromOtherFunctionBad) {
+ string str = kHeader +
+ "OpName %first \"first\"\n"
+ "OpName %entry2 \"entry2\"\n"
+ "OpName %func \"func\"\n" +
+ kBasicTypes +
+ R"(
+%viifunct = OpTypeFunction %voidt %intt %intt
+%func = OpFunction %voidt None %viifunct
+%first = OpFunctionParameter %intt
+%second = OpFunctionParameter %intt
+ OpFunctionEnd
+%func2 = OpFunction %voidt None %viifunct
+%first2 = OpFunctionParameter %intt
+%second2 = OpFunctionParameter %intt
+%entry2 = OpLabel
+%baduse = OpIAdd %intt %first %first2
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ EXPECT_THAT(
+ getDiagnosticString(),
+ MatchesRegex("ID .\\[first\\] used in block .\\[entry2\\] is used "
+ "outside of it's defining function .\\[func\\]"));
+}
+// TODO(umar): OpGroupMemberDecorate
+} // namespace
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index f83b793..90bd472 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -39,7 +39,22 @@
LIBS SPIRV-Tools-opt ${SPIRV_TOOLS}
)
+add_spvtools_unittest(TARGET pass_freeze_spec_const
+ SRCS test_freeze_spec_const.cpp pass_utils.cpp
+ LIBS SPIRV-Tools-opt ${SPIRV_TOOLS}
+)
+
add_spvtools_unittest(TARGET pass_utils
SRCS test_utils.cpp pass_utils.cpp
LIBS SPIRV-Tools-opt ${SPIRV_TOOLS}
)
+
+add_spvtools_unittest(TARGET def_use
+ SRCS test_def_use.cpp pass_utils.cpp
+ LIBS SPIRV-Tools-opt ${SPIRV_TOOLS}
+)
+
+add_spvtools_unittest(TARGET assembly_builder
+ SRCS test_assembly_builder.cpp pass_utils.cpp
+ LIBS SPIRV-Tools-opt ${SPIRV_TOOLS}
+)
diff --git a/test/opt/assembly_builder.h b/test/opt/assembly_builder.h
new file mode 100644
index 0000000..855a546
--- /dev/null
+++ b/test/opt/assembly_builder.h
@@ -0,0 +1,250 @@
+// Copyright (c) 2016 Google Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+// https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#ifndef LIBSPIRV_TEST_OPT_ASSEMBLY_BUILDER
+#define LIBSPIRV_TEST_OPT_ASSEMBLY_BUILDER
+
+#include <algorithm>
+#include <sstream>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+namespace spvtools {
+
+// A simple SPIR-V assembly code builder for test uses. It builds an SPIR-V
+// assembly module from vectors of assembly strings. It allows users to add
+// instructions to the main function and the type-constants-globals section
+// directly. It relies on OpName instructions and friendly-name disassembling
+// to keep the ID names unchanged after assembling.
+//
+// An assembly module is divided into several sections, matching with the
+// SPIR-V Logical Layout:
+// Global Preamble:
+// OpCapability instructions;
+// OpExtension instructions and OpExtInstImport instructions;
+// OpMemoryModel instruction;
+// OpEntryPoint and OpExecutionMode instruction;
+// OpString, OpSourceExtension, OpSource and OpSourceContinued instructions.
+// Names:
+// OpName instructions.
+// Annotations:
+// OpDecorate, OpMemberDecorate, OpGroupDecorate, OpGroupMemberDecorate and
+// OpDecorationGroup.
+// Types, Constants and Global variables:
+// Types, constants and global variables declaration instructions.
+// Main Function:
+// Main function instructions.
+// Main Function Postamble:
+// The return and function end instructions.
+//
+// The assembly code is built by concatenating all the strings in the above
+// sections.
+//
+// Users define the contents in section <Type, Constants and Global Variables>
+// and <Main Function>. The <Names> section is to hold the names for IDs to
+// keep them unchanged before and after assembling. All defined IDs to be added
+// to this code builder will be assigned with a global name through OpName
+// instruction. The name is extracted from the definition instruction.
+// E.g. adding instruction: %var_a = OpConstant %int 2, will also add an
+// instruction: OpName %var_a, "var_a".
+//
+// Note that the name must not be used on more than one defined IDs and
+// friendly-name disassembling must be enabled so that OpName instructions will
+// be respected.
+class AssemblyBuilder {
+ // The base ID value for spec constants.
+ static const uint32_t SPEC_ID_BASE = 200;
+
+ public:
+ // Initalize a minimal SPIR-V assembly code as the template. The minimal
+ // module contains an empty main function and some predefined names for the
+ // main function.
+ AssemblyBuilder()
+ : spec_id_counter_(SPEC_ID_BASE),
+ global_preamble_({
+ // clang-format off
+ "OpCapability Shader",
+ "OpCapability Float64",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %main \"main\"",
+ // clang-format on
+ }),
+ names_(),
+ annotations_(),
+ types_consts_globals_(),
+ main_func_(),
+ main_func_postamble_({
+ "OpReturn", "OpFunctionEnd",
+ }) {
+ AppendTypesConstantsGlobals({
+ "%void = OpTypeVoid", "%main_func_type = OpTypeFunction %void",
+ });
+ AppendInMain({
+ "%main = OpFunction %void None %main_func_type",
+ "%main_func_entry_block = OpLabel",
+ });
+ }
+
+ // Appends instructions to the types-constants-globals section and returns
+ // the reference of this assembly builder. IDs defined in the given code will
+ // be added to the Names section and then be registered with OpName
+ // instruction. Corresponding decoration instruction will be added for spec
+ // constants defined with opcode: 'OpSpecConstant'.
+ AssemblyBuilder& AppendTypesConstantsGlobals(
+ const std::vector<std::string>& vec_asm_code) {
+ AddNamesForResultIDsIn(vec_asm_code);
+ // Check spec constants defined with OpSpecConstant.
+ for (auto& inst_str : vec_asm_code) {
+ if (inst_str.find("= OpSpecConstant ") != std::string::npos ||
+ inst_str.find("= OpSpecConstantTrue ") != std::string::npos ||
+ inst_str.find("= OpSpecConstantFalse ") != std::string::npos) {
+ AddSpecIDFor(GetResultIDName(inst_str));
+ }
+ }
+ types_consts_globals_.insert(types_consts_globals_.end(),
+ vec_asm_code.begin(), vec_asm_code.end());
+ return *this;
+ }
+
+ // Appends instructions to the main function block, which is already labelled
+ // with "main_func_entry_block". Returns the reference of this assembly
+ // builder. IDs defined in the given code will be added to the Names section
+ // and then be registered with OpName instruction.
+ AssemblyBuilder& AppendInMain(const std::vector<std::string>& vec_asm_code) {
+ AddNamesForResultIDsIn(vec_asm_code);
+ main_func_.insert(main_func_.end(), vec_asm_code.begin(),
+ vec_asm_code.end());
+ return *this;
+ }
+
+ // Appends annotation instructions to the annotation section, and returns the
+ // reference of this assembly builder.
+ AssemblyBuilder& AppendAnnotations(
+ const std::vector<std::string>& vec_annotations) {
+ annotations_.insert(annotations_.end(), vec_annotations.begin(),
+ vec_annotations.end());
+ return *this;
+ }
+
+ // Get the SPIR-V assembly code as string.
+ std::string GetCode() const {
+ std::ostringstream ss;
+ for (const auto& line : global_preamble_) {
+ ss << line << std::endl;
+ }
+ for (const auto& line : names_) {
+ ss << line << std::endl;
+ }
+ for (const auto& line : annotations_) {
+ ss << line << std::endl;
+ }
+ for (const auto& line : types_consts_globals_) {
+ ss << line << std::endl;
+ }
+ for (const auto& line : main_func_) {
+ ss << line << std::endl;
+ }
+ for (const auto& line : main_func_postamble_) {
+ ss << line << std::endl;
+ }
+ return ss.str();
+ }
+
+ private:
+ // Adds a given name to the Name section with OpName. If the given name has
+ // been added before, does nothing.
+ void AddOpNameIfNotExist(const std::string& id_name) {
+ if (!used_names_.count(id_name)) {
+ std::stringstream opname_inst;
+ opname_inst << "OpName "
+ << "%" << id_name << " \"" << id_name << "\"";
+ names_.emplace_back(opname_inst.str());
+ used_names_.insert(id_name);
+ }
+ }
+
+ // Adds the names in a vector of assembly code strings to the Names section.
+ // If a '=' sign is found in an instruction, this instruction will be treated
+ // as an ID defining instruction. The ID name used in the instruction will be
+ // extracted and added to the Names section.
+ void AddNamesForResultIDsIn(const std::vector<std::string>& vec_asm_code) {
+ for (const auto& line : vec_asm_code) {
+ std::string name = GetResultIDName(line);
+ if (!name.empty()) {
+ AddOpNameIfNotExist(name);
+ }
+ }
+ }
+
+ // Adds an OpDecorate SpecId instruction for the given ID name.
+ void AddSpecIDFor(const std::string& id_name) {
+ std::stringstream decorate_inst;
+ decorate_inst << "OpDecorate "
+ << "%" << id_name << " SpecId " << spec_id_counter_;
+ spec_id_counter_ += 1;
+ annotations_.emplace_back(decorate_inst.str());
+ }
+
+ // Extracts the ID name from a SPIR-V assembly instruction string. If the
+ // instruction is an ID-defining instruction (has result ID), returns the
+ // name of the result ID in string. If the instruction does not have result
+ // ID, returns an empty string.
+ std::string GetResultIDName(const std::string inst_str) {
+ std::string name;
+ if (inst_str.find('=') != std::string::npos) {
+ size_t assign_sign = inst_str.find('=');
+ name = inst_str.substr(0, assign_sign);
+ name.erase(remove_if(name.begin(), name.end(),
+ [](char c) { return c == ' ' || c == '%'; }),
+ name.end());
+ }
+ return name;
+ }
+
+ uint32_t spec_id_counter_;
+ // The vector that contains common preambles shared across all test SPIR-V
+ // code.
+ std::vector<std::string> global_preamble_;
+ // The vector that contains OpName instructions.
+ std::vector<std::string> names_;
+ // The vector that contains annotation instructions.
+ std::vector<std::string> annotations_;
+ // The vector that contains the code to declare types, constants and global
+ // variables (aka. the Types-Constants-Globals section).
+ std::vector<std::string> types_consts_globals_;
+ // The vector that contains the code in main function's entry block.
+ std::vector<std::string> main_func_;
+ // The vector that contains the postamble of main function body.
+ std::vector<std::string> main_func_postamble_;
+ // All of the defined variable names.
+ std::unordered_set<std::string> used_names_;
+};
+
+} // namespace spvtools
+
+#endif // LIBSPIRV_TEST_OPT_ASSEMBLY_BUILDER
diff --git a/test/opt/pass_fixture.h b/test/opt/pass_fixture.h
index 0c3b2df..1247184 100644
--- a/test/opt/pass_fixture.h
+++ b/test/opt/pass_fixture.h
@@ -29,6 +29,7 @@
#include <memory>
#include <string>
+#include <tuple>
#include <vector>
#include <gtest/gtest.h>
@@ -52,26 +53,50 @@
PassTest()
: tools_(SPV_ENV_UNIVERSAL_1_1), manager_(new opt::PassManager()) {}
+ // Runs the given |pass| on the binary assembled from the |assembly|, and
+ // disassebles the optimized binary. Returns a tuple of disassembly string
+ // and the boolean value returned from pass Process() function.
+ std::tuple<std::string, bool> OptimizeAndDisassemble(
+ opt::Pass* pass, const std::string& original, bool skip_nop = false) {
+ std::unique_ptr<ir::Module> module = tools_.BuildModule(original);
+ EXPECT_NE(nullptr, module);
+ if (!module) {
+ return std::make_tuple(std::string(), false);
+ }
+
+ const bool modified = pass->Process(module.get());
+
+ std::vector<uint32_t> binary;
+ module->ToBinary(&binary, skip_nop);
+ std::string optimized;
+ EXPECT_EQ(SPV_SUCCESS, tools_.Disassemble(binary, &optimized));
+ return std::make_tuple(optimized, modified);
+ }
+
+ // Runs a single pass of class |PassT| on the binary assembled from the
+ // |assembly|, disassembles the optimized binary. Returns a tuple of
+ // disassembly string and the boolean value from the pass Process() function.
+ template <typename PassT>
+ std::tuple<std::string, bool> SinglePassRunAndDisassemble(
+ const std::string& assembly, bool skip_nop = false) {
+ auto pass = std::unique_ptr<PassT>(new PassT);
+ return OptimizeAndDisassemble(pass.get(), assembly, skip_nop);
+ }
+
// Runs a single pass of class |PassT| on the binary assembled from the
// |original| assembly, and checks whether the optimized binary can be
// disassembled to the |expected| assembly. This does *not* involve pass
// manager. Callers are suggested to use SCOPED_TRACE() for better messages.
template <typename PassT>
void SinglePassRunAndCheck(const std::string& original,
- const std::string& expected) {
- std::unique_ptr<ir::Module> module = tools_.BuildModule(original);
- ASSERT_NE(nullptr, module);
-
- const bool modified =
- std::unique_ptr<PassT>(new PassT)->Process(module.get());
+ const std::string& expected,
+ bool skip_nop = false) {
+ std::string optimized;
+ bool modified = false;
+ std::tie(optimized, modified) =
+ SinglePassRunAndDisassemble<PassT>(original, skip_nop);
// Check whether the pass returns the correct modification indication.
EXPECT_EQ(original != expected, modified);
-
- std::vector<uint32_t> binary;
- module->ToBinary(&binary, /* skip_nop = */ false);
-
- std::string optimized;
- EXPECT_EQ(SPV_SUCCESS, tools_.Disassemble(binary, &optimized));
EXPECT_EQ(expected, optimized);
}
diff --git a/test/opt/pass_utils.cpp b/test/opt/pass_utils.cpp
index a6f2374..68bc1a4 100644
--- a/test/opt/pass_utils.cpp
+++ b/test/opt/pass_utils.cpp
@@ -43,7 +43,27 @@
// clang-format on
};
-// Returns true if the given string contains any debug opcode substring.
+} // anonymous namespace
+
+namespace spvtools {
+
+bool FindAndReplace(std::string* process_str, const std::string find_str,
+ const std::string replace_str) {
+ if (process_str->empty() || find_str.empty()) {
+ return false;
+ }
+ bool replaced = false;
+ // Note this algorithm has quadratic time complexity. It is OK for test cases
+ // with short strings, but might not fit in other contexts.
+ for (size_t pos = process_str->find(find_str, 0); pos != std::string::npos;
+ pos = process_str->find(find_str, pos)) {
+ process_str->replace(pos, find_str.length(), replace_str);
+ pos += replace_str.length();
+ replaced = true;
+ }
+ return replaced;
+}
+
bool ContainsDebugOpcode(const char* inst) {
return std::any_of(std::begin(kDebugOpcodes), std::end(kDebugOpcodes),
[inst](const char* op) {
@@ -51,10 +71,6 @@
});
}
-} // anonymous namespace
-
-namespace spvtools {
-
std::string SelectiveJoin(const std::vector<const char*>& strings,
const std::function<bool(const char*)>& skip_dictator,
char delimiter) {
diff --git a/test/opt/pass_utils.h b/test/opt/pass_utils.h
index 6e047db..f504eb5 100644
--- a/test/opt/pass_utils.h
+++ b/test/opt/pass_utils.h
@@ -33,6 +33,18 @@
namespace spvtools {
+// In-place substring replacement. Finds the |find_str| in the |process_str|
+// and replaces the found substring with |replace_str|. Returns true if at
+// least one replacement is done successfully, returns false otherwise. The
+// replaced substring won't be processed again, which means: If the
+// |replace_str| has |find_str| as its substring, that newly replaced part of
+// |process_str| won't be processed again.
+bool FindAndReplace(std::string* process_str, const std::string find_str,
+ const std::string replace_str);
+
+// Returns true if the given string contains any debug opcode substring.
+bool ContainsDebugOpcode(const char* inst);
+
// Returns the concatenated string from a vector of |strings|, with postfixing
// each string with the given |delimiter|. if the |skip_dictator| returns true
// for an original string, that string will be omitted.
diff --git a/test/opt/test_assembly_builder.cpp b/test/opt/test_assembly_builder.cpp
new file mode 100644
index 0000000..7303047
--- /dev/null
+++ b/test/opt/test_assembly_builder.cpp
@@ -0,0 +1,256 @@
+// Copyright (c) 2016 Google Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+// https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#include "assembly_builder.h"
+
+#include "pass_fixture.h"
+#include "pass_utils.h"
+
+namespace {
+
+using namespace spvtools;
+using AssemblyBuilderTest = PassTest<::testing::Test>;
+
+TEST_F(AssemblyBuilderTest, MinimalShader) {
+ AssemblyBuilder builder;
+ std::vector<const char*> expected = {
+ // clang-format off
+ "OpCapability Shader",
+ "OpCapability Float64",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %main \"main\"",
+ "OpName %void \"void\"",
+ "OpName %main_func_type \"main_func_type\"",
+ "OpName %main \"main\"",
+ "OpName %main_func_entry_block \"main_func_entry_block\"",
+ "%void = OpTypeVoid",
+ "%main_func_type = OpTypeFunction %void",
+ "%main = OpFunction %void None %main_func_type",
+"%main_func_entry_block = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ SinglePassRunAndCheck<opt::NullPass>(builder.GetCode(),
+ JoinAllInsts(expected));
+}
+
+TEST_F(AssemblyBuilderTest, ShaderWithConstants) {
+ AssemblyBuilder builder;
+ builder
+ .AppendTypesConstantsGlobals({
+ // clang-format off
+ "%bool = OpTypeBool",
+ "%_PF_bool = OpTypePointer Function %bool",
+ "%bt = OpConstantTrue %bool",
+ "%bf = OpConstantFalse %bool",
+ "%int = OpTypeInt 32 1",
+ "%_PF_int = OpTypePointer Function %int",
+ "%si = OpConstant %int 1",
+ "%uint = OpTypeInt 32 0",
+ "%_PF_uint = OpTypePointer Function %uint",
+ "%ui = OpConstant %uint 2",
+ "%float = OpTypeFloat 32",
+ "%_PF_float = OpTypePointer Function %float",
+ "%f = OpConstant %float 3.14",
+ "%double = OpTypeFloat 64",
+ "%_PF_double = OpTypePointer Function %double",
+ "%d = OpConstant %double 3.14159265358979",
+ // clang-format on
+ })
+ .AppendInMain({
+ // clang-format off
+ "%btv = OpVariable %_PF_bool Function",
+ "%bfv = OpVariable %_PF_bool Function",
+ "%iv = OpVariable %_PF_int Function",
+ "%uv = OpVariable %_PF_uint Function",
+ "%fv = OpVariable %_PF_float Function",
+ "%dv = OpVariable %_PF_double Function",
+ "OpStore %btv %bt",
+ "OpStore %bfv %bf",
+ "OpStore %iv %si",
+ "OpStore %uv %ui",
+ "OpStore %fv %f",
+ "OpStore %dv %d",
+ // clang-format on
+ });
+
+ std::vector<const char*> expected = {
+ // clang-format off
+ "OpCapability Shader",
+ "OpCapability Float64",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %main \"main\"",
+ "OpName %void \"void\"",
+ "OpName %main_func_type \"main_func_type\"",
+ "OpName %main \"main\"",
+ "OpName %main_func_entry_block \"main_func_entry_block\"",
+ "OpName %bool \"bool\"",
+ "OpName %_PF_bool \"_PF_bool\"",
+ "OpName %bt \"bt\"",
+ "OpName %bf \"bf\"",
+ "OpName %int \"int\"",
+ "OpName %_PF_int \"_PF_int\"",
+ "OpName %si \"si\"",
+ "OpName %uint \"uint\"",
+ "OpName %_PF_uint \"_PF_uint\"",
+ "OpName %ui \"ui\"",
+ "OpName %float \"float\"",
+ "OpName %_PF_float \"_PF_float\"",
+ "OpName %f \"f\"",
+ "OpName %double \"double\"",
+ "OpName %_PF_double \"_PF_double\"",
+ "OpName %d \"d\"",
+ "OpName %btv \"btv\"",
+ "OpName %bfv \"bfv\"",
+ "OpName %iv \"iv\"",
+ "OpName %uv \"uv\"",
+ "OpName %fv \"fv\"",
+ "OpName %dv \"dv\"",
+ "%void = OpTypeVoid",
+"%main_func_type = OpTypeFunction %void",
+ "%bool = OpTypeBool",
+ "%_PF_bool = OpTypePointer Function %bool",
+ "%bt = OpConstantTrue %bool",
+ "%bf = OpConstantFalse %bool",
+ "%int = OpTypeInt 32 1",
+ "%_PF_int = OpTypePointer Function %int",
+ "%si = OpConstant %int 1",
+ "%uint = OpTypeInt 32 0",
+ "%_PF_uint = OpTypePointer Function %uint",
+ "%ui = OpConstant %uint 2",
+ "%float = OpTypeFloat 32",
+ "%_PF_float = OpTypePointer Function %float",
+ "%f = OpConstant %float 3.14",
+ "%double = OpTypeFloat 64",
+ "%_PF_double = OpTypePointer Function %double",
+ "%d = OpConstant %double 3.14159265358979",
+ "%main = OpFunction %void None %main_func_type",
+"%main_func_entry_block = OpLabel",
+ "%btv = OpVariable %_PF_bool Function",
+ "%bfv = OpVariable %_PF_bool Function",
+ "%iv = OpVariable %_PF_int Function",
+ "%uv = OpVariable %_PF_uint Function",
+ "%fv = OpVariable %_PF_float Function",
+ "%dv = OpVariable %_PF_double Function",
+ "OpStore %btv %bt",
+ "OpStore %bfv %bf",
+ "OpStore %iv %si",
+ "OpStore %uv %ui",
+ "OpStore %fv %f",
+ "OpStore %dv %d",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+ SinglePassRunAndCheck<opt::NullPass>(builder.GetCode(),
+ JoinAllInsts(expected));
+}
+
+TEST_F(AssemblyBuilderTest, SpecConstants) {
+ AssemblyBuilder builder;
+ builder.AppendTypesConstantsGlobals({
+ "%bool = OpTypeBool", "%uint = OpTypeInt 32 0", "%int = OpTypeInt 32 1",
+ "%float = OpTypeFloat 32", "%double = OpTypeFloat 64",
+ "%v2int = OpTypeVector %int 2",
+
+ "%spec_true = OpSpecConstantTrue %bool",
+ "%spec_false = OpSpecConstantFalse %bool",
+ "%spec_uint = OpSpecConstant %uint 1",
+ "%spec_int = OpSpecConstant %int 1",
+ "%spec_float = OpSpecConstant %float 1.2",
+ "%spec_double = OpSpecConstant %double 1.23456789",
+
+ // Spec constants defined below should not have SpecID.
+ "%spec_add_op = OpSpecConstantOp %int IAdd %spec_int %spec_int",
+ "%spec_vec = OpSpecConstantComposite %v2int %spec_int %spec_int",
+ "%spec_vec_x = OpSpecConstantOp %int CompositeExtract %spec_vec 0",
+ });
+ std::vector<const char*> expected = {
+ // clang-format off
+ "OpCapability Shader",
+ "OpCapability Float64",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %main \"main\"",
+ "OpName %void \"void\"",
+ "OpName %main_func_type \"main_func_type\"",
+ "OpName %main \"main\"",
+ "OpName %main_func_entry_block \"main_func_entry_block\"",
+ "OpName %bool \"bool\"",
+ "OpName %uint \"uint\"",
+ "OpName %int \"int\"",
+ "OpName %float \"float\"",
+ "OpName %double \"double\"",
+ "OpName %v2int \"v2int\"",
+ "OpName %spec_true \"spec_true\"",
+ "OpName %spec_false \"spec_false\"",
+ "OpName %spec_uint \"spec_uint\"",
+ "OpName %spec_int \"spec_int\"",
+ "OpName %spec_float \"spec_float\"",
+ "OpName %spec_double \"spec_double\"",
+ "OpName %spec_add_op \"spec_add_op\"",
+ "OpName %spec_vec \"spec_vec\"",
+ "OpName %spec_vec_x \"spec_vec_x\"",
+ "OpDecorate %spec_true SpecId 200",
+ "OpDecorate %spec_false SpecId 201",
+ "OpDecorate %spec_uint SpecId 202",
+ "OpDecorate %spec_int SpecId 203",
+ "OpDecorate %spec_float SpecId 204",
+ "OpDecorate %spec_double SpecId 205",
+ "%void = OpTypeVoid",
+ "%main_func_type = OpTypeFunction %void",
+ "%bool = OpTypeBool",
+ "%uint = OpTypeInt 32 0",
+ "%int = OpTypeInt 32 1",
+ "%float = OpTypeFloat 32",
+ "%double = OpTypeFloat 64",
+ "%v2int = OpTypeVector %int 2",
+ "%spec_true = OpSpecConstantTrue %bool",
+ "%spec_false = OpSpecConstantFalse %bool",
+ "%spec_uint = OpSpecConstant %uint 1",
+ "%spec_int = OpSpecConstant %int 1",
+ "%spec_float = OpSpecConstant %float 1.2",
+ "%spec_double = OpSpecConstant %double 1.23456789",
+ "%spec_add_op = OpSpecConstantOp %int IAdd %spec_int %spec_int",
+ "%spec_vec = OpSpecConstantComposite %v2int %spec_int %spec_int",
+ "%spec_vec_x = OpSpecConstantOp %int CompositeExtract %spec_vec 0",
+ "%main = OpFunction %void None %main_func_type",
+"%main_func_entry_block = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd",
+
+ // clang-format on
+ };
+
+ SinglePassRunAndCheck<opt::NullPass>(builder.GetCode(),
+ JoinAllInsts(expected));
+}
+
+} // anonymous namespace
diff --git a/test/opt/test_def_use.cpp b/test/opt/test_def_use.cpp
new file mode 100644
index 0000000..9bcc114
--- /dev/null
+++ b/test/opt/test_def_use.cpp
@@ -0,0 +1,1158 @@
+// Copyright (c) 2016 Google Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+// https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#include <memory>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "opt/def_use_manager.h"
+#include "opt/libspirv.hpp"
+#include "pass_utils.h"
+
+namespace {
+
+using ::testing::ElementsAre;
+
+using namespace spvtools;
+using spvtools::opt::analysis::DefUseManager;
+
+// Disassembles the given |inst| and returns the disassembly.
+std::string DisassembleInst(ir::Instruction* inst) {
+ SpvTools tools(SPV_ENV_UNIVERSAL_1_1);
+
+ std::vector<uint32_t> binary;
+ // We need this to generate the necessary header in the binary.
+ tools.Assemble("", &binary);
+ inst->ToBinary(&binary, /* skip_nop = */ false);
+
+ std::string text;
+ // We'll need to check the underlying id numbers.
+ // So turn off friendly names for ids.
+ tools.Disassemble(binary, &text, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ while (!text.empty() && text.back() == '\n') text.pop_back();
+ return text;
+}
+
+// A struct for holding expected id defs and uses.
+struct InstDefUse {
+ using IdInstPair = std::pair<uint32_t, const char*>;
+ using IdInstsPair = std::pair<uint32_t, std::vector<const char*>>;
+
+ // Ids and their corresponding def instructions.
+ std::vector<IdInstPair> defs;
+ // Ids and their corresponding use instructions.
+ std::vector<IdInstsPair> uses;
+};
+
+// Checks that the |actual_defs| and |actual_uses| are in accord with
+// |expected_defs_uses|.
+void CheckDef(const InstDefUse& expected_defs_uses,
+ const DefUseManager::IdToDefMap& actual_defs) {
+ // Check defs.
+ ASSERT_EQ(expected_defs_uses.defs.size(), actual_defs.size());
+ for (uint32_t i = 0; i < expected_defs_uses.defs.size(); ++i) {
+ const auto id = expected_defs_uses.defs[i].first;
+ const auto expected_def = expected_defs_uses.defs[i].second;
+ ASSERT_EQ(1u, actual_defs.count(id)) << "expected to def id [" << id << "]";
+ EXPECT_EQ(expected_def, DisassembleInst(actual_defs.at(id)));
+ }
+}
+
+void CheckUse(const InstDefUse& expected_defs_uses,
+ const DefUseManager::IdToUsesMap& actual_uses) {
+ // Check uses.
+ ASSERT_EQ(expected_defs_uses.uses.size(), actual_uses.size());
+ for (uint32_t i = 0; i < expected_defs_uses.uses.size(); ++i) {
+ const auto id = expected_defs_uses.uses[i].first;
+ const auto& expected_uses = expected_defs_uses.uses[i].second;
+
+ ASSERT_EQ(1u, actual_uses.count(id)) << "expected to use id [" << id << "]";
+ const auto& uses = actual_uses.at(id);
+
+ ASSERT_EQ(expected_uses.size(), uses.size())
+ << "id [" << id << "] # uses: expected: " << expected_uses.size()
+ << " actual: " << uses.size();
+ auto it = uses.cbegin();
+ for (const auto expected_use : expected_uses) {
+ EXPECT_EQ(expected_use, DisassembleInst(it->inst))
+ << "id [" << id << "] use instruction mismatch";
+ ++it;
+ }
+ }
+}
+
+// The following test case mimics how LLVM handles induction variables.
+// But, yeah, it's not very readable. However, we only care about the id
+// defs and uses. So, no need to make sure this is valid OpPhi construct.
+const char kOpPhiTestFunction[] =
+ " %2 = OpFunction %1 None %3 "
+ " %4 = OpLabel "
+ " OpBranch %5 "
+
+ " %5 = OpLabel "
+ " %7 = OpPhi %6 %8 %4 %9 %5 "
+ "%11 = OpPhi %10 %12 %4 %13 %5 "
+ " %9 = OpIAdd %6 %7 %14 "
+ "%13 = OpFAdd %10 %11 %15 "
+ "%17 = OpSLessThan %16 %7 %18 "
+ " OpLoopMerge %19 %5 None "
+ " OpBranchConditional %17 %5 %19 "
+
+ "%19 = OpLabel "
+ " OpReturn "
+ " OpFunctionEnd";
+
+struct ParseDefUseCase {
+ const char* text;
+ InstDefUse du;
+};
+
+using ParseDefUseTest = ::testing::TestWithParam<ParseDefUseCase>;
+
+TEST_P(ParseDefUseTest, Case) {
+ const auto& tc = GetParam();
+
+ // Build module.
+ const std::vector<const char*> text = {tc.text};
+ std::unique_ptr<ir::Module> module =
+ SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(JoinAllInsts(text));
+ ASSERT_NE(nullptr, module);
+
+ // Analyze def and use.
+ opt::analysis::DefUseManager manager;
+ manager.AnalyzeDefUse(module.get());
+
+ CheckDef(tc.du, manager.id_to_defs());
+ CheckUse(tc.du, manager.id_to_uses());
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(
+ TestCase, ParseDefUseTest,
+ ::testing::ValuesIn(std::vector<ParseDefUseCase>{
+ {"", {{}, {}}}, // no instruction
+ {"OpMemoryModel Logical GLSL450", {{}, {}}}, // no def and use
+ { // single def, no use
+ "%1 = OpString \"wow\"",
+ {
+ {{1, "%1 = OpString \"wow\""}}, // defs
+ {} // uses
+ }
+ },
+ { // multiple def, no use
+ "%1 = OpString \"hello\" "
+ "%2 = OpString \"world\" "
+ "%3 = OpTypeVoid",
+ {
+ { // defs
+ {1, "%1 = OpString \"hello\""},
+ {2, "%2 = OpString \"world\""},
+ {3, "%3 = OpTypeVoid"},
+ },
+ {} // uses
+ }
+ },
+ { // single use, no def
+ "OpTypeForwardPointer %1 Input",
+ {
+ {}, // defs
+ { // uses
+ {1, {"OpTypeForwardPointer %1 Input"}},
+ }
+ }
+ },
+ { // multiple use, no def
+ "OpEntryPoint Fragment %1 \"main\" "
+ "OpTypeForwardPointer %2 Input "
+ "OpTypeForwardPointer %3 Output",
+ {
+ {}, // defs
+ { // uses
+ {1, {"OpEntryPoint Fragment %1 \"main\""}},
+ {2, {"OpTypeForwardPointer %2 Input"}},
+ {3, {"OpTypeForwardPointer %3 Output"}},
+ }
+ }
+ },
+ { // multiple def, multiple use
+ "%1 = OpTypeBool "
+ "%2 = OpTypeVector %1 3 "
+ "%3 = OpTypeMatrix %2 3",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpTypeVector %1 3"},
+ {3, "%3 = OpTypeMatrix %2 3"},
+ },
+ { // uses
+ {1, {"%2 = OpTypeVector %1 3"}},
+ {2, {"%3 = OpTypeMatrix %2 3"}},
+ }
+ }
+ },
+ { // multiple use of the same id
+ "%1 = OpTypeBool "
+ "%2 = OpTypeVector %1 2 "
+ "%3 = OpTypeVector %1 3 "
+ "%4 = OpTypeVector %1 4",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpTypeVector %1 2"},
+ {3, "%3 = OpTypeVector %1 3"},
+ {4, "%4 = OpTypeVector %1 4"},
+ },
+ { // uses
+ {1,
+ {
+ "%2 = OpTypeVector %1 2",
+ "%3 = OpTypeVector %1 3",
+ "%4 = OpTypeVector %1 4",
+ }
+ },
+ }
+ }
+ },
+ { // labels
+ "%2 = OpFunction %1 None %3 "
+
+ "%4 = OpLabel "
+ "OpBranchConditional %5 %6 %7 "
+
+ "%6 = OpLabel "
+ "OpBranch %7 "
+
+ "%7 = OpLabel "
+ "OpReturn "
+
+ "OpFunctionEnd",
+ {
+ { // defs
+ {2, "%2 = OpFunction %1 None %3"},
+ {4, "%4 = OpLabel"},
+ {6, "%6 = OpLabel"},
+ {7, "%7 = OpLabel"},
+ },
+ { // uses
+ {1, {"%2 = OpFunction %1 None %3"}},
+ {3, {"%2 = OpFunction %1 None %3"}},
+ {5, {"OpBranchConditional %5 %6 %7"}},
+ {6, {"OpBranchConditional %5 %6 %7"}},
+ {7,
+ {
+ "OpBranchConditional %5 %6 %7",
+ "OpBranch %7",
+ }
+ },
+ }
+ }
+ },
+ { // cross function
+ "%1 = OpTypeBool "
+
+ "%2 = OpFunction %1 None %3 "
+
+ "%4 = OpLabel "
+ "%5 = OpVariable %1 Function "
+ "%6 = OpFunctionCall %1 %2 %5 "
+ "OpReturnValue %6 "
+
+ "OpFunctionEnd",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpFunction %1 None %3"},
+ {4, "%4 = OpLabel"},
+ {5, "%5 = OpVariable %1 Function"},
+ {6, "%6 = OpFunctionCall %1 %2 %5"},
+ },
+ { // uses
+ {1,
+ {
+ "%2 = OpFunction %1 None %3",
+ "%5 = OpVariable %1 Function",
+ "%6 = OpFunctionCall %1 %2 %5",
+ }
+ },
+ {2, {"%6 = OpFunctionCall %1 %2 %5"}},
+ {5, {"%6 = OpFunctionCall %1 %2 %5"}},
+ {3, {"%2 = OpFunction %1 None %3"}},
+ {6, {"OpReturnValue %6"}},
+ }
+ }
+ },
+ { // selection merge and loop merge
+ "%2 = OpFunction %1 None %3 "
+
+ "%4 = OpLabel "
+ "OpLoopMerge %5 %4 None "
+ "OpBranch %6 "
+
+ "%5 = OpLabel "
+ "OpReturn "
+
+ "%6 = OpLabel "
+ "OpSelectionMerge %7 None "
+ "OpBranchConditional %8 %9 %7 "
+
+ "%7 = OpLabel "
+ "OpReturn "
+
+ "%9 = OpLabel "
+ "OpReturn "
+
+ "OpFunctionEnd",
+ {
+ { // defs
+ {2, "%2 = OpFunction %1 None %3"},
+ {4, "%4 = OpLabel"},
+ {5, "%5 = OpLabel"},
+ {6, "%6 = OpLabel"},
+ {7, "%7 = OpLabel"},
+ {9, "%9 = OpLabel"},
+ },
+ { // uses
+ {1, {"%2 = OpFunction %1 None %3"}},
+ {3, {"%2 = OpFunction %1 None %3"}},
+ {4, {"OpLoopMerge %5 %4 None"}},
+ {5, {"OpLoopMerge %5 %4 None"}},
+ {6, {"OpBranch %6"}},
+ {7,
+ {
+ "OpSelectionMerge %7 None",
+ "OpBranchConditional %8 %9 %7",
+ }
+ },
+ {8, {"OpBranchConditional %8 %9 %7"}},
+ {9, {"OpBranchConditional %8 %9 %7"}},
+ }
+ }
+ },
+ { // Forward reference
+ "OpDecorate %1 Block "
+ "OpTypeForwardPointer %2 Input "
+ "%3 = OpTypeInt 32 0 "
+ "%1 = OpTypeStruct %3 "
+ "%2 = OpTypePointer Input %3",
+ {
+ { // defs
+ {1, "%1 = OpTypeStruct %3"},
+ {2, "%2 = OpTypePointer Input %3"},
+ {3, "%3 = OpTypeInt 32 0"},
+ },
+ { // uses
+ {1, {"OpDecorate %1 Block"}},
+ {2, {"OpTypeForwardPointer %2 Input"}},
+ {3,
+ {
+ "%1 = OpTypeStruct %3",
+ "%2 = OpTypePointer Input %3",
+ }
+ }
+ },
+ },
+ },
+ { // OpPhi
+ kOpPhiTestFunction,
+ {
+ { // defs
+ {2, "%2 = OpFunction %1 None %3"},
+ {4, "%4 = OpLabel"},
+ {5, "%5 = OpLabel"},
+ {7, "%7 = OpPhi %6 %8 %4 %9 %5"},
+ {9, "%9 = OpIAdd %6 %7 %14"},
+ {11, "%11 = OpPhi %10 %12 %4 %13 %5"},
+ {13, "%13 = OpFAdd %10 %11 %15"},
+ {17, "%17 = OpSLessThan %16 %7 %18"},
+ {19, "%19 = OpLabel"},
+ },
+ { // uses
+ {1, {"%2 = OpFunction %1 None %3"}},
+ {3, {"%2 = OpFunction %1 None %3"}},
+ {4,
+ {
+ "%7 = OpPhi %6 %8 %4 %9 %5",
+ "%11 = OpPhi %10 %12 %4 %13 %5",
+ }
+ },
+ {5,
+ {
+ "OpBranch %5",
+ "%7 = OpPhi %6 %8 %4 %9 %5",
+ "%11 = OpPhi %10 %12 %4 %13 %5",
+ "OpLoopMerge %19 %5 None",
+ "OpBranchConditional %17 %5 %19",
+ }
+ },
+ {6,
+ {
+ "%7 = OpPhi %6 %8 %4 %9 %5",
+ "%9 = OpIAdd %6 %7 %14",
+ }
+ },
+ {7,
+ {
+ "%9 = OpIAdd %6 %7 %14",
+ "%17 = OpSLessThan %16 %7 %18",
+ }
+ },
+ {8, {"%7 = OpPhi %6 %8 %4 %9 %5"}},
+ {9, {"%7 = OpPhi %6 %8 %4 %9 %5"}},
+ {10,
+ {
+ "%11 = OpPhi %10 %12 %4 %13 %5",
+ "%13 = OpFAdd %10 %11 %15",
+ }
+ },
+ {11, {"%13 = OpFAdd %10 %11 %15"}},
+ {12, {"%11 = OpPhi %10 %12 %4 %13 %5"}},
+ {13, {"%11 = OpPhi %10 %12 %4 %13 %5"}},
+ {14, {"%9 = OpIAdd %6 %7 %14"}},
+ {15, {"%13 = OpFAdd %10 %11 %15"}},
+ {16, {"%17 = OpSLessThan %16 %7 %18"}},
+ {17, {"OpBranchConditional %17 %5 %19"}},
+ {18, {"%17 = OpSLessThan %16 %7 %18"}},
+ {19,
+ {
+ "OpLoopMerge %19 %5 None",
+ "OpBranchConditional %17 %5 %19",
+ }
+ },
+ },
+ },
+ },
+ { // OpPhi defining and referencing the same id.
+ "%1 = OpTypeBool "
+ "%2 = OpConstantTrue %1 "
+
+ "%4 = OpFunction %3 None %5 "
+ "%6 = OpLabel "
+ " OpBranch %7 "
+ "%7 = OpLabel "
+ "%8 = OpPhi %1 %8 %7 %2 %6 " // both defines and uses %8
+ " OpBranch %7 "
+ " OpFunctionEnd",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpConstantTrue %1"},
+ {4, "%4 = OpFunction %3 None %5"},
+ {6, "%6 = OpLabel"},
+ {7, "%7 = OpLabel"},
+ {8, "%8 = OpPhi %1 %8 %7 %2 %6"},
+ },
+ { // uses
+ {1,
+ {
+ "%2 = OpConstantTrue %1",
+ "%8 = OpPhi %1 %8 %7 %2 %6",
+ }
+ },
+ {2, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
+ {3, {"%4 = OpFunction %3 None %5"}},
+ {5, {"%4 = OpFunction %3 None %5"}},
+ {6, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
+ {7,
+ {
+ "OpBranch %7",
+ "%8 = OpPhi %1 %8 %7 %2 %6",
+ "OpBranch %7",
+ }
+ },
+ {8, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
+ },
+ },
+ },
+ })
+);
+// clang-format on
+
+struct ReplaceUseCase {
+ const char* before;
+ std::vector<std::pair<uint32_t, uint32_t>> candidates;
+ const char* after;
+ InstDefUse du;
+};
+
+using ReplaceUseTest = ::testing::TestWithParam<ReplaceUseCase>;
+
+// Disassembles the given |module| and returns the disassembly.
+std::string DisassembleModule(ir::Module* module) {
+ SpvTools tools(SPV_ENV_UNIVERSAL_1_1);
+
+ std::vector<uint32_t> binary;
+ module->ToBinary(&binary, /* skip_nop = */ false);
+
+ std::string text;
+ // We'll need to check the underlying id numbers.
+ // So turn off friendly names for ids.
+ tools.Disassemble(binary, &text, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ while (!text.empty() && text.back() == '\n') text.pop_back();
+ return text;
+}
+
+TEST_P(ReplaceUseTest, Case) {
+ const auto& tc = GetParam();
+
+ // Build module.
+ const std::vector<const char*> text = {tc.before};
+ std::unique_ptr<ir::Module> module =
+ SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(JoinAllInsts(text));
+ ASSERT_NE(nullptr, module);
+
+ // Analyze def and use.
+ opt::analysis::DefUseManager manager;
+ manager.AnalyzeDefUse(module.get());
+
+ // Do the substitution.
+ for (const auto& candiate : tc.candidates) {
+ manager.ReplaceAllUsesWith(candiate.first, candiate.second);
+ }
+
+ EXPECT_EQ(tc.after, DisassembleModule(module.get()));
+ CheckDef(tc.du, manager.id_to_defs());
+ CheckUse(tc.du, manager.id_to_uses());
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(
+ TestCase, ReplaceUseTest,
+ ::testing::ValuesIn(std::vector<ReplaceUseCase>{
+ { // no use, no replace request
+ "", {}, "", {},
+ },
+ { // no use, some replace requests
+ "OpMemoryModel Logical GLSL450",
+ {{1, 2}, {3, 4}, {7, 8}, {7, 9}, {7, 10}, {2, 10}, {3, 10}},
+ "OpMemoryModel Logical GLSL450",
+ {},
+ },
+ { // replace one use
+ "%1 = OpTypeBool "
+ "%2 = OpTypeVector %1 3",
+ {{1, 3}},
+ "%1 = OpTypeBool\n"
+ "%2 = OpTypeVector %3 3",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpTypeVector %3 3"},
+ },
+ { // uses
+ {3, {"%2 = OpTypeVector %3 3"}},
+ },
+ },
+ },
+ { // replace and then replace back
+ "%1 = OpTypeBool "
+ "%2 = OpTypeVector %1 3",
+ {{1, 3}, {3, 1}},
+ "%1 = OpTypeBool\n"
+ "%2 = OpTypeVector %1 3",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpTypeVector %1 3"},
+ },
+ { // uses
+ {1, {"%2 = OpTypeVector %1 3"}},
+ },
+ },
+ },
+ { // replace with the same id
+ "%1 = OpTypeBool "
+ "%2 = OpTypeVector %1 3",
+ {{1, 1}, {2, 2}, {3, 3}},
+ "%1 = OpTypeBool\n"
+ "%2 = OpTypeVector %1 3",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpTypeVector %1 3"},
+ },
+ { // uses
+ {1, {"%2 = OpTypeVector %1 3"}},
+ },
+ },
+ },
+ { // replace in sequence
+ "%1 = OpTypeBool "
+ "%2 = OpTypeVector %1 3",
+ {{1, 3}, {3, 4}, {4, 5}, {5, 100}},
+ "%1 = OpTypeBool\n"
+ "%2 = OpTypeVector %100 3",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpTypeVector %100 3"},
+ },
+ { // uses
+ {100, {"%2 = OpTypeVector %100 3"}},
+ },
+ },
+ },
+ { // replace multiple uses
+ "%1 = OpTypeBool "
+ "%2 = OpTypeVector %1 2 "
+ "%3 = OpTypeVector %1 3 "
+ "%4 = OpTypeVector %1 4 "
+ "%5 = OpTypeMatrix %2 2 "
+ "%6 = OpTypeMatrix %3 3 "
+ "%7 = OpTypeMatrix %4 4",
+ {{1, 10}, {2, 20}, {4, 40}},
+ "%1 = OpTypeBool\n"
+ "%2 = OpTypeVector %10 2\n"
+ "%3 = OpTypeVector %10 3\n"
+ "%4 = OpTypeVector %10 4\n"
+ "%5 = OpTypeMatrix %20 2\n"
+ "%6 = OpTypeMatrix %3 3\n"
+ "%7 = OpTypeMatrix %40 4",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpTypeVector %10 2"},
+ {3, "%3 = OpTypeVector %10 3"},
+ {4, "%4 = OpTypeVector %10 4"},
+ {5, "%5 = OpTypeMatrix %20 2"},
+ {6, "%6 = OpTypeMatrix %3 3"},
+ {7, "%7 = OpTypeMatrix %40 4"},
+ },
+ { // uses
+ {10,
+ {
+ "%2 = OpTypeVector %10 2",
+ "%3 = OpTypeVector %10 3",
+ "%4 = OpTypeVector %10 4",
+ }
+ },
+ {20, {"%5 = OpTypeMatrix %20 2"}},
+ {3, {"%6 = OpTypeMatrix %3 3"}},
+ {40, {"%7 = OpTypeMatrix %40 4"}},
+ },
+ },
+ },
+ { // OpPhi.
+ kOpPhiTestFunction,
+ // replace one id used by OpPhi, replace one id generated by OpPhi
+ {{9, 9000}, {11, 9}},
+ "%2 = OpFunction %1 None %3\n"
+ "%4 = OpLabel\n"
+ "OpBranch %5\n"
+
+ "%5 = OpLabel\n"
+ "%7 = OpPhi %6 %8 %4 %9000 %5\n" // %9 -> %9000
+ "%11 = OpPhi %10 %12 %4 %13 %5\n"
+ "%9 = OpIAdd %6 %7 %14\n"
+ "%13 = OpFAdd %10 %9 %15\n" // %11 -> %9
+ "%17 = OpSLessThan %16 %7 %18\n"
+ "OpLoopMerge %19 %5 None\n"
+ "OpBranchConditional %17 %5 %19\n"
+
+ "%19 = OpLabel\n"
+ "OpReturn\n"
+ "OpFunctionEnd",
+ {
+ { // defs.
+ {2, "%2 = OpFunction %1 None %3"},
+ {4, "%4 = OpLabel"},
+ {5, "%5 = OpLabel"},
+ {7, "%7 = OpPhi %6 %8 %4 %9000 %5"},
+ {9, "%9 = OpIAdd %6 %7 %14"},
+ {11, "%11 = OpPhi %10 %12 %4 %13 %5"},
+ {13, "%13 = OpFAdd %10 %9 %15"},
+ {17, "%17 = OpSLessThan %16 %7 %18"},
+ {19, "%19 = OpLabel"},
+ },
+ { // uses
+ {1, {"%2 = OpFunction %1 None %3"}},
+ {3, {"%2 = OpFunction %1 None %3"}},
+ {4,
+ {
+ "%7 = OpPhi %6 %8 %4 %9000 %5",
+ "%11 = OpPhi %10 %12 %4 %13 %5",
+ }
+ },
+ {5,
+ {
+ "OpBranch %5",
+ "%7 = OpPhi %6 %8 %4 %9000 %5",
+ "%11 = OpPhi %10 %12 %4 %13 %5",
+ "OpLoopMerge %19 %5 None",
+ "OpBranchConditional %17 %5 %19",
+ }
+ },
+ {6,
+ {
+ "%7 = OpPhi %6 %8 %4 %9000 %5",
+ "%9 = OpIAdd %6 %7 %14",
+ }
+ },
+ {7,
+ {
+ "%9 = OpIAdd %6 %7 %14",
+ "%17 = OpSLessThan %16 %7 %18",
+ }
+ },
+ {8, {"%7 = OpPhi %6 %8 %4 %9000 %5"}},
+ {9, {"%13 = OpFAdd %10 %9 %15"}}, // uses of %9 changed from %7 to %13
+ {10,
+ {
+ "%11 = OpPhi %10 %12 %4 %13 %5",
+ "%13 = OpFAdd %10 %9 %15",
+ }
+ },
+ // no more uses of %11
+ {12, {"%11 = OpPhi %10 %12 %4 %13 %5"}},
+ {13, {"%11 = OpPhi %10 %12 %4 %13 %5"}},
+ {14, {"%9 = OpIAdd %6 %7 %14"}},
+ {15, {"%13 = OpFAdd %10 %9 %15"}},
+ {16, {"%17 = OpSLessThan %16 %7 %18"}},
+ {17, {"OpBranchConditional %17 %5 %19"}},
+ {18, {"%17 = OpSLessThan %16 %7 %18"}},
+ {19,
+ {
+ "OpLoopMerge %19 %5 None",
+ "OpBranchConditional %17 %5 %19",
+ }
+ },
+ // new uses of %9000
+ {9000, {"%7 = OpPhi %6 %8 %4 %9000 %5"}},
+ },
+ },
+ },
+ { // OpPhi defining and referencing the same id.
+ "%1 = OpTypeBool "
+ "%2 = OpConstantTrue %1 "
+
+ "%4 = OpFunction %3 None %5 "
+ "%6 = OpLabel "
+ " OpBranch %7 "
+ "%7 = OpLabel "
+ "%8 = OpPhi %1 %8 %7 %2 %6 " // both defines and uses %8
+ " OpBranch %7 "
+ " OpFunctionEnd",
+ {{8, 2}},
+ "%1 = OpTypeBool\n"
+ "%2 = OpConstantTrue %1\n"
+
+ "%4 = OpFunction %3 None %5\n"
+ "%6 = OpLabel\n"
+ "OpBranch %7\n"
+ "%7 = OpLabel\n"
+ "%8 = OpPhi %1 %2 %7 %2 %6\n" // use of %8 changed to %2
+ "OpBranch %7\n"
+ "OpFunctionEnd",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpConstantTrue %1"},
+ {4, "%4 = OpFunction %3 None %5"},
+ {6, "%6 = OpLabel"},
+ {7, "%7 = OpLabel"},
+ {8, "%8 = OpPhi %1 %2 %7 %2 %6"},
+ },
+ { // uses
+ {1,
+ {
+ "%2 = OpConstantTrue %1",
+ "%8 = OpPhi %1 %2 %7 %2 %6",
+ }
+ },
+ {2,
+ {
+ // TODO(antiagainst): address this.
+ // We have duplication here because we didn't check existence
+ // before inserting uses.
+ "%8 = OpPhi %1 %2 %7 %2 %6",
+ "%8 = OpPhi %1 %2 %7 %2 %6",
+ }
+ },
+ {3, {"%4 = OpFunction %3 None %5"}},
+ {5, {"%4 = OpFunction %3 None %5"}},
+ {6, {"%8 = OpPhi %1 %2 %7 %2 %6"}},
+ {7,
+ {
+ "OpBranch %7",
+ "%8 = OpPhi %1 %2 %7 %2 %6",
+ "OpBranch %7",
+ }
+ },
+ // {8, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
+ },
+ },
+ },
+ })
+);
+// clang-format on
+
+struct KillDefCase {
+ const char* before;
+ std::vector<uint32_t> ids_to_kill;
+ const char* after;
+ InstDefUse du;
+};
+
+using KillDefTest = ::testing::TestWithParam<KillDefCase>;
+
+TEST_P(KillDefTest, Case) {
+ const auto& tc = GetParam();
+
+ // Build module.
+ const std::vector<const char*> text = {tc.before};
+ std::unique_ptr<ir::Module> module =
+ SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(JoinAllInsts(text));
+ ASSERT_NE(nullptr, module);
+
+ // Analyze def and use.
+ opt::analysis::DefUseManager manager;
+ manager.AnalyzeDefUse(module.get());
+
+ // Do the substitution.
+ for (const auto id : tc.ids_to_kill) manager.KillDef(id);
+
+ EXPECT_EQ(tc.after, DisassembleModule(module.get()));
+ CheckDef(tc.du, manager.id_to_defs());
+ CheckUse(tc.du, manager.id_to_uses());
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(
+ TestCase, KillDefTest,
+ ::testing::ValuesIn(std::vector<KillDefCase>{
+ { // no def, no use, no kill
+ "", {}, "", {}
+ },
+ { // kill nothing
+ "%1 = OpTypeBool "
+ "%2 = OpTypeVector %1 2 "
+ "%3 = OpTypeVector %1 3 ",
+ {},
+ "%1 = OpTypeBool\n"
+ "%2 = OpTypeVector %1 2\n"
+ "%3 = OpTypeVector %1 3",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpTypeVector %1 2"},
+ {3, "%3 = OpTypeVector %1 3"},
+ },
+ { // uses
+ {1,
+ {
+ "%2 = OpTypeVector %1 2",
+ "%3 = OpTypeVector %1 3",
+ }
+ },
+ },
+ },
+ },
+ { // kill id used, kill id not used, kill id not defined
+ "%1 = OpTypeBool "
+ "%2 = OpTypeVector %1 2 "
+ "%3 = OpTypeVector %1 3 "
+ "%4 = OpTypeVector %1 4 "
+ "%5 = OpTypeMatrix %3 3 "
+ "%6 = OpTypeMatrix %2 3",
+ {1, 3, 5, 10}, // ids to kill
+ "OpNop\n"
+ "%2 = OpTypeVector %1 2\n"
+ "OpNop\n"
+ "%4 = OpTypeVector %1 4\n"
+ "OpNop\n"
+ "%6 = OpTypeMatrix %2 3",
+ {
+ { // defs
+ {2, "%2 = OpTypeVector %1 2"},
+ {4, "%4 = OpTypeVector %1 4"},
+ {6, "%6 = OpTypeMatrix %2 3"},
+ },
+ { // uses. %1 and %3 are both killed, so no uses
+ // recorded for them anymore.
+ {2, {"%6 = OpTypeMatrix %2 3"}},
+ }
+ },
+ },
+ { // OpPhi.
+ kOpPhiTestFunction,
+ {9, 11}, // kill one id used by OpPhi, kill one id generated by OpPhi
+ "%2 = OpFunction %1 None %3\n"
+ "%4 = OpLabel\n"
+ "OpBranch %5\n"
+
+ "%5 = OpLabel\n"
+ "%7 = OpPhi %6 %8 %4 %9 %5\n"
+ "OpNop\n"
+ "OpNop\n"
+ "%13 = OpFAdd %10 %11 %15\n"
+ "%17 = OpSLessThan %16 %7 %18\n"
+ "OpLoopMerge %19 %5 None\n"
+ "OpBranchConditional %17 %5 %19\n"
+
+ "%19 = OpLabel\n"
+ "OpReturn\n"
+ "OpFunctionEnd",
+ {
+ { // defs. %9 & %11 are killed.
+ {2, "%2 = OpFunction %1 None %3"},
+ {4, "%4 = OpLabel"},
+ {5, "%5 = OpLabel"},
+ {7, "%7 = OpPhi %6 %8 %4 %9 %5"},
+ {13, "%13 = OpFAdd %10 %11 %15"},
+ {17, "%17 = OpSLessThan %16 %7 %18"},
+ {19, "%19 = OpLabel"},
+ },
+ { // uses
+ {1, {"%2 = OpFunction %1 None %3"}},
+ {3, {"%2 = OpFunction %1 None %3"}},
+ {4,
+ {
+ "%7 = OpPhi %6 %8 %4 %9 %5",
+ // "%11 = OpPhi %10 %12 %4 %13 %5",
+ }
+ },
+ {5,
+ {
+ "OpBranch %5",
+ "%7 = OpPhi %6 %8 %4 %9 %5",
+ // "%11 = OpPhi %10 %12 %4 %13 %5",
+ "OpLoopMerge %19 %5 None",
+ "OpBranchConditional %17 %5 %19",
+ }
+ },
+ {6,
+ {
+ "%7 = OpPhi %6 %8 %4 %9 %5",
+ // "%9 = OpIAdd %6 %7 %14",
+ }
+ },
+ {7,
+ {
+ // "%9 = OpIAdd %6 %7 %14",
+ "%17 = OpSLessThan %16 %7 %18",
+ }
+ },
+ {8, {"%7 = OpPhi %6 %8 %4 %9 %5"}},
+ // {9, {"%7 = OpPhi %6 %8 %4 %9 %5"}},
+ {10,
+ {
+ // "%11 = OpPhi %10 %12 %4 %13 %5",
+ "%13 = OpFAdd %10 %11 %15",
+ }
+ },
+ // {11, {"%13 = OpFAdd %10 %11 %15"}},
+ // {12, {"%11 = OpPhi %10 %12 %4 %13 %5"}},
+ // {13, {"%11 = OpPhi %10 %12 %4 %13 %5"}},
+ // {14, {"%9 = OpIAdd %6 %7 %14"}},
+ {15, {"%13 = OpFAdd %10 %11 %15"}},
+ {16, {"%17 = OpSLessThan %16 %7 %18"}},
+ {17, {"OpBranchConditional %17 %5 %19"}},
+ {18, {"%17 = OpSLessThan %16 %7 %18"}},
+ {19,
+ {
+ "OpLoopMerge %19 %5 None",
+ "OpBranchConditional %17 %5 %19",
+ }
+ },
+ },
+ },
+ },
+ { // OpPhi defining and referencing the same id.
+ "%1 = OpTypeBool "
+ "%2 = OpConstantTrue %1 "
+
+ "%4 = OpFunction %3 None %5 "
+ "%6 = OpLabel "
+ " OpBranch %7 "
+ "%7 = OpLabel "
+ "%8 = OpPhi %1 %8 %7 %2 %6 " // both defines and uses %8
+ " OpBranch %7 "
+ " OpFunctionEnd",
+ {8},
+ "%1 = OpTypeBool\n"
+ "%2 = OpConstantTrue %1\n"
+
+ "%4 = OpFunction %3 None %5\n"
+ "%6 = OpLabel\n"
+ "OpBranch %7\n"
+ "%7 = OpLabel\n"
+ "OpNop\n"
+ "OpBranch %7\n"
+ "OpFunctionEnd",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpConstantTrue %1"},
+ {4, "%4 = OpFunction %3 None %5"},
+ {6, "%6 = OpLabel"},
+ {7, "%7 = OpLabel"},
+ // {8, "%8 = OpPhi %1 %8 %7 %2 %6"},
+ },
+ { // uses
+ {1,
+ {
+ "%2 = OpConstantTrue %1",
+ // "%8 = OpPhi %1 %8 %7 %2 %6",
+ }
+ },
+ // {2, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
+ {3, {"%4 = OpFunction %3 None %5"}},
+ {5, {"%4 = OpFunction %3 None %5"}},
+ // {6, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
+ {7,
+ {
+ "OpBranch %7",
+ // "%8 = OpPhi %1 %8 %7 %2 %6",
+ "OpBranch %7",
+ }
+ },
+ // {8, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
+ },
+ },
+ },
+ })
+);
+// clang-format on
+//
+TEST(DefUseTest, OpSwitch) {
+ // Because disassembler has basic type check for OpSwitch's selector, we
+ // cannot use the DisassembleInst() in the above. Thus, this special spotcheck
+ // test case.
+
+ const char original_text[] =
+ // int64 f(int64 v) {
+ // switch (v) {
+ // case 1: break;
+ // case -4294967296: break;
+ // case 9223372036854775807: break;
+ // default: break;
+ // }
+ // return v;
+ // }
+ " %1 = OpTypeInt 64 1 "
+ " %2 = OpFunction %1 None %3 " // %3 is int64(int64)*
+ " %4 = OpFunctionParameter %1 "
+ " %5 = OpLabel "
+ " %6 = OpLoad %1 %4 " // selector value
+ " OpSelectionMerge %7 None "
+ " OpSwitch %6 %8 "
+ " 1 %9 " // 1
+ " -4294967296 %10 " // -2^32
+ " 9223372036854775807 %11 " // 2^63-1
+ " %8 = OpLabel " // default
+ " OpBranch %7 "
+ " %9 = OpLabel "
+ " OpBranch %7 "
+ "%10 = OpLabel "
+ " OpBranch %7 "
+ "%11 = OpLabel "
+ " OpBranch %7 "
+ " %7 = OpLabel "
+ " OpReturnValue %6 "
+ " OpFunctionEnd";
+
+ std::unique_ptr<ir::Module> module =
+ SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(original_text);
+ ASSERT_NE(nullptr, module);
+
+ // Analyze def and use.
+ opt::analysis::DefUseManager manager;
+ manager.AnalyzeDefUse(module.get());
+
+ // Do a bunch replacements.
+ manager.ReplaceAllUsesWith(9, 900); // to unused id
+ manager.ReplaceAllUsesWith(10, 1000); // to unused id
+ manager.ReplaceAllUsesWith(11, 7); // to existing id
+
+ // clang-format off
+ const char modified_text[] =
+ "%1 = OpTypeInt 64 1\n"
+ "%2 = OpFunction %1 None %3\n" // %3 is int64(int64)*
+ "%4 = OpFunctionParameter %1\n"
+ "%5 = OpLabel\n"
+ "%6 = OpLoad %1 %4\n" // selector value
+ "OpSelectionMerge %7 None\n"
+ "OpSwitch %6 %8 1 %900 -4294967296 %1000 9223372036854775807 %7\n" // changed!
+ "%8 = OpLabel\n" // default
+ "OpBranch %7\n"
+ "%9 = OpLabel\n"
+ "OpBranch %7\n"
+ "%10 = OpLabel\n"
+ "OpBranch %7\n"
+ "%11 = OpLabel\n"
+ "OpBranch %7\n"
+ "%7 = OpLabel\n"
+ "OpReturnValue %6\n"
+ "OpFunctionEnd";
+ // clang-format on
+
+ EXPECT_EQ(modified_text, DisassembleModule(module.get()));
+
+ InstDefUse def_uses = {};
+ def_uses.defs = {
+ {1, "%1 = OpTypeInt 64 1"},
+ {2, "%2 = OpFunction %1 None %3"},
+ {4, "%4 = OpFunctionParameter %1"},
+ {5, "%5 = OpLabel"},
+ {6, "%6 = OpLoad %1 %4"},
+ {7, "%7 = OpLabel"},
+ {8, "%8 = OpLabel"},
+ {9, "%9 = OpLabel"},
+ {10, "%10 = OpLabel"},
+ {11, "%11 = OpLabel"},
+ };
+ CheckDef(def_uses, manager.id_to_defs());
+
+ {
+ auto* use_list = manager.GetUses(6);
+ ASSERT_NE(nullptr, use_list);
+ EXPECT_EQ(2u, use_list->size());
+ EXPECT_EQ(SpvOpSwitch, use_list->front().inst->opcode());
+ EXPECT_EQ(SpvOpReturnValue, use_list->back().inst->opcode());
+ }
+ {
+ auto* use_list = manager.GetUses(7);
+ ASSERT_NE(nullptr, use_list);
+ EXPECT_EQ(6u, use_list->size());
+ std::vector<SpvOp> opcodes;
+ for (const auto& use : *use_list) {
+ opcodes.push_back(use.inst->opcode());
+ }
+ // OpSwitch is now a user of %7.
+ EXPECT_THAT(opcodes,
+ ElementsAre(SpvOpSelectionMerge, SpvOpBranch, SpvOpBranch,
+ SpvOpBranch, SpvOpBranch, SpvOpSwitch));
+ }
+ // Check all ids only used by OpSwitch after replacement.
+ for (const auto id : {8, 900, 1000}) {
+ auto* use_list = manager.GetUses(id);
+ ASSERT_NE(nullptr, use_list);
+ EXPECT_EQ(1u, use_list->size());
+ EXPECT_EQ(SpvOpSwitch, use_list->front().inst->opcode());
+ }
+}
+
+} // anonymous namespace
diff --git a/test/opt/test_freeze_spec_const.cpp b/test/opt/test_freeze_spec_const.cpp
new file mode 100644
index 0000000..2be826b
--- /dev/null
+++ b/test/opt/test_freeze_spec_const.cpp
@@ -0,0 +1,136 @@
+// Copyright (c) 2016 Google Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and/or associated documentation files (the
+// "Materials"), to deal in the Materials without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Materials, and to
+// permit persons to whom the Materials are furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Materials.
+//
+// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
+// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
+// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
+// https://www.khronos.org/registry/
+//
+// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+
+#include "pass_fixture.h"
+#include "pass_utils.h"
+
+#include <algorithm>
+#include <tuple>
+#include <vector>
+
+namespace {
+
+using namespace spvtools;
+
+struct FreezeSpecConstantValueTypeTestCase {
+ const char* type_decl;
+ const char* spec_const;
+ const char* expected_frozen_const;
+};
+
+using FreezeSpecConstantValueTypeTest =
+ PassTest<::testing::TestWithParam<FreezeSpecConstantValueTypeTestCase>>;
+
+TEST_P(FreezeSpecConstantValueTypeTest, PrimaryType) {
+ auto& test_case = GetParam();
+ std::vector<const char*> text = {"OpCapability Shader",
+ "OpMemoryModel Logical GLSL450",
+ test_case.type_decl, test_case.spec_const};
+ std::vector<const char*> expected = {
+ "OpCapability Shader", "OpMemoryModel Logical GLSL450",
+ test_case.type_decl, test_case.expected_frozen_const};
+ SinglePassRunAndCheck<opt::FreezeSpecConstantValuePass>(
+ JoinAllInsts(text), JoinAllInsts(expected));
+}
+
+// Test each primary type.
+INSTANTIATE_TEST_CASE_P(
+ PrimaryTypeSpecConst, FreezeSpecConstantValueTypeTest,
+ ::testing::ValuesIn(std::vector<FreezeSpecConstantValueTypeTestCase>({
+ // Type declaration, original spec constant definition, expected frozen
+ // spec constants.
+ {"%int = OpTypeInt 32 1", "%2 = OpSpecConstant %int 1",
+ "%2 = OpConstant %int 1"},
+ {"%uint = OpTypeInt 32 0", "%2 = OpSpecConstant %uint 1",
+ "%2 = OpConstant %uint 1"},
+ {"%float = OpTypeFloat 32", "%2 = OpSpecConstant %float 3.14",
+ "%2 = OpConstant %float 3.14"},
+ {"%double = OpTypeFloat 64", "%2 = OpSpecConstant %double 3.1415926",
+ "%2 = OpConstant %double 3.1415926"},
+ {"%bool = OpTypeBool", "%2 = OpSpecConstantTrue %bool",
+ "%2 = OpConstantTrue %bool"},
+ {"%bool = OpTypeBool", "%2 = OpSpecConstantFalse %bool",
+ "%2 = OpConstantFalse %bool"},
+ })));
+
+using FreezeSpecConstantValueRemoveDecorationTest = PassTest<::testing::Test>;
+
+TEST_F(FreezeSpecConstantValueRemoveDecorationTest,
+ RemoveDecorationInstWithSpecId) {
+ std::vector<const char*> text = {
+ // clang-format off
+ "OpCapability Shader",
+ "OpCapability Float64",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %main \"main\"",
+ "OpSource GLSL 450",
+ "OpSourceExtension \"GL_GOOGLE_cpp_style_line_directive\"",
+ "OpSourceExtension \"GL_GOOGLE_include_directive\"",
+ "OpName %main \"main\"",
+ "OpDecorate %3 SpecId 200",
+ "OpDecorate %4 SpecId 201",
+ "OpDecorate %5 SpecId 202",
+ "OpDecorate %6 SpecId 203",
+ "%void = OpTypeVoid",
+ "%8 = OpTypeFunction %void",
+ "%int = OpTypeInt 32 1",
+ "%3 = OpSpecConstant %int 3",
+ "%float = OpTypeFloat 32",
+ "%4 = OpSpecConstant %float 3.14",
+ "%double = OpTypeFloat 64",
+ "%5 = OpSpecConstant %double 3.14159265358979",
+ "%bool = OpTypeBool",
+ "%6 = OpSpecConstantTrue %bool",
+ "%13 = OpSpecConstantFalse %bool",
+ "%main = OpFunction %void None %8",
+ "%14 = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+ std::string expected_disassembly = SelectiveJoin(text, [](const char* line) {
+ return std::string(line).find("SpecId") != std::string::npos;
+ });
+ std::vector<std::pair<const char*, const char*>> opcode_replacement_pairs = {
+ {" OpSpecConstant ", " OpConstant "},
+ {" OpSpecConstantTrue ", " OpConstantTrue "},
+ {" OpSpecConstantFalse ", " OpConstantFalse "},
+ };
+ for (auto& p : opcode_replacement_pairs) {
+ EXPECT_TRUE(FindAndReplace(&expected_disassembly, p.first, p.second))
+ << "text:\n"
+ << expected_disassembly << "\n"
+ << "find_str:\n"
+ << p.first << "\n"
+ << "replace_str:\n"
+ << p.second << "\n";
+ }
+ SinglePassRunAndCheck<opt::FreezeSpecConstantValuePass>(
+ JoinAllInsts(text), expected_disassembly,
+ /* skip_nop = */ true);
+}
+} // anonymous namespace
diff --git a/test/opt/test_ir_loader.cpp b/test/opt/test_ir_loader.cpp
index bb788f5..c5ccd5e 100644
--- a/test/opt/test_ir_loader.cpp
+++ b/test/opt/test_ir_loader.cpp
@@ -37,44 +37,46 @@
// int add(int a, int b) { return a + b; }
// void main() { add(1, 2); }
const std::string text =
- "OpCapability Shader\n"
- "%1 = OpExtInstImport \"GLSL.std.450\"\n"
- "OpMemoryModel Logical GLSL450\n"
- "OpEntryPoint Vertex %2 \"main\"\n"
- "OpSource ESSL 310\n"
- "OpSourceExtension \"GL_GOOGLE_cpp_style_line_directive\"\n"
- "OpSourceExtension \"GL_GOOGLE_include_directive\"\n"
- "OpName %2 \"main\"\n"
- "OpName %3 \"add(i1;i1;\"\n"
- "OpName %4 \"a\"\n"
- "OpName %5 \"b\"\n"
- "OpName %6 \"param\"\n"
- "OpName %7 \"param\"\n"
- "%8 = OpTypeVoid\n"
- "%9 = OpTypeFunction %8\n"
- "%10 = OpTypeInt 32 1\n"
- "%11 = OpTypePointer Function %10\n"
- "%12 = OpTypeFunction %10 %11 %11\n"
- "%13 = OpConstant %10 1\n"
- "%14 = OpConstant %10 2\n"
- "%2 = OpFunction %8 None %9\n"
- "%15 = OpLabel\n"
- "%6 = OpVariable %11 Function\n"
- "%7 = OpVariable %11 Function\n"
- "OpStore %6 %13\n"
- "OpStore %7 %14\n"
- "%16 = OpFunctionCall %10 %3 %6 %7\n"
- "OpReturn\n"
- "OpFunctionEnd\n"
- "%3 = OpFunction %10 None %12\n"
- "%4 = OpFunctionParameter %11\n"
- "%5 = OpFunctionParameter %11\n"
- "%17 = OpLabel\n"
- "%18 = OpLoad %10 %4\n"
- "%19 = OpLoad %10 %5\n"
- "%20 = OpIAdd %10 %18 %19\n"
- "OpReturnValue %20\n"
- "OpFunctionEnd\n";
+ // clang-format off
+ "OpCapability Shader\n"
+ "%1 = OpExtInstImport \"GLSL.std.450\"\n"
+ "OpMemoryModel Logical GLSL450\n"
+ "OpEntryPoint Vertex %main \"main\"\n"
+ "OpSource ESSL 310\n"
+ "OpSourceExtension \"GL_GOOGLE_cpp_style_line_directive\"\n"
+ "OpSourceExtension \"GL_GOOGLE_include_directive\"\n"
+ "OpName %main \"main\"\n"
+ "OpName %add_i1_i1_ \"add(i1;i1;\"\n"
+ "OpName %a \"a\"\n"
+ "OpName %b \"b\"\n"
+ "OpName %param \"param\"\n"
+ "OpName %param_0 \"param\"\n"
+ "%void = OpTypeVoid\n"
+ "%9 = OpTypeFunction %void\n"
+ "%int = OpTypeInt 32 1\n"
+ "%_ptr_Function_int = OpTypePointer Function %int\n"
+ "%12 = OpTypeFunction %int %_ptr_Function_int %_ptr_Function_int\n"
+ "%13 = OpConstant %int 1\n"
+ "%14 = OpConstant %int 2\n"
+ "%main = OpFunction %void None %9\n"
+ "%15 = OpLabel\n"
+ "%param = OpVariable %_ptr_Function_int Function\n"
+ "%param_0 = OpVariable %_ptr_Function_int Function\n"
+ "OpStore %param %13\n"
+ "OpStore %param_0 %14\n"
+ "%16 = OpFunctionCall %int %add_i1_i1_ %param %param_0\n"
+ "OpReturn\n"
+ "OpFunctionEnd\n"
+ "%add_i1_i1_ = OpFunction %int None %12\n"
+ "%a = OpFunctionParameter %_ptr_Function_int\n"
+ "%b = OpFunctionParameter %_ptr_Function_int\n"
+ "%17 = OpLabel\n"
+ "%18 = OpLoad %int %a\n"
+ "%19 = OpLoad %int %b\n"
+ "%20 = OpIAdd %int %18 %19\n"
+ "OpReturnValue %20\n"
+ "OpFunctionEnd\n";
+ // clang-format on
SpvTools t(SPV_ENV_UNIVERSAL_1_1);
std::unique_ptr<ir::Module> module = t.BuildModule(text);
@@ -92,27 +94,29 @@
// #version 310 es
// void main() {}
const std::string text =
- "OpCapability Shader\n"
- "%1 = OpExtInstImport \"GLSL.std.450\"\n"
- "OpMemoryModel Logical GLSL450\n"
- "OpEntryPoint Vertex %2 \"main\"\n"
- "%3 = OpString \"minimal.vert\"\n"
- "OpSource ESSL 310\n"
- "OpName %2 \"main\"\n"
- "OpLine %3 10 10\n"
- "%4 = OpTypeVoid\n"
- "OpLine %3 100 100\n"
- "%5 = OpTypeFunction %4\n"
- "%2 = OpFunction %4 None %5\n"
- "OpLine %3 1 1\n"
- "OpNoLine\n"
- "OpLine %3 2 2\n"
- "OpLine %3 3 3\n"
- "%6 = OpLabel\n"
- "OpLine %3 4 4\n"
- "OpNoLine\n"
- "OpReturn\n"
- "OpFunctionEnd\n";
+ // clang-format off
+ "OpCapability Shader\n"
+ "%1 = OpExtInstImport \"GLSL.std.450\"\n"
+ "OpMemoryModel Logical GLSL450\n"
+ "OpEntryPoint Vertex %main \"main\"\n"
+ "%3 = OpString \"minimal.vert\"\n"
+ "OpSource ESSL 310\n"
+ "OpName %main \"main\"\n"
+ "OpLine %3 10 10\n"
+ "%void = OpTypeVoid\n"
+ "OpLine %3 100 100\n"
+ "%5 = OpTypeFunction %void\n"
+ "%main = OpFunction %void None %5\n"
+ "OpLine %3 1 1\n"
+ "OpNoLine\n"
+ "OpLine %3 2 2\n"
+ "OpLine %3 3 3\n"
+ "%6 = OpLabel\n"
+ "OpLine %3 4 4\n"
+ "OpNoLine\n"
+ "OpReturn\n"
+ "OpFunctionEnd\n";
+ // clang-format on
SpvTools t(SPV_ENV_UNIVERSAL_1_1);
std::unique_ptr<ir::Module> module = t.BuildModule(text);
@@ -142,56 +146,58 @@
// float lv1 = gv1 - gv2;
// }
const std::string text =
- "OpCapability Shader\n"
- "%1 = OpExtInstImport \"GLSL.std.450\"\n"
- "OpMemoryModel Logical GLSL450\n"
- "OpEntryPoint Vertex %2 \"main\"\n"
- "OpSource ESSL 310\n"
- "OpName %2 \"main\"\n"
- "OpName %3 \"f(\"\n"
- "OpName %4 \"gv1\"\n"
- "OpName %5 \"gv2\"\n"
- "OpName %6 \"lv1\"\n"
- "OpName %7 \"lv2\"\n"
- "OpName %8 \"lv1\"\n"
- "%9 = OpTypeVoid\n"
- "%10 = OpTypeFunction %9\n"
- "%11 = OpTypeFloat 32\n"
- "%12 = OpTypeFunction %11\n"
- "%13 = OpTypePointer Private %11\n"
- "%4 = OpVariable %13 Private\n"
- "%14 = OpConstant %11 10\n"
- "%5 = OpVariable %13 Private\n"
- "%15 = OpConstant %11 100\n"
- "%16 = OpTypePointer Function %11\n"
- "%2 = OpFunction %9 None %10\n"
- "%17 = OpLabel\n"
- "%8 = OpVariable %16 Function\n"
- "OpStore %4 %14\n"
- "OpStore %5 %15\n"
- "%18 = OpLoad %11 %4\n"
- "%19 = OpLoad %11 %5\n"
- "%20 = OpFSub %11 %18 %19\n"
- "OpStore %8 %20\n"
- "OpReturn\n"
- "OpFunctionEnd\n"
- "%3 = OpFunction %11 None %12\n"
- "%21 = OpLabel\n"
- "%6 = OpVariable %16 Function\n"
- "%7 = OpVariable %16 Function\n"
- "%22 = OpLoad %11 %4\n"
- "%23 = OpLoad %11 %5\n"
- "%24 = OpFAdd %11 %22 %23\n"
- "OpStore %6 %24\n"
- "%25 = OpLoad %11 %4\n"
- "%26 = OpLoad %11 %5\n"
- "%27 = OpFMul %11 %25 %26\n"
- "OpStore %7 %27\n"
- "%28 = OpLoad %11 %6\n"
- "%29 = OpLoad %11 %7\n"
- "%30 = OpFDiv %11 %28 %29\n"
- "OpReturnValue %30\n"
- "OpFunctionEnd\n";
+ // clang-format off
+ "OpCapability Shader\n"
+ "%1 = OpExtInstImport \"GLSL.std.450\"\n"
+ "OpMemoryModel Logical GLSL450\n"
+ "OpEntryPoint Vertex %main \"main\"\n"
+ "OpSource ESSL 310\n"
+ "OpName %main \"main\"\n"
+ "OpName %f_ \"f(\"\n"
+ "OpName %gv1 \"gv1\"\n"
+ "OpName %gv2 \"gv2\"\n"
+ "OpName %lv1 \"lv1\"\n"
+ "OpName %lv2 \"lv2\"\n"
+ "OpName %lv1_0 \"lv1\"\n"
+ "%void = OpTypeVoid\n"
+ "%10 = OpTypeFunction %void\n"
+ "%float = OpTypeFloat 32\n"
+ "%12 = OpTypeFunction %float\n"
+ "%_ptr_Private_float = OpTypePointer Private %float\n"
+ "%gv1 = OpVariable %_ptr_Private_float Private\n"
+ "%14 = OpConstant %float 10\n"
+ "%gv2 = OpVariable %_ptr_Private_float Private\n"
+ "%15 = OpConstant %float 100\n"
+ "%_ptr_Function_float = OpTypePointer Function %float\n"
+ "%main = OpFunction %void None %10\n"
+ "%17 = OpLabel\n"
+ "%lv1_0 = OpVariable %_ptr_Function_float Function\n"
+ "OpStore %gv1 %14\n"
+ "OpStore %gv2 %15\n"
+ "%18 = OpLoad %float %gv1\n"
+ "%19 = OpLoad %float %gv2\n"
+ "%20 = OpFSub %float %18 %19\n"
+ "OpStore %lv1_0 %20\n"
+ "OpReturn\n"
+ "OpFunctionEnd\n"
+ "%f_ = OpFunction %float None %12\n"
+ "%21 = OpLabel\n"
+ "%lv1 = OpVariable %_ptr_Function_float Function\n"
+ "%lv2 = OpVariable %_ptr_Function_float Function\n"
+ "%22 = OpLoad %float %gv1\n"
+ "%23 = OpLoad %float %gv2\n"
+ "%24 = OpFAdd %float %22 %23\n"
+ "OpStore %lv1 %24\n"
+ "%25 = OpLoad %float %gv1\n"
+ "%26 = OpLoad %float %gv2\n"
+ "%27 = OpFMul %float %25 %26\n"
+ "OpStore %lv2 %27\n"
+ "%28 = OpLoad %float %lv1\n"
+ "%29 = OpLoad %float %lv2\n"
+ "%30 = OpFDiv %float %28 %29\n"
+ "OpReturnValue %30\n"
+ "OpFunctionEnd\n";
+ // clang-format on
SpvTools t(SPV_ENV_UNIVERSAL_1_1);
std::unique_ptr<ir::Module> module = t.BuildModule(text);
diff --git a/test/opt/test_pass_manager.cpp b/test/opt/test_pass_manager.cpp
index cf2f2f6..1ecde39 100644
--- a/test/opt/test_pass_manager.cpp
+++ b/test/opt/test_pass_manager.cpp
@@ -36,18 +36,18 @@
manager.AddPass<opt::StripDebugInfoPass>();
EXPECT_EQ(1u, manager.NumPasses());
- EXPECT_STREQ("StripDebugInfo", manager.GetPass(0)->name());
+ EXPECT_STREQ("strip-debug", manager.GetPass(0)->name());
manager.AddPass(std::unique_ptr<opt::NullPass>(new opt::NullPass));
EXPECT_EQ(2u, manager.NumPasses());
- EXPECT_STREQ("StripDebugInfo", manager.GetPass(0)->name());
- EXPECT_STREQ("Null", manager.GetPass(1)->name());
+ EXPECT_STREQ("strip-debug", manager.GetPass(0)->name());
+ EXPECT_STREQ("null", manager.GetPass(1)->name());
manager.AddPass<opt::StripDebugInfoPass>();
EXPECT_EQ(3u, manager.NumPasses());
- EXPECT_STREQ("StripDebugInfo", manager.GetPass(0)->name());
- EXPECT_STREQ("Null", manager.GetPass(1)->name());
- EXPECT_STREQ("StripDebugInfo", manager.GetPass(2)->name());
+ EXPECT_STREQ("strip-debug", manager.GetPass(0)->name());
+ EXPECT_STREQ("null", manager.GetPass(1)->name());
+ EXPECT_STREQ("strip-debug", manager.GetPass(2)->name());
}
// A pass that appends an OpNop instruction to the debug section.
diff --git a/test/opt/test_strip_debug_info.cpp b/test/opt/test_strip_debug_info.cpp
index 964b33c..d9b35e1 100644
--- a/test/opt/test_strip_debug_info.cpp
+++ b/test/opt/test_strip_debug_info.cpp
@@ -35,28 +35,30 @@
TEST_F(StripLineDebugInfoTest, LineNoLine) {
std::vector<const char*> text = {
- "OpCapability Shader",
- "%1 = OpExtInstImport \"GLSL.std.450\"",
- "OpMemoryModel Logical GLSL450",
- "OpEntryPoint Vertex %2 \"main\"",
- "%3 = OpString \"minimal.vert\"",
- "OpNoLine",
- "OpLine %3 10 10",
- "%4 = OpTypeVoid",
- "OpLine %3 100 100",
- "%5 = OpTypeFunction %4",
- "%2 = OpFunction %4 None %5",
- "OpLine %3 1 1",
- "OpNoLine",
- "OpLine %3 2 2",
- "OpLine %3 3 3",
- "%6 = OpLabel",
- "OpLine %3 4 4",
- "OpNoLine",
- "OpReturn",
- "OpLine %3 4 4",
- "OpNoLine",
- "OpFunctionEnd",
+ // clang-format off
+ "OpCapability Shader",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %2 \"main\"",
+ "%3 = OpString \"minimal.vert\"",
+ "OpNoLine",
+ "OpLine %3 10 10",
+ "%void = OpTypeVoid",
+ "OpLine %3 100 100",
+ "%5 = OpTypeFunction %void",
+ "%2 = OpFunction %void None %5",
+ "OpLine %3 1 1",
+ "OpNoLine",
+ "OpLine %3 2 2",
+ "OpLine %3 3 3",
+ "%6 = OpLabel",
+ "OpLine %3 4 4",
+ "OpNoLine",
+ "OpReturn",
+ "OpLine %3 4 4",
+ "OpNoLine",
+ "OpFunctionEnd",
+ // clang-format on
};
SinglePassRunAndCheck<opt::StripDebugInfoPass>(JoinAllInsts(text),
JoinNonDebugInsts(text));
diff --git a/test/opt/test_utils.cpp b/test/opt/test_utils.cpp
index 4a96559..e9ebb31 100644
--- a/test/opt/test_utils.cpp
+++ b/test/opt/test_utils.cpp
@@ -55,4 +55,64 @@
"the only remaining string"}));
}
+namespace {
+struct SubstringReplacementTestCase {
+ const char* orig_str;
+ const char* find_substr;
+ const char* replace_substr;
+ const char* expected_str;
+ bool replace_should_succeed;
+};
+}
+using FindAndReplaceTest =
+ ::testing::TestWithParam<SubstringReplacementTestCase>;
+
+TEST_P(FindAndReplaceTest, SubstringReplacement) {
+ auto process = std::string(GetParam().orig_str);
+ EXPECT_EQ(GetParam().replace_should_succeed,
+ FindAndReplace(&process, GetParam().find_substr,
+ GetParam().replace_substr))
+ << "Original string: " << GetParam().orig_str
+ << " replace: " << GetParam().find_substr
+ << " to: " << GetParam().replace_substr
+ << " should returns: " << GetParam().replace_should_succeed;
+ EXPECT_STREQ(GetParam().expected_str, process.c_str())
+ << "Original string: " << GetParam().orig_str
+ << " replace: " << GetParam().find_substr
+ << " to: " << GetParam().replace_substr
+ << " expected string: " << GetParam().expected_str;
+}
+
+INSTANTIATE_TEST_CASE_P(
+ SubstringReplacement, FindAndReplaceTest,
+ ::testing::ValuesIn(std::vector<SubstringReplacementTestCase>({
+ // orig string, find substring, replace substring, expected string,
+ // replacement happened
+ {"", "", "", "", false},
+ {"", "b", "", "", false},
+ {"", "", "c", "", false},
+ {"", "a", "b", "", false},
+
+ {"a", "", "c", "a", false},
+ {"a", "b", "c", "a", false},
+ {"a", "b", "", "a", false},
+ {"a", "a", "", "", true},
+ {"a", "a", "b", "b", true},
+
+ {"ab", "a", "b", "bb", true},
+ {"ab", "a", "", "b", true},
+ {"ab", "b", "", "a", true},
+ {"ab", "ab", "", "", true},
+ {"ab", "ab", "cd", "cd", true},
+ {"bc", "abc", "efg", "bc", false},
+
+ {"abc", "ab", "bc", "bcc", true},
+ {"abc", "ab", "", "c", true},
+ {"abc", "bc", "", "a", true},
+ {"abc", "bc", "d", "ad", true},
+ {"abc", "a", "123", "123bc", true},
+ {"abc", "ab", "a", "ac", true},
+ {"abc", "a", "aab", "aabbc", true},
+ {"abc", "abcd", "efg", "abc", false},
+ })));
} // anonymous namespace
diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp
index e3246a1..3c3f79c 100644
--- a/tools/opt/opt.cpp
+++ b/tools/opt/opt.cpp
@@ -51,6 +51,9 @@
Options:
--strip-debug
Remove all debug instructions.
+ --freeze-spec-const
+ Freeze the values of specialization constants to their default
+ values.
-h, --help Print this help.
--version Display optimizer version information.
)",
@@ -83,6 +86,8 @@
}
} else if (0 == strcmp(cur_arg, "--strip-debug")) {
pass_manager.AddPass<opt::StripDebugInfoPass>();
+ } else if (0 == strcmp(cur_arg, "--freeze-spec-const")) {
+ pass_manager.AddPass<opt::FreezeSpecConstantValuePass>();
} else if ('\0' == cur_arg[1]) {
// Setting a filename of "-" to indicate stdin.
if (!in_file) {
diff --git a/utils/update_build_version.py b/utils/update_build_version.py
index 98ab5b1..9a3d5ac 100755
--- a/utils/update_build_version.py
+++ b/utils/update_build_version.py
@@ -25,7 +25,8 @@
# - A longer string with the project name, the software version number, and
# git commit information for the directory. The commit information
# is the output of "git describe" if that succeeds, or "git rev-parse HEAD"
-# if that succeeds, or otherwise a message containing the phrase "unknown hash".
+# if that succeeds, or otherwise a message containing the phrase
+# "unknown hash".
# The string contents are escaped as necessary.
from __future__ import print_function
@@ -37,7 +38,7 @@
import sys
-def command_output(cmd, dir):
+def command_output(cmd, directory):
"""Runs a command in a directory and returns its standard output stream.
Captures the standard error stream.
@@ -45,24 +46,24 @@
Raises a RuntimeError if the command fails to launch or otherwise fails.
"""
p = subprocess.Popen(cmd,
- cwd=dir,
+ cwd=directory,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(stdout, _) = p.communicate()
if p.returncode != 0:
- raise RuntimeError('Failed to run %s in %s' % (cmd, dir))
+ raise RuntimeError('Failed to run %s in %s' % (cmd, directory))
return stdout
-def deduce_software_version(dir):
+def deduce_software_version(directory):
"""Returns a software version number parsed from the CHANGES file
- in the given dir.
+ in the given directory.
The CHANGES file describes most recent versions first.
"""
- pattern = re.compile('(v\d+\.\d+(-dev)?) \d\d\d\d-\d\d-\d\d$')
- changes_file = os.path.join(dir, 'CHANGES')
+ pattern = re.compile(r'(v\d+\.\d+(-dev)?) \d\d\d\d-\d\d-\d\d$')
+ changes_file = os.path.join(directory, 'CHANGES')
with open(changes_file) as f:
for line in f.readlines():
match = pattern.match(line)
@@ -71,11 +72,11 @@
raise Exception('No version number found in {}'.format(changes_file))
-def describe(dir):
+def describe(directory):
"""Returns a string describing the current Git HEAD version as descriptively
as possible.
- Runs 'git describe', or alternately 'git rev-parse HEAD', in dir. If
+ Runs 'git describe', or alternately 'git rev-parse HEAD', in directory. If
successful, returns the output; otherwise returns 'unknown hash, <date>'."""
try:
# decode() is needed here for Python3 compatibility. In Python2,
@@ -83,29 +84,37 @@
# Popen.communicate() returns a bytes instance, which needs to be
# decoded into text data first in Python3. And this decode() won't
# hurt Python2.
- return command_output(['git', 'describe'], dir).rstrip().decode()
+ return command_output(['git', 'describe'], directory).rstrip().decode()
except:
try:
return command_output(
- ['git', 'rev-parse', 'HEAD'], dir).rstrip().decode()
+ ['git', 'rev-parse', 'HEAD'], directory).rstrip().decode()
except:
return 'unknown hash, {}'.format(datetime.date.today().isoformat())
def main():
if len(sys.argv) != 3:
- print('usage: {0} <spirv-tools_dir> <output-file>'.format(sys.argv[0]))
+ print('usage: {} <spirv-tools-dir> <output-file>'.format(sys.argv[0]))
sys.exit(1)
output_file = sys.argv[2]
+ output_dir = os.path.dirname(output_file)
+ if not os.path.isdir(output_dir):
+ os.makedirs(output_dir)
software_version = deduce_software_version(sys.argv[1])
new_content = '"{}", "SPIRV-Tools {} {}"\n'.format(
software_version, software_version,
describe(sys.argv[1]).replace('"', '\\"'))
- if os.path.isfile(output_file) and new_content == open(output_file, 'r').read():
- sys.exit(0)
- open(output_file, 'w').write(new_content)
+
+ if os.path.isfile(output_file):
+ with open(output_file, 'r') as f:
+ if new_content == f.read():
+ return
+
+ with open(output_file, 'w') as f:
+ f.write(new_content)
if __name__ == '__main__':
main()