#include "validate.h"
-#include <assert.h>
-#include <stdio.h>
-#include <string.h>
-
-#include <vector>
-
#include "binary.h"
#include "diagnostic.h"
#include "endian.h"
#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,
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;
// 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));
return SPV_SUCCESS;
}
+#endif
spv_result_t spvValidateIDs(const spv_instruction_t* pInsts,
const uint64_t count, const uint32_t bound,
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) {
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;
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(
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;
}
--- /dev/null
+// 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
+}