Basic SSA Validation
Most uses of an ID must occur after the definition
of the ID. Forward references are allowed for
things like OpName, OpDecorate, and various cases
of control-flow instructions such as OpBranch, OpPhi,
and OpFunctionCall.
TODO: Use CFG analysis for SSA checks. In particular,
an ID defined inside a function body is only usable inside
that function body. Also, use dominator info to catch
some failing cases.
Also:
* Validator test cases use (standard) assignment form.
* Update style to more closely follow the Google C++ style guide
* Remove color-diagnostics flag.
This is enabled by default on terminals with color. Prints
hidden ASCII for terminals that can't handle color(Emacs)
* Pass functors to SSAPass to check if the
operand can be forward referenced based on its index value
* Return SPV_ERROR_INVALID_ID for ID related errors
spvBinaryParse returned SPV_ERROR_INVALID_BINARY for all types of
errors. Since spvBinaryParse does some ID validation, this was
returning inappropriate error codes for some tests.
* Common fixture for validation tests.
It only runs certian validation passes.
* Add a SPV_VALIDATE_SSA_BIT for testing purposes
* Fixtures now return error codes
* Add OpName support in diag message and unit tests
* Binary parsing can fail with invalid ID or invalid binary error code
Tests include:
* OpDecorate
* OpName
* OpMemberName
* OpBranchConditional
* OpSelectionMerge
* OpMemberDecorate
* OpGroupDecorate
* OpDeviceEnqueue
* Enable several tests failing in ID validation.
diff --git a/.gitignore b/.gitignore
index 345f8a6..b2e6030 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
build*
.ycm_extra_conf.py*
compile_commands.json
+/external/googletest/
+/TAGS
+/.clang_complete
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6826b98..652418e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -79,7 +79,6 @@
target_compile_options(${TARGET} PRIVATE -fno-omit-frame-pointer)
endif()
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
- target_compile_options(${TARGET} PRIVATE -fcolor-diagnostics)
set(SPIRV_USE_SANITIZER "" CACHE STRING
"Use the clang sanitizer [address|memory|thread|...]")
if(NOT "${SPIRV_USE_SANITIZER}" STREQUAL "")
@@ -213,7 +212,8 @@
${CMAKE_CURRENT_SOURCE_DIR}/test/TextToBinary.TypeDeclaration.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/TextWordGet.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/UnitSPIRV.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/test/Validate.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/ValidateFixtures.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/Validate.SSA.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/ValidateID.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/main.cpp)
diff --git a/include/libspirv/libspirv.h b/include/libspirv/libspirv.h
index c23cde9..07bbe75 100644
--- a/include/libspirv/libspirv.h
+++ b/include/libspirv/libspirv.h
@@ -257,8 +257,10 @@
SPV_VALIDATE_LAYOUT_BIT = SPV_BIT(1),
SPV_VALIDATE_ID_BIT = SPV_BIT(2),
SPV_VALIDATE_RULES_BIT = SPV_BIT(3),
+ SPV_VALIDATE_SSA_BIT = SPV_BIT(4),
SPV_VALIDATE_ALL = SPV_VALIDATE_BASIC_BIT | SPV_VALIDATE_LAYOUT_BIT |
- SPV_VALIDATE_ID_BIT | SPV_VALIDATE_RULES_BIT,
+ SPV_VALIDATE_ID_BIT | SPV_VALIDATE_RULES_BIT |
+ SPV_VALIDATE_SSA_BIT,
SPV_FORCE_32_BIT_ENUM(spv_validation_options_t)
} spv_validate_options_t;
@@ -419,9 +421,10 @@
// callback once for each instruction in the stream. The user_data parameter
// is supplied as context to the callbacks. Returns SPV_SUCCESS on successful
// parse where the callbacks always return SPV_SUCCESS. For an invalid parse,
-// returns SPV_ERROR_INVALID_BINARY and emits a diagnostic. If a callback
-// returns anything other than SPV_SUCCESS, then that status code is returned,
-// no further callbacks are issued, and no additional diagnostics are emitted.
+// returns a status code other than SPV_SUCCESS and emits a diagnostic. If a
+// callback returns anything other than SPV_SUCCESS, then that status code
+// is returned, no further callbacks are issued, and no additional diagnostics
+// are emitted.
spv_result_t spvBinaryParse(const spv_const_context context, void* user_data,
const uint32_t* words, const size_t num_words,
spv_parsed_header_fn_t parse_header,
diff --git a/source/binary.cpp b/source/binary.cpp
index 918b908..5d36dfb 100755
--- a/source/binary.cpp
+++ b/source/binary.cpp
@@ -463,17 +463,17 @@
switch (type) {
case SPV_OPERAND_TYPE_TYPE_ID:
- if (!word) return diagnostic() << "Error: Type Id is 0";
+ if (!word) return diagnostic(SPV_ERROR_INVALID_ID) << "Error: Type Id is 0";
inst->type_id = word;
break;
case SPV_OPERAND_TYPE_RESULT_ID:
- if (!word) return diagnostic() << "Error: Result Id is 0";
+ if (!word) return diagnostic(SPV_ERROR_INVALID_ID) << "Error: Result Id is 0";
inst->result_id = word;
// Save the result ID to type ID mapping.
// In the grammar, type ID always appears before result ID.
if (_.id_to_type_id.find(inst->result_id) != _.id_to_type_id.end())
- return diagnostic() << "Id " << inst->result_id
+ return diagnostic(SPV_ERROR_INVALID_ID) << "Id " << inst->result_id
<< " is defined more than once";
// Record it.
// A regular value maps to its type. Some instructions (e.g. OpLabel)
@@ -486,7 +486,7 @@
case SPV_OPERAND_TYPE_ID:
case SPV_OPERAND_TYPE_OPTIONAL_ID:
- if (!word) return diagnostic() << "Id is 0";
+ if (!word) return diagnostic(SPV_ERROR_INVALID_ID) << "Id is 0";
parsed_operand.type = SPV_OPERAND_TYPE_ID;
if (inst->opcode == SpvOpExtInst && parsed_operand.offset == 3) {
@@ -494,7 +494,7 @@
// Set the extended instruction set type for the current instruction.
auto ext_inst_type_iter = _.import_id_to_ext_inst_type.find(word);
if (ext_inst_type_iter == _.import_id_to_ext_inst_type.end()) {
- return diagnostic()
+ return diagnostic(SPV_ERROR_INVALID_ID)
<< "OpExtInst set Id " << word
<< " does not reference an OpExtInstImport result Id";
}
diff --git a/source/validate.cpp b/source/validate.cpp
index 8b6a797..4554e01 100644
--- a/source/validate.cpp
+++ b/source/validate.cpp
@@ -26,12 +26,6 @@
#include "validate.h"
-#include <assert.h>
-#include <stdio.h>
-#include <string.h>
-
-#include <vector>
-
#include "binary.h"
#include "diagnostic.h"
#include "endian.h"
@@ -41,9 +35,32 @@
#include "operand.h"
#include "spirv_constant.h"
+#include <algorithm>
+#include <cassert>
+#include <cstdio>
+#include <functional>
+#include <iterator>
+#include <map>
+#include <sstream>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+using std::function;
+using std::map;
+using std::ostream_iterator;
+using std::placeholders::_1;
+using std::string;
+using std::stringstream;
+using std::transform;
+using std::unordered_set;
+using std::vector;
+
+
#define spvCheckReturn(expression) \
if (spv_result_t error = (expression)) return error;
+#if 0
spv_result_t spvValidateOperandsString(const uint32_t* words,
const uint16_t wordCount,
spv_position position,
@@ -80,7 +97,10 @@
spv_diagnostic* pDiagnostic) {
switch (type) {
case SPV_OPERAND_TYPE_ID:
- case SPV_OPERAND_TYPE_RESULT_ID: {
+ case SPV_OPERAND_TYPE_TYPE_ID:
+ case SPV_OPERAND_TYPE_RESULT_ID:
+ case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
+ case SPV_OPERAND_TYPE_SCOPE_ID: {
// NOTE: ID's are validated in SPV_VALIDATION_LEVEL_1, this is
// SPV_VALIDATION_LEVEL_0
} break;
@@ -166,14 +186,15 @@
// of the parse.
spv_operand_type_t type = spvBinaryOperandInfo(
word, index, opcodeEntry, operandTable, &operandEntry);
+
if (SPV_OPERAND_TYPE_LITERAL_STRING == type) {
spvCheckReturn(spvValidateOperandsString(
words + index, wordCount - index, position, pDiagnostic));
// NOTE: String literals are always at the end of Opcodes
break;
} else if (SPV_OPERAND_TYPE_LITERAL_INTEGER == type) {
- spvCheckReturn(spvValidateOperandsLiteral(
- words + index, wordCount - index, 2, position, pDiagnostic));
+ // spvCheckReturn(spvValidateOperandsNumber(
+ // words + index, wordCount - index, 2, position, pDiagnostic));
} else {
spvCheckReturn(spvValidateOperandValue(type, word, operandTable,
position, pDiagnostic));
@@ -183,6 +204,7 @@
return SPV_SUCCESS;
}
+#endif
spv_result_t spvValidateIDs(const spv_instruction_t* pInsts,
const uint64_t count, const uint32_t bound,
@@ -261,6 +283,260 @@
return SPV_SUCCESS;
}
+namespace {
+
+// TODO(umar): Validate header
+// TODO(umar): The Id bound should be validated also. But you can only do that
+// after you've seen all the instructions in the module.
+// TODO(umar): The binary parser validates the magic word, and the length of the
+// header, but nothing else.
+spv_result_t setHeader(void* user_data, spv_endianness_t endian, uint32_t magic,
+ uint32_t version, uint32_t generator, uint32_t id_bound,
+ uint32_t reserved) {
+ (void)user_data;
+ (void)endian;
+ (void)magic;
+ (void)version;
+ (void)generator;
+ (void)id_bound;
+ (void)reserved;
+ return SPV_SUCCESS;
+}
+
+// TODO(umar): Move this class to another file
+class ValidationState_t {
+ public:
+ ValidationState_t(spv_diagnostic* diag, uint32_t options)
+ : diagnostic_(diag),
+ instruction_counter_(0),
+ validation_flags_(options) {}
+
+ spv_result_t definedIds(uint32_t id) {
+ if (defined_ids_.find(id) == std::end(defined_ids_)) {
+ defined_ids_.insert(id);
+ } else {
+ return diag(SPV_ERROR_INVALID_ID)
+ << "ID cannot be assigned multiple times";
+ }
+ return SPV_SUCCESS;
+ }
+
+ spv_result_t forwardDeclareId(uint32_t id) {
+ unresolved_forward_ids_.insert(id);
+ return SPV_SUCCESS;
+ }
+
+ spv_result_t removeIfForwardDeclared(uint32_t id) {
+ unresolved_forward_ids_.erase(id);
+ return SPV_SUCCESS;
+ }
+
+ void assignNameToId(uint32_t id, string name) { operand_names[id] = name; }
+
+ string getIdName(uint32_t id) {
+ std::stringstream out;
+ out << id;
+ if (operand_names.find(id) != end(operand_names)) {
+ out << "[" << operand_names[id] << "]";
+ }
+ return out.str();
+ }
+
+ size_t unresolvedForwardIdCount() const {
+ return unresolved_forward_ids_.size();
+ }
+
+ vector<uint32_t> unresolvedForwardIds() const {
+ vector<uint32_t> out(begin(unresolved_forward_ids_),
+ end(unresolved_forward_ids_));
+ return out;
+ }
+
+ //
+ bool isDefinedId(uint32_t id) const {
+ return defined_ids_.find(id) != std::end(defined_ids_);
+ }
+
+ bool is_enabled(uint32_t flag) const {
+ return (flag & validation_flags_) == flag;
+ }
+
+ // Increments the instruction count. Used for diagnostic
+ int incrementInstructionCount() { return instruction_counter_++; }
+
+ libspirv::DiagnosticStream diag(spv_result_t error_code) const {
+ return libspirv::DiagnosticStream(
+ {0, 0, static_cast<size_t>(instruction_counter_)}, diagnostic_,
+ error_code);
+ }
+
+ private:
+ spv_diagnostic* diagnostic_;
+ // Tracks the number of instructions evaluated by the validator
+ int instruction_counter_;
+
+ // All IDs which have been defined
+ unordered_set<uint32_t> defined_ids_;
+
+ // IDs which have been forward declared but have not been defined
+ unordered_set<uint32_t> unresolved_forward_ids_;
+
+ // Validation options to determine the passes to execute
+ uint32_t validation_flags_;
+
+ map<uint32_t, string> operand_names;
+};
+
+// 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 funciton.
+spv_result_t SsaPass(ValidationState_t& _,
+ function<bool(unsigned)> can_have_forward_declared_ids,
+ const spv_parsed_instruction_t* inst) {
+ if (_.is_enabled(SPV_VALIDATE_SSA_BIT)) {
+ 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 = _.definedIds(*operand_ptr);
+ 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;
+}
+
+// This funciton 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;
+}
+
+// Improves diagnostic messages by collecting names of IDs
+// NOTE: This function returns void and is not involved in validation
+void DebugInstructionPass(ValidationState_t& _,
+ const spv_parsed_instruction_t* inst) {
+ switch (inst->opcode) {
+ case SpvOpName: {
+ const uint32_t target = *(inst->words + inst->operands[0].offset);
+ const char* str =
+ reinterpret_cast<const char*>(inst->words + inst->operands[1].offset);
+ _.assignNameToId(target, str);
+ } break;
+ case SpvOpMemberName: {
+ const uint32_t target = *(inst->words + inst->operands[0].offset);
+ const char* str =
+ reinterpret_cast<const char*>(inst->words + inst->operands[2].offset);
+ _.assignNameToId(target, str);
+ } break;
+ case SpvOpSourceContinued:
+ case SpvOpSource:
+ case SpvOpSourceExtension:
+ case SpvOpString:
+ case SpvOpLine:
+ case SpvOpNoLine:
+
+ default:
+ break;
+ }
+}
+
+spv_result_t ProcessInstructions(void* user_data,
+ const spv_parsed_instruction_t* inst) {
+ ValidationState_t& _ = *(reinterpret_cast<ValidationState_t*>(user_data));
+ _.incrementInstructionCount();
+
+ auto can_have_forward_declared_ids =
+ getCanBeForwardDeclaredFunction(inst->opcode);
+
+ DebugInstructionPass(_, inst);
+
+ // TODO(umar): Perform CFG pass
+ // TODO(umar): Perform logical layout validation pass
+ // TODO(umar): Perform data rules pass
+ // TODO(umar): Perform instruction validation pass
+ return SsaPass(_, can_have_forward_declared_ids, inst);
+}
+
+} // anonymous namespace
+
spv_result_t spvValidate(const spv_const_context context,
const spv_const_binary binary, const uint32_t options,
spv_diagnostic* pDiagnostic) {
@@ -279,6 +555,33 @@
return SPV_ERROR_INVALID_BINARY;
}
+ // NOTE: Parse the module and perform inline validation checks. These
+ // checks do not require the the knowledge of the whole module.
+ ValidationState_t vstate(pDiagnostic, options);
+ auto err = spvBinaryParse(context, &vstate, binary->code, binary->wordCount,
+ setHeader, ProcessInstructions, pDiagnostic);
+
+ if (err) {
+ return err;
+ }
+
+ // TODO(umar): Add validation checks which require the parsing of the entire
+ // module. Use the information from the processInstructions pass to make
+ // the checks.
+
+ if (vstate.unresolvedForwardIdCount() > 0) {
+ stringstream ss;
+ vector<uint32_t> ids = vstate.unresolvedForwardIds();
+
+ transform(begin(ids), end(ids), ostream_iterator<string>(ss, " "),
+ bind(&ValidationState_t::getIdName, vstate, _1));
+
+ auto id_str = ss.str();
+ return vstate.diag(SPV_ERROR_INVALID_ID)
+ << "The following forward referenced IDs have not be defined:\n"
+ << id_str.substr(0, id_str.size() - 1);
+ }
+
// NOTE: Copy each instruction for easier processing
std::vector<spv_instruction_t> instructions;
uint64_t index = SPV_INDEX_INSTRUCTION;
@@ -293,19 +596,6 @@
index += wordCount;
}
- if (spvIsInBitfield(SPV_VALIDATE_BASIC_BIT, options)) {
- position.index = SPV_INDEX_INSTRUCTION;
- // TODO: Imcomplete implementation
- spvCheckReturn(spvValidateBasic(
- instructions.data(), instructions.size(), context->opcode_table,
- context->operand_table, &position, pDiagnostic));
- }
-
- if (spvIsInBitfield(SPV_VALIDATE_LAYOUT_BIT, options)) {
- position.index = SPV_INDEX_INSTRUCTION;
- // TODO: spvBinaryValidateLayout
- }
-
if (spvIsInBitfield(SPV_VALIDATE_ID_BIT, options)) {
position.index = SPV_INDEX_INSTRUCTION;
spvCheckReturn(
@@ -314,10 +604,5 @@
context->ext_inst_table, &position, pDiagnostic));
}
- if (spvIsInBitfield(SPV_VALIDATE_RULES_BIT, options)) {
- position.index = SPV_INDEX_INSTRUCTION;
- // TODO: Specified validation rules...
- }
-
return SPV_SUCCESS;
}
diff --git a/source/validate_id.cpp b/source/validate_id.cpp
index 9d9a7eb..cdb8e59 100644
--- a/source/validate_id.cpp
+++ b/source/validate_id.cpp
@@ -1216,22 +1216,21 @@
<< inst->words[resultTypeIndex]
<< "' is not defined.";
return false);
- auto function = inst - 1;
// NOTE: Find OpFunction & ensure OpFunctionParameter is not out of place.
size_t paramIndex = 0;
- while (firstInst != function) {
- spvCheck(SpvOpFunction != function->opcode &&
- SpvOpFunctionParameter != function->opcode,
+ assert(firstInst < inst && "Invalid instruction pointer");
+ while (firstInst != --inst) {
+ spvCheck(SpvOpFunction != inst->opcode && SpvOpFunctionParameter != inst->opcode,
DIAG(0) << "OpFunctionParameter is not preceded by OpFunction or "
"OpFunctionParameter sequence.";
return false);
- if (SpvOpFunction == function->opcode) {
+ if (SpvOpFunction == inst->opcode) {
break;
} else {
paramIndex++;
}
}
- auto functionType = find(function->words[4]);
+ auto functionType = find(inst->words[4]);
spvCheck(!found(functionType), assert(0 && "Unreachable!"));
auto paramType = find(functionType->second.inst->words[paramIndex + 3]);
spvCheck(!found(paramType), assert(0 && "Unreachable!"));
diff --git a/test/BinaryParse.cpp b/test/BinaryParse.cpp
index af7040f..1c6f237 100644
--- a/test/BinaryParse.cpp
+++ b/test/BinaryParse.cpp
@@ -447,9 +447,9 @@
TEST_P(BinaryParseWordVectorDiagnosticTest, WordVectorCases) {
spv_diagnostic diagnostic = nullptr;
const auto& words = GetParam().words;
- EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
- spvBinaryParse(context, nullptr, words.data(), words.size(),
- nullptr, nullptr, &diagnostic));
+ EXPECT_THAT(spvBinaryParse(context, nullptr, words.data(), words.size(),
+ nullptr, nullptr, &diagnostic),
+ AnyOf(SPV_ERROR_INVALID_BINARY, SPV_ERROR_INVALID_ID));
ASSERT_NE(nullptr, diagnostic);
EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
}
@@ -669,9 +669,9 @@
TEST_P(BinaryParseAssemblyDiagnosticTest, AssemblyCases) {
spv_diagnostic diagnostic = nullptr;
auto words = CompileSuccessfully(GetParam().assembly);
- EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
- spvBinaryParse(context, nullptr, words.data(), words.size(),
- nullptr, nullptr, &diagnostic));
+ EXPECT_THAT(spvBinaryParse(context, nullptr, words.data(), words.size(),
+ nullptr, nullptr, &diagnostic),
+ AnyOf(SPV_ERROR_INVALID_BINARY, SPV_ERROR_INVALID_ID));
ASSERT_NE(nullptr, diagnostic);
EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
}
diff --git a/test/Validate.SSA.cpp b/test/Validate.SSA.cpp
new file mode 100644
index 0000000..ecbf46b
--- /dev/null
+++ b/test/Validate.SSA.cpp
@@ -0,0 +1,1000 @@
+// Copyright (c) 2015 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.
+
+// Validation tests for SSA
+
+#include "UnitSPIRV.h"
+#include "ValidateFixtures.h"
+
+#include <sstream>
+#include <string>
+#include <utility>
+
+using std::string;
+using std::pair;
+using std::stringstream;
+namespace {
+
+using Validate =
+ spvtest::ValidateBase<pair<string, bool>, SPV_VALIDATE_SSA_BIT>;
+
+// Returns true if the substr is a substring of message
+bool ContainsString(const string &message, const string &substr) {
+ return std::string::npos != message.find(substr);
+}
+
+TEST_F(Validate, Default) {
+ char str[] = R"(
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %3 ""
+ OpExecutionMode %3 LocalSize 1 1 1
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+%4 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(Validate, IdUndefinedBad) {
+ char str[] = R"(
+ OpMemoryModel Logical GLSL450
+ OpName %missing "missing"
+%voidt = OpTypeVoid
+%vfunct = OpTypeFunction %voidt
+%func = OpFunction %vfunct None %missing
+%flabel = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "missing"));
+}
+
+TEST_F(Validate, IdRedefinedBad) {
+ char str[] = R"(
+ OpMemoryModel Logical GLSL450
+ OpName %2 "redefined"
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%2 = OpFunction %1 None %2
+%4 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+}
+
+TEST_F(Validate, DominateUsageBad) {
+ char str[] = R"(
+ OpMemoryModel Logical GLSL450
+ OpName %1 "not_dominant"
+%2 = OpTypeFunction %1 ; uses %1 before it's definition
+%1 = OpTypeVoid
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "not_dominant"));
+}
+
+TEST_F(Validate, ForwardNameGood) {
+ char str[] = R"(
+ OpMemoryModel Logical GLSL450
+ OpName %3 "main"
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+%4 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(Validate, ForwardNameMissingTargetBad) {
+ char str[] = R"(
+ OpMemoryModel Logical GLSL450
+ OpName %5 "main" ; Target never defined
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "main"));
+}
+
+TEST_F(Validate, ForwardMemberNameGood) {
+ char str[] = R"(
+ OpMemoryModel Logical GLSL450
+ OpMemberName %struct 0 "value"
+ OpMemberName %struct 1 "size"
+%intt = OpTypeInt 32 1
+%uintt = OpTypeInt 32 0
+%struct = OpTypeStruct %intt %uintt
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(Validate, ForwardMemberNameMissingTargetBad) {
+ char str[] = R"(
+ OpMemoryModel Logical GLSL450
+ OpMemberName %struct 0 "value"
+ OpMemberName %bad 1 "size" ; Target is not defined
+%intt = OpTypeInt 32 1
+%uintt = OpTypeInt 32 0
+%struct = OpTypeStruct %intt %uintt
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "size"));
+}
+
+TEST_F(Validate, ForwardDecorateGood) {
+ char str[] = R"(
+ OpMemoryModel Logical GLSL450
+ OpDecorate %var Restrict
+%intt = OpTypeInt 32 1
+%ptrt = OpTypePointer UniformConstant %intt
+%var = OpVariable %ptrt UniformConstant
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(Validate, ForwardDecorateInvalidIDBad) {
+ char str[] = R"(
+ OpMemoryModel Logical GLSL450
+ OpName %missing "missing"
+ OpDecorate %missing Restrict ;Missing ID
+%voidt = OpTypeVoid
+%intt = OpTypeInt 32 1
+%ptrt = OpTypePointer UniformConstant %intt
+%var = OpVariable %ptrt UniformConstant
+%2 = OpTypeFunction %voidt
+%3 = OpFunction %voidt None %2
+%4 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "missing"));
+}
+
+TEST_F(Validate, ForwardMemberDecorateGood) {
+ char str[] = R"(
+ OpCapability Matrix
+ OpMemoryModel Logical GLSL450
+ OpMemberDecorate %struct 1 RowMajor
+%intt = OpTypeInt 32 1
+%vec3 = OpTypeVector %intt 3
+%mat33 = OpTypeMatrix %vec3 3
+%struct = OpTypeStruct %intt %mat33
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(Validate, ForwardMemberDecorateInvalidIdBad) {
+ char str[] = R"(
+ OpCapability Matrix
+ OpMemoryModel Logical GLSL450
+ OpName %missing "missing"
+ OpMemberDecorate %missing 1 RowMajor ; Target not defined
+%intt = OpTypeInt 32 1
+%vec3 = OpTypeVector %intt 3
+%mat33 = OpTypeMatrix %vec3 3
+%struct = OpTypeStruct %intt %mat33
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "missing"));
+}
+
+TEST_F(Validate, ForwardGroupDecorateGood) {
+ char str[] = R"(
+ OpCapability Matrix
+ OpMemoryModel Logical GLSL450
+ OpDecorate %dgrp RowMajor
+%dgrp = OpDecorationGroup
+ OpGroupDecorate %dgrp %mat33 %mat44
+%intt = OpTypeInt 32 1
+%vec3 = OpTypeVector %intt 3
+%vec4 = OpTypeVector %intt 4
+%mat33 = OpTypeMatrix %vec3 3
+%mat44 = OpTypeMatrix %vec4 4
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(Validate, ForwardGroupDecorateMissingGroupBad) {
+ char str[] = R"(
+ OpCapability Matrix
+ OpMemoryModel Logical GLSL450
+ OpName %missing "missing"
+ OpDecorate %dgrp RowMajor
+%dgrp = OpDecorationGroup
+ OpGroupDecorate %missing %mat33 %mat44 ; Target not defined
+%intt = OpTypeInt 32 1
+%vec3 = OpTypeVector %intt 3
+%vec4 = OpTypeVector %intt 4
+%mat33 = OpTypeMatrix %vec3 3
+%mat44 = OpTypeMatrix %vec4 4
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "missing"));
+}
+
+TEST_F(Validate, ForwardGroupDecorateMissingTargetBad) {
+ char str[] = R"(
+ OpCapability Matrix
+ OpMemoryModel Logical GLSL450
+ OpName %missing "missing"
+ OpDecorate %dgrp RowMajor
+%dgrp = OpDecorationGroup
+ OpGroupDecorate %dgrp %missing %mat44 ; Target not defined
+%intt = OpTypeInt 32 1
+%vec3 = OpTypeVector %intt 3
+%vec4 = OpTypeVector %intt 4
+%mat33 = OpTypeMatrix %vec3 3
+%mat44 = OpTypeMatrix %vec4 4
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "missing"));
+}
+
+TEST_F(Validate, ForwardGroupDecorateDecorationGroupDominateBad) {
+ char str[] = R"(
+ OpCapability Matrix
+ OpMemoryModel Logical GLSL450
+ OpName %dgrp "group"
+ OpDecorate %dgrp RowMajor
+ OpGroupDecorate %dgrp %mat33 %mat44 ; Decoration group does not dominate usage
+%dgrp = OpDecorationGroup
+%intt = OpTypeInt 32 1
+%vec3 = OpTypeVector %intt 3
+%vec4 = OpTypeVector %intt 4
+%mat33 = OpTypeMatrix %vec3 3
+%mat44 = OpTypeMatrix %vec4 4
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "group"));
+}
+
+TEST_F(Validate, ForwardDecorateInvalidIdBad) {
+ char str[] = R"(
+ OpMemoryModel Logical GLSL450
+ OpName %missing "missing"
+ OpDecorate %missing Restrict ; Missing target
+%voidt = OpTypeVoid
+%intt = OpTypeInt 32 1
+%ptrt = OpTypePointer UniformConstant %intt
+%var = OpVariable %ptrt UniformConstant
+%2 = OpTypeFunction %voidt
+%3 = OpFunction %voidt None %2
+%4 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "missing"));
+}
+
+TEST_F(Validate, FunctionCallGood) {
+ char str[] = R"(
+ OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeInt 32 1
+%3 = OpTypeInt 32 0
+%4 = OpTypeFunction %1
+%8 = OpTypeFunction %1 %2 %3
+%four = OpConstant %2 4
+%five = OpConstant %3 5
+%9 = OpFunction %1 None %8
+%10 = OpFunctionParameter %2
+%11 = OpFunctionParameter %3
+%12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+%5 = OpFunction %1 None %4
+%6 = OpLabel
+%7 = OpFunctionCall %1 %9 %four %five
+ OpFunctionEnd
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(Validate, ForwardFunctionCallGood) {
+ char str[] = R"(
+ OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeInt 32 1
+%3 = OpTypeInt 32 0
+%four = OpConstant %2 4
+%five = OpConstant %3 5
+%4 = OpTypeFunction %1
+%5 = OpFunction %1 None %4
+%6 = OpLabel
+%7 = OpFunctionCall %1 %9 %four %five
+ OpFunctionEnd
+%8 = OpTypeFunction %1 %2 %3
+%9 = OpFunction %1 None %8
+%10 = OpFunctionParameter %2
+%11 = OpFunctionParameter %3
+%12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(Validate, ForwardBranchConditionalGood) {
+ char str[] = R"(
+ OpMemoryModel Logical GLSL450
+%voidt = OpTypeVoid
+%boolt = OpTypeBool
+%vfunct = OpTypeFunction %voidt
+%true = OpConstantTrue %boolt
+%main = OpFunction %voidt None %vfunct
+%mainl = OpLabel
+ OpSelectionMerge %endl None
+ OpBranchConditional %true %truel %falsel
+%truel = OpLabel
+ OpNop
+ OpBranch %endl
+%falsel = OpLabel
+ OpNop
+%endl = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(Validate, ForwardBranchConditionalWithWeightsGood) {
+ char str[] = R"(
+ OpMemoryModel Logical GLSL450
+%voidt = OpTypeVoid
+%boolt = OpTypeBool
+%vfunct = OpTypeFunction %voidt
+%true = OpConstantTrue %boolt
+%main = OpFunction %voidt None %vfunct
+%mainl = OpLabel
+ OpSelectionMerge %endl None
+ OpBranchConditional %true %truel %falsel 1 9
+%truel = OpLabel
+ OpNop
+ OpBranch %endl
+%falsel = OpLabel
+ OpNop
+%endl = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(Validate, ForwardBranchConditionalNonDominantConditionBad) {
+ char str[] = R"(
+ OpMemoryModel Logical GLSL450
+ OpName %tcpy "conditional"
+%voidt = OpTypeVoid
+%boolt = OpTypeBool
+%vfunct = OpTypeFunction %voidt
+%true = OpConstantTrue %boolt
+%main = OpFunction %voidt None %vfunct
+%mainl = OpLabel
+ OpSelectionMerge %endl None
+ OpBranchConditional %tcpy %truel %falsel ;
+%truel = OpLabel
+ OpNop
+ OpBranch %endl
+%falsel = OpLabel
+ OpNop
+%endl = OpLabel
+%tcpy = OpCopyObject %boolt %true
+ OpReturn
+ OpFunctionEnd
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "conditional"));
+}
+
+TEST_F(Validate, ForwardBranchConditionalMissingTargetBad) {
+ char str[] = R"(
+ OpMemoryModel Logical GLSL450
+ OpName %missing "missing"
+%voidt = OpTypeVoid
+%boolt = OpTypeBool
+%vfunct = OpTypeFunction %voidt
+%true = OpConstantTrue %boolt
+%main = OpFunction %voidt None %vfunct
+%mainl = OpLabel
+ OpSelectionMerge %endl None
+ OpBranchConditional %true %missing %falsel
+%truel = OpLabel
+ OpNop
+ OpBranch %endl
+%falsel = OpLabel
+ OpNop
+%endl = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "missing"));
+}
+
+const string kBasicTypes = R"(
+ OpCapability Int8
+ OpCapability DeviceEnqueue
+ OpMemoryModel Logical GLSL450
+%voidt = OpTypeVoid
+%boolt = OpTypeBool
+%int8t = OpTypeInt 8 0
+%intt = OpTypeInt 32 1
+%uintt = OpTypeInt 32 0
+%vfunct = OpTypeFunction %voidt
+)";
+
+const string kKernelTypesAndConstants = R"(
+%queuet = OpTypeQueue
+
+%three = OpConstant %uintt 3
+%arr3t = OpTypeArray %intt %three
+%ndt = OpTypeStruct %intt %arr3t %arr3t %arr3t
+
+%eventt = OpTypeEvent
+%intptrt = OpTypePointer UniformConstant %int8t
+
+%offset = OpConstant %intt 0
+%local = OpConstant %intt 1
+%gl = OpConstant %intt 1
+
+%nevent = OpConstant %intt 0
+%event = OpConstantNull %eventt
+
+%firstp = OpConstant %int8t 0
+%psize = OpConstant %intt 0
+%palign = OpConstant %intt 32
+%lsize = OpConstant %intt 1
+%flags = OpConstant %intt 0 ; NoWait
+
+%kfunct = OpTypeFunction %voidt %intptrt
+)";
+
+const string kKernelSetup = R"(
+%dqueue = OpGetDefaultQueue %queuet
+%ndval = OpBuildNDRange %ndt %gl %local %offset
+%revent = OpUndef %eventt
+
+)";
+
+const string kKernelDefinition = R"(
+%kfunc = OpFunction %voidt None %kfunct
+%iparam = OpFunctionParameter %intptrt
+%kfuncl = OpLabel
+ OpNop
+ OpReturn
+ OpFunctionEnd
+)";
+
+TEST_F(Validate, EnqueueKernelGood) {
+ string str = kBasicTypes + kKernelTypesAndConstants + kKernelDefinition + R"(
+ %main = OpFunction %voidt None %vfunct
+ %mainl = OpLabel
+ )" +
+ kKernelSetup + R"(
+ %err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
+ %event %revent %kfunc %firstp %psize
+ %palign %lsize
+ OpReturn
+ OpFunctionEnd
+ )";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(Validate, ForwardEnqueueKernelGood) {
+ string str = kBasicTypes + kKernelTypesAndConstants + R"(
+ %main = OpFunction %voidt None %vfunct
+ %mainl = OpLabel
+ )" +
+ kKernelSetup + R"(
+ %err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
+ %event %revent %kfunc %firstp %psize
+ %palign %lsize
+ OpReturn
+ OpFunctionEnd
+ )" +
+ kKernelDefinition;
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(Validate, EnqueueMissingFunctionBad) {
+ string str = kBasicTypes + "OpName %kfunc \"kfunc\"" +
+ kKernelTypesAndConstants + R"(
+ %main = OpFunction %voidt None %vfunct
+ %mainl = OpLabel
+ )" +
+ kKernelSetup + R"(
+ %err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
+ %event %revent %kfunc %firstp %psize
+ %palign %lsize
+ OpReturn
+ OpFunctionEnd
+ )";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "kfunc"));
+}
+
+string forwardKernelNonDominantParameterBaseCode(string name = string()) {
+ string op_name;
+ if (name.empty()) {
+ op_name = "";
+ } else {
+ op_name = "\nOpName %" + name + " \"" + name + "\"\n";
+ }
+ string out = kBasicTypes + op_name + kKernelTypesAndConstants +
+ kKernelDefinition +
+ R"(
+ %main = OpFunction %voidt None %vfunct
+ %mainl = OpLabel
+ )" +
+ kKernelSetup;
+ return out;
+}
+
+TEST_F(Validate, ForwardEnqueueKernelMissingParameter1Bad) {
+ string str = forwardKernelNonDominantParameterBaseCode("missing") + R"(
+ %err = OpEnqueueKernel %missing %dqueue %flags %ndval
+ %nevent %event %revent %kfunc %firstp
+ %psize %palign %lsize
+ OpReturn
+ OpFunctionEnd
+ )";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "missing"));
+}
+
+TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter2Bad) {
+ string str = forwardKernelNonDominantParameterBaseCode("dqueue2") + R"(
+ %err = OpEnqueueKernel %uintt %dqueue2 %flags %ndval
+ %nevent %event %revent %kfunc
+ %firstp %psize %palign %lsize
+ %dqueue2 = OpGetDefaultQueue %queuet
+ OpReturn
+ OpFunctionEnd
+ )";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "dqueue2"));
+}
+
+TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter3Bad) {
+ string str = forwardKernelNonDominantParameterBaseCode("ndval2") + R"(
+ %err = OpEnqueueKernel %uintt %dqueue %flags %ndval2
+ %nevent %event %revent %kfunc %firstp
+ %psize %palign %lsize
+ %ndval2 = OpBuildNDRange %ndt %gl %local %offset
+ OpReturn
+ OpFunctionEnd
+ )";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "ndval2"));
+}
+
+TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter4Bad) {
+ string str = forwardKernelNonDominantParameterBaseCode("nevent2") + R"(
+ %err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent2
+ %event %revent %kfunc %firstp %psize
+ %palign %lsize
+ %nevent2 = OpCopyObject %intt %nevent
+ OpReturn
+ OpFunctionEnd
+ )";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "nevent2"));
+}
+
+TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter5Bad) {
+ string str = forwardKernelNonDominantParameterBaseCode("event2") + R"(
+ %err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
+ %event2 %revent %kfunc %firstp %psize
+ %palign %lsize
+ %event2 = OpCopyObject %eventt %event
+ OpReturn
+ OpFunctionEnd
+ )";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "event2"));
+}
+
+TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter6Bad) {
+ string str = forwardKernelNonDominantParameterBaseCode("revent2") + R"(
+ %err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
+ %event %revent2 %kfunc %firstp %psize
+ %palign %lsize
+ %revent2 = OpCopyObject %eventt %revent
+ OpReturn
+ OpFunctionEnd
+ )";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "revent2"));
+}
+
+TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter8Bad) {
+ string str = forwardKernelNonDominantParameterBaseCode("firstp2") + R"(
+ %err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
+ %event %revent %kfunc %firstp2 %psize
+ %palign %lsize
+ %firstp2 = OpCopyObject %int8t %firstp
+ OpReturn
+ OpFunctionEnd
+ )";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "firstp2"));
+}
+
+TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter9Bad) {
+ string str = forwardKernelNonDominantParameterBaseCode("psize2") + R"(
+ %err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
+ %event %revent %kfunc %firstp %psize2
+ %palign %lsize
+ %psize2 = OpCopyObject %intt %psize
+ OpReturn
+ OpFunctionEnd
+ )";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "psize2"));
+}
+
+TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter10Bad) {
+ string str = forwardKernelNonDominantParameterBaseCode("palign2") + R"(
+ %err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
+ %event %revent %kfunc %firstp %psize
+ %palign2 %lsize
+ %palign2 = OpCopyObject %intt %palign
+ OpReturn
+ OpFunctionEnd
+ )";
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "palign2"));
+}
+
+TEST_F(Validate, ForwardEnqueueKernelNonDominantParameter11Bad) {
+ string str = forwardKernelNonDominantParameterBaseCode("lsize2") + R"(
+ %err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
+ %event %revent %kfunc %firstp %psize
+ %palign %lsize2
+ %lsize2 = OpCopyObject %intt %lsize
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "lsize2"));
+}
+
+static const bool kWithNDrange = true;
+static const bool kNoNDrange = false;
+pair<string, bool> cases[] = {
+ {"OpGetKernelNDrangeSubGroupCount", kWithNDrange},
+ {"OpGetKernelNDrangeMaxSubGroupSize", kWithNDrange},
+ {"OpGetKernelWorkGroupSize", kNoNDrange},
+ {"OpGetKernelPreferredWorkGroupSizeMultiple", kNoNDrange}};
+
+INSTANTIATE_TEST_CASE_P(KernelArgs, Validate, ::testing::ValuesIn(cases));
+
+static const string return_instructions = R"(
+ OpReturn
+ OpFunctionEnd
+)";
+
+TEST_P(Validate, GetKernelGood) {
+ string instruction = GetParam().first;
+ bool with_ndrange = GetParam().second;
+ string ndrange_param = with_ndrange ? " %ndval " : " ";
+
+ stringstream ss;
+ // clang-format off
+ ss << forwardKernelNonDominantParameterBaseCode() + " %numsg = "
+ << instruction + " %uintt" + ndrange_param + "%kfunc %firstp %psize %palign"
+ << return_instructions;
+ // clang-format on
+
+ CompileSuccessfully(ss.str());
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(Validate, ForwardGetKernelGood) {
+ string instruction = GetParam().first;
+ bool with_ndrange = GetParam().second;
+ string ndrange_param = with_ndrange ? " %ndval " : " ";
+
+ // clang-format off
+ string str = kBasicTypes + kKernelTypesAndConstants +
+ R"(
+ %main = OpFunction %voidt None %vfunct
+ )"
+ + kKernelSetup + " %numsg = "
+ + instruction + " %uintt" + ndrange_param + "%kfunc %firstp %psize %palign"
+ + return_instructions + kKernelDefinition;
+ // clang-format on
+
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(Validate, ForwardGetKernelMissingDefinitionBad) {
+ string instruction = GetParam().first;
+ bool with_ndrange = GetParam().second;
+ string ndrange_param = with_ndrange ? " %ndval " : " ";
+
+ stringstream ss;
+ // clang-format off
+ ss << forwardKernelNonDominantParameterBaseCode("missing") + " %numsg = "
+ << instruction + " %uintt" + ndrange_param + "%missing %firstp %psize %palign"
+ << return_instructions;
+ // clang-format on
+
+ CompileSuccessfully(ss.str());
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "missing"));
+}
+
+TEST_P(Validate, ForwardGetKernelNDrangeSubGroupCountMissingParameter1Bad) {
+ string instruction = GetParam().first;
+ bool with_ndrange = GetParam().second;
+ string ndrange_param = with_ndrange ? " %ndval " : " ";
+
+ stringstream ss;
+ // clang-format off
+ ss << forwardKernelNonDominantParameterBaseCode("missing") + " %numsg = "
+ << instruction + " %missing" + ndrange_param + "%kfunc %firstp %psize %palign"
+ << return_instructions;
+ // clang-format on
+
+ CompileSuccessfully(ss.str());
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "missing"));
+}
+
+TEST_P(Validate, ForwardGetKernelNDrangeSubGroupCountNonDominantParameter2Bad) {
+ string instruction = GetParam().first;
+ bool with_ndrange = GetParam().second;
+ string ndrange_param = with_ndrange ? " %ndval2 " : " ";
+
+ stringstream ss;
+ // clang-format off
+ ss << forwardKernelNonDominantParameterBaseCode("ndval2") + " %numsg = "
+ << instruction + " %uintt" + ndrange_param + "%kfunc %firstp %psize %palign"
+ << "\n %ndval2 = OpBuildNDRange %ndt %gl %local %offset"
+ << return_instructions;
+ // clang-format on
+
+ if (GetParam().second) {
+ CompileSuccessfully(ss.str());
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "ndval2"));
+ }
+}
+
+TEST_P(Validate, ForwardGetKernelNDrangeSubGroupCountNonDominantParameter4Bad) {
+ string instruction = GetParam().first;
+ bool with_ndrange = GetParam().second;
+ string ndrange_param = with_ndrange ? " %ndval " : " ";
+
+ stringstream ss;
+ // clang-format off
+ ss << forwardKernelNonDominantParameterBaseCode("firstp2") + " %numsg = "
+ << instruction + " %uintt" + ndrange_param + "%kfunc %firstp2 %psize %palign"
+ << "\n %firstp2 = OpCopyObject %int8t %firstp"
+ << return_instructions;
+ // clang-format on
+
+ CompileSuccessfully(ss.str());
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "firstp2"));
+}
+
+TEST_P(Validate, ForwardGetKernelNDrangeSubGroupCountNonDominantParameter5Bad) {
+ string instruction = GetParam().first;
+ bool with_ndrange = GetParam().second;
+ string ndrange_param = with_ndrange ? " %ndval " : " ";
+
+ stringstream ss;
+ // clang-format off
+ ss << forwardKernelNonDominantParameterBaseCode("psize2") + " %numsg = "
+ << instruction + " %uintt" + ndrange_param + "%kfunc %firstp %psize2 %palign"
+ << "\n %psize2 = OpCopyObject %intt %psize"
+ << return_instructions;
+ // clang-format on
+
+ CompileSuccessfully(ss.str());
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "psize2"));
+}
+
+TEST_P(Validate, ForwardGetKernelNDrangeSubGroupCountNonDominantParameter6Bad) {
+ string instruction = GetParam().first;
+ bool with_ndrange = GetParam().second;
+ string ndrange_param = with_ndrange ? " %ndval " : " ";
+
+ stringstream ss;
+ // clang-format off
+ ss << forwardKernelNonDominantParameterBaseCode("palign2") + " %numsg = "
+ << instruction + " %uintt" + ndrange_param + "%kfunc %firstp %psize %palign2"
+ << "\n %palign2 = OpCopyObject %intt %palign"
+ << return_instructions;
+ // clang-format on
+
+ if (GetParam().second) {
+ CompileSuccessfully(ss.str());
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "palign2"));
+ }
+}
+
+TEST_F(Validate, PhiGood) {
+ string str = 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
+ OpBranch %loop
+%loop = OpLabel
+%i = OpPhi %intt %init %preheader %loopi %loop
+%loopi = OpIAdd %intt %i %one
+ OpNop
+%cond = OpSLessThan %boolt %i %ten
+ OpLoopMerge %endl %loop None
+ OpBranchConditional %cond %loop %endl
+%endl = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(Validate, PhiMissingTypeBad) {
+ string str = kBasicTypes +
+ R"(
+ OpName %missing "missing"
+%zero = OpConstant %intt 0
+%one = OpConstant %intt 1
+%ten = OpConstant %intt 10
+%func = OpFunction %voidt None %vfunct
+%preheader = OpLabel
+%init = OpCopyObject %intt %zero
+ OpBranch %loop
+%loop = OpLabel
+%i = OpPhi %missing %init %preheader %loopi %loop
+%loopi = OpIAdd %intt %i %one
+ OpNop
+%cond = OpSLessThan %boolt %i %ten
+ OpLoopMerge %endl %loop None
+ OpBranchConditional %cond %loop %endl
+%endl = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "missing"));
+}
+
+TEST_F(Validate, PhiMissingIdBad) {
+ string str = kBasicTypes +
+ R"(
+ OpName %missing "missing"
+%zero = OpConstant %intt 0
+%one = OpConstant %intt 1
+%ten = OpConstant %intt 10
+%func = OpFunction %voidt None %vfunct
+%preheader = OpLabel
+%init = OpCopyObject %intt %zero
+ OpBranch %loop
+%loop = OpLabel
+%i = OpPhi %intt %missing %preheader %loopi %loop
+%loopi = OpIAdd %intt %i %one
+ OpNop
+%cond = OpSLessThan %boolt %i %ten
+ OpLoopMerge %endl %loop None
+ OpBranchConditional %cond %loop %endl
+%endl = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "missing"));
+}
+
+TEST_F(Validate, PhiMissingLabelBad) {
+ string str = kBasicTypes +
+ R"(
+ OpName %missing "missing"
+%zero = OpConstant %intt 0
+%one = OpConstant %intt 1
+%ten = OpConstant %intt 10
+%func = OpFunction %voidt None %vfunct
+%preheader = OpLabel
+%init = OpCopyObject %intt %zero
+ OpBranch %loop
+%loop = OpLabel
+%i = OpPhi %intt %init %missing %loopi %loop
+%loopi = OpIAdd %intt %i %one
+ OpNop
+%cond = OpSLessThan %boolt %i %ten
+ OpLoopMerge %endl %loop None
+ OpBranchConditional %cond %loop %endl
+%endl = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(str);
+ ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ ASSERT_TRUE(ContainsString(getDiagnosticString(), "missing"));
+}
+
+// TODO(umar): OpGroupMemberDecorate
+}
diff --git a/test/Validate.cpp b/test/Validate.cpp
deleted file mode 100644
index 2733ace..0000000
--- a/test/Validate.cpp
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright (c) 2015 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 "UnitSPIRV.h"
-
-namespace {
-
-class Validate : public ::testing::Test {
- public:
- Validate() : context(spvContextCreate()), binary() {}
- ~Validate() { spvContextDestroy(context); }
-
- virtual void TearDown() { spvBinaryDestroy(binary); }
- spv_const_binary get_const_binary() { return spv_const_binary(binary); }
-
- spv_context context;
- spv_binary binary;
-};
-
-TEST_F(Validate, DISABLED_Default) {
- char str[] = R"(
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute 1 ""
-OpExecutionMode 1 LocalSize 1 1 1
-OpTypeVoid 2
-OpTypeFunction 3 2
-OpFunction 2 1 NoControl 3
-OpLabel 4
-OpReturn
-OpFunctionEnd
-)";
- spv_diagnostic diagnostic = nullptr;
- ASSERT_EQ(SPV_SUCCESS,
- spvTextToBinary(context, str, strlen(str), &binary, &diagnostic));
- ASSERT_EQ(SPV_SUCCESS, spvValidate(context, get_const_binary(),
- SPV_VALIDATE_ALL, &diagnostic));
- if (diagnostic) {
- spvDiagnosticPrint(diagnostic);
- spvDiagnosticDestroy(diagnostic);
- }
-}
-
-TEST_F(Validate, DISABLED_InvalidIdUndefined) {
- char str[] = R"(
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute 1 ""
-OpExecutionMode 5 LocalSize 1 1 1
-OpTypeVoid 2
-OpTypeFunction 3 2
-OpFunction 2 1 NoControl 3
-OpLabel 4
-OpReturn
-OpFunctionEnd
-)";
- spv_diagnostic diagnostic = nullptr;
- ASSERT_EQ(SPV_SUCCESS,
- spvTextToBinary(context, str, strlen(str), &binary, &diagnostic));
- ASSERT_EQ(SPV_ERROR_INVALID_ID, spvValidate(context, get_const_binary(),
- SPV_VALIDATE_ALL, &diagnostic));
- ASSERT_NE(nullptr, diagnostic);
- spvDiagnosticPrint(diagnostic);
- spvDiagnosticDestroy(diagnostic);
-}
-
-TEST_F(Validate, DISABLED_InvalidIdRedefined) {
- char str[] = R"(
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute 1 ""
-OpExecutionMode 1 LocalSize 1 1 1
-OpTypeVoid 2
-OpTypeFunction 2 2
-OpFunction 2 1 NoControl 3
-OpLabel 4
-OpReturn
-OpFunctionEnd
-)";
- spv_diagnostic diagnostic = nullptr;
- ASSERT_EQ(SPV_SUCCESS,
- spvTextToBinary(context, str, strlen(str), &binary, &diagnostic));
- // TODO: Fix setting of bound in spvTextTo, then remove this!
- ASSERT_EQ(SPV_ERROR_INVALID_ID, spvValidate(context, get_const_binary(),
- SPV_VALIDATE_ALL, &diagnostic));
- ASSERT_NE(nullptr, diagnostic);
- spvDiagnosticPrint(diagnostic);
- spvDiagnosticDestroy(diagnostic);
-}
-
-} // anonymous namespace
diff --git a/test/ValidateFixtures.cpp b/test/ValidateFixtures.cpp
new file mode 100644
index 0000000..5fde620
--- /dev/null
+++ b/test/ValidateFixtures.cpp
@@ -0,0 +1,78 @@
+// Copyright (c) 2015 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.
+
+// Common validation fixtures for unit tests
+
+#include "UnitSPIRV.h"
+#include "ValidateFixtures.h"
+
+namespace spvtest {
+
+template <typename T, uint32_t OPTIONS>
+ValidateBase<T, OPTIONS>::ValidateBase()
+ : context_(spvContextCreate()), binary_(), diagnostic_() {}
+
+template <typename T, uint32_t OPTIONS>
+ValidateBase<T, OPTIONS>::~ValidateBase() {
+ spvContextDestroy(context_);
+}
+
+template <typename T, uint32_t OPTIONS>
+spv_const_binary ValidateBase<T, OPTIONS>::get_const_binary() {
+ return spv_const_binary(binary_);
+}
+
+template <typename T, uint32_t OPTIONS>
+void ValidateBase<T, OPTIONS>::TearDown() {
+ if (diagnostic_) {
+ spvDiagnosticPrint(diagnostic_);
+ }
+ spvDiagnosticDestroy(diagnostic_);
+ spvBinaryDestroy(binary_);
+}
+
+template <typename T, uint32_t OPTIONS>
+void ValidateBase<T, OPTIONS>::CompileSuccessfully(std::string code) {
+ spv_diagnostic diagnostic = nullptr;
+ EXPECT_EQ(SPV_SUCCESS, spvTextToBinary(context_, code.c_str(), code.size(),
+ &binary_, &diagnostic))
+ << "SPIR-V could not be compiled into binary:" << code;
+}
+
+template <typename T, uint32_t OPTIONS>
+spv_result_t ValidateBase<T, OPTIONS>::ValidateInstructions() {
+ return spvValidate(context_, get_const_binary(), validation_options_,
+ &diagnostic_);
+}
+
+template <typename T, uint32_t OPTIONS>
+std::string ValidateBase<T, OPTIONS>::getDiagnosticString() {
+ return std::string(diagnostic_->error);
+}
+
+template class spvtest::ValidateBase<std::pair<std::string, bool>,
+ SPV_VALIDATE_SSA_BIT>;
+}
diff --git a/test/ValidateFixtures.h b/test/ValidateFixtures.h
new file mode 100644
index 0000000..1978f87
--- /dev/null
+++ b/test/ValidateFixtures.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2015 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.
+
+// Common validation fixtures for unit tests
+
+#include "UnitSPIRV.h"
+
+namespace spvtest {
+
+template <typename T, uint32_t OPTIONS = SPV_VALIDATE_ALL>
+class ValidateBase : public ::testing::Test,
+ public ::testing::WithParamInterface<T> {
+ public:
+ ValidateBase();
+ ~ValidateBase();
+
+ virtual void TearDown();
+
+ // Returns the a spv_const_binary struct
+ spv_const_binary get_const_binary();
+
+ void CompileSuccessfully(std::string code);
+
+ // Performs validation on the SPIR-V code and compares the result of the
+ // spvValidate function
+ spv_result_t ValidateInstructions();
+
+ std::string getDiagnosticString();
+
+ spv_context context_;
+ spv_binary binary_;
+ spv_diagnostic diagnostic_;
+ static const uint32_t validation_options_ = OPTIONS;
+};
+}
diff --git a/test/ValidateID.cpp b/test/ValidateID.cpp
index 956eb04..078d956 100644
--- a/test/ValidateID.cpp
+++ b/test/ValidateID.cpp
@@ -407,7 +407,7 @@
%2 = OpConstant %1 1)";
CHECK(spirv, SPV_SUCCESS);
}
-TEST_F(ValidateID, OpConstantBad) {
+TEST_F(ValidateID, DISABLED_OpConstantBad) {
const char* spirv = R"(
%1 = OpTypeVoid
%2 = OpConstant !1 !0)";
@@ -629,7 +629,7 @@
%2 = OpSpecConstant %1 42)";
CHECK(spirv, SPV_SUCCESS);
}
-TEST_F(ValidateID, OpSpecConstantBad) {
+TEST_F(ValidateID, DISABLED_OpSpecConstantBad) {
const char* spirv = R"(
%1 = OpTypeVoid
%2 = OpSpecConstant !1 !4)";
@@ -948,6 +948,19 @@
OpFunctionEnd)";
CHECK(spirv, SPV_SUCCESS);
}
+TEST_F(ValidateID, OpFunctionParameterMultipleGood) {
+ const char* spirv = R"(
+%1 = OpTypeVoid
+%2 = OpTypeInt 32 0
+%3 = OpTypeFunction %1 %2 %2
+%4 = OpFunction %1 None %3
+%5 = OpFunctionParameter %2
+%6 = OpFunctionParameter %2
+%7 = OpLabel
+ OpReturn
+ OpFunctionEnd)";
+ CHECK(spirv, SPV_SUCCESS);
+}
TEST_F(ValidateID, OpFunctionParameterResultTypeBad) {
const char* spirv = R"(
%1 = OpTypeVoid