Basic SSA Validation
authorUmar Arshad <umar@arrayfire.com>
Wed, 18 Nov 2015 20:43:43 +0000 (15:43 -0500)
committerDavid Neto <dneto@google.com>
Wed, 9 Dec 2015 21:15:00 +0000 (16:15 -0500)
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.

12 files changed:
.gitignore
CMakeLists.txt
include/libspirv/libspirv.h
source/binary.cpp
source/validate.cpp
source/validate_id.cpp
test/BinaryParse.cpp
test/Validate.SSA.cpp [new file with mode: 0644]
test/Validate.cpp [deleted file]
test/ValidateFixtures.cpp [new file with mode: 0644]
test/ValidateFixtures.h [new file with mode: 0644]
test/ValidateID.cpp

index 345f8a6..b2e6030 100644 (file)
@@ -1,3 +1,6 @@
 build*
 .ycm_extra_conf.py*
 compile_commands.json
+/external/googletest/
+/TAGS
+/.clang_complete
index 6826b98..652418e 100644 (file)
@@ -79,7 +79,6 @@ function(default_compile_options TARGET)
       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 @@ if (NOT ${SPIRV_SKIP_EXECUTABLES})
       ${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)
 
index c23cde9..07bbe75 100644 (file)
@@ -257,8 +257,10 @@ typedef enum spv_validate_options_t {
   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 @@ typedef spv_result_t (*spv_parsed_instruction_fn_t)(
 // 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,
index 918b908..5d36dfb 100755 (executable)
@@ -463,17 +463,17 @@ spv_result_t Parser::parseOperand(size_t inst_offset,
 
   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 @@ spv_result_t Parser::parseOperand(size_t inst_offset,
 
     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 @@ spv_result_t Parser::parseOperand(size_t inst_offset,
         // 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";
         }
index 8b6a797..4554e01 100644 (file)
 
 #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,
@@ -80,7 +97,10 @@ spv_result_t spvValidateOperandValue(const spv_operand_type_t type,
                                      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 @@ spv_result_t spvValidateBasic(const spv_instruction_t* pInsts,
       // 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 @@ spv_result_t spvValidateBasic(const spv_instruction_t* pInsts,
 
   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 @@ spv_result_t spvValidateIDs(const spv_instruction_t* pInsts,
   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 @@ spv_result_t spvValidate(const spv_const_context context,
     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 @@ spv_result_t spvValidate(const spv_const_context context,
     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 @@ spv_result_t spvValidate(const spv_const_context context,
                        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;
 }
index 9d9a7eb..cdb8e59 100644 (file)
@@ -1216,22 +1216,21 @@ bool idUsage::isValid<SpvOpFunctionParameter>(const spv_instruction_t* inst,
                                    << 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!"));
index af7040f..1c6f237 100644 (file)
@@ -447,9 +447,9 @@ using BinaryParseWordVectorDiagnosticTest = spvtest::TextToBinaryTestBase<
 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 @@ using BinaryParseAssemblyDiagnosticTest = spvtest::TextToBinaryTestBase<
 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 (file)
index 0000000..ecbf46b
--- /dev/null
@@ -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 (file)
index 2733ace..0000000
+++ /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 (file)
index 0000000..5fde620
--- /dev/null
@@ -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 (file)
index 0000000..1978f87
--- /dev/null
@@ -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;
+};
+}
index 956eb04..078d956 100644 (file)
@@ -407,7 +407,7 @@ TEST_F(ValidateID, OpConstantGood) {
 %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 @@ TEST_F(ValidateID, OpSpecConstantGood) {
 %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 @@ TEST_F(ValidateID, OpFunctionParameterGood) {
      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