Initial logical layout validation
authorUmar Arshad <umar@arrayfire.com>
Tue, 15 Dec 2015 19:50:05 +0000 (14:50 -0500)
committerDavid Neto <dneto@google.com>
Thu, 17 Dec 2015 20:58:09 +0000 (15:58 -0500)
* Validates module level instructions for logical layout
  conformance
* Does not validate:
  1. Function logical layout
  2. Minor cases with OpVariable
  3. Missing MemoryModel instruction in module
  4. Order of function definition and function declaration
* 782 unit tests for logical layout

Addressed feedback

CMakeLists.txt
include/libspirv/libspirv.h
source/validate.cpp
source/validate_types.cpp
source/validate_types.h
test/Validate.Layout.cpp [new file with mode: 0644]
test/Validate.SSA.cpp
test/ValidateFixtures.cpp

index 4b74257..c4e2007 100644 (file)
@@ -224,6 +224,7 @@ if (NOT ${SPIRV_SKIP_EXECUTABLES})
       ${CMAKE_CURRENT_SOURCE_DIR}/test/TextWordGet.cpp
       ${CMAKE_CURRENT_SOURCE_DIR}/test/UnitSPIRV.cpp
       ${CMAKE_CURRENT_SOURCE_DIR}/test/ValidateFixtures.cpp
+      ${CMAKE_CURRENT_SOURCE_DIR}/test/Validate.Layout.cpp
       ${CMAKE_CURRENT_SOURCE_DIR}/test/Validate.SSA.cpp
       ${CMAKE_CURRENT_SOURCE_DIR}/test/ValidateID.cpp
       ${CMAKE_CURRENT_SOURCE_DIR}/test/main.cpp)
index bd5ad5a..044ba88 100644 (file)
@@ -75,6 +75,7 @@ typedef enum spv_result_t {
   SPV_ERROR_INVALID_LOOKUP = -9,
   SPV_ERROR_INVALID_ID = -10,
   SPV_ERROR_INVALID_CFG = -11,
+  SPV_ERROR_INVALID_LAYOUT = -12,
   SPV_FORCE_32_BIT_ENUM(spv_result_t)
 } spv_result_t;
 
index 179d225..24a9793 100644 (file)
@@ -436,6 +436,109 @@ void DebugInstructionPass(ValidationState_t& _,
   }
 }
 
+// TODO(umar): Check MemoryModel is in module
+// TODO(umar): Check OpVariable storage class is not function in module section
+// TODO(umar): Make sure function declarations appear before function
+// definitions
+// TODO(umar): Better error messages
+// NOTE: This function does not handle CFG related validation
+// Performs logical layout validation. See Section 2.4
+spv_result_t ModuleLayoutPass(ValidationState_t& _,
+                              const spv_parsed_instruction_t* inst) {
+  if (_.is_enabled(SPV_VALIDATE_LAYOUT_BIT)) {
+    SpvOp opcode = inst->opcode;
+
+    if (libspirv::ModuleLayoutSection::kModule == _.getLayoutStage()) {
+      // Module scoped instructions are processed by determining if the opcode
+      // is part of the current stage. If it is not then the next stage is
+      // checked.
+      while (_.isOpcodeInCurrentLayoutStage(opcode) == false) {
+        // TODO(umar): Check if the MemoryModel instruction has executed
+        _.progressToNextLayoutStageOrder();
+        if (_.getLayoutStage() == libspirv::ModuleLayoutSection::kFunction) {
+          // All module stages have been processed. Recursivly call
+          // ModuleLayoutPass
+          // to process the next section of the module
+          return ModuleLayoutPass(_, inst);
+        }
+      }
+    } else {
+      // Validate the function layout.
+      switch (opcode) {
+        case SpvOpCapability:
+        case SpvOpExtension:
+        case SpvOpExtInstImport:
+        case SpvOpMemoryModel:
+        case SpvOpEntryPoint:
+        case SpvOpExecutionMode:
+        case SpvOpSourceContinued:
+        case SpvOpSource:
+        case SpvOpSourceExtension:
+        case SpvOpString:
+        case SpvOpName:
+        case SpvOpMemberName:
+        case SpvOpDecorate:
+        case SpvOpMemberDecorate:
+        case SpvOpGroupDecorate:
+        case SpvOpGroupMemberDecorate:
+        case SpvOpDecorationGroup:
+        case SpvOpTypeVoid:
+        case SpvOpTypeBool:
+        case SpvOpTypeInt:
+        case SpvOpTypeFloat:
+        case SpvOpTypeVector:
+        case SpvOpTypeMatrix:
+        case SpvOpTypeImage:
+        case SpvOpTypeSampler:
+        case SpvOpTypeSampledImage:
+        case SpvOpTypeArray:
+        case SpvOpTypeRuntimeArray:
+        case SpvOpTypeStruct:
+        case SpvOpTypeOpaque:
+        case SpvOpTypePointer:
+        case SpvOpTypeFunction:
+        case SpvOpTypeEvent:
+        case SpvOpTypeDeviceEvent:
+        case SpvOpTypeReserveId:
+        case SpvOpTypeQueue:
+        case SpvOpTypePipe:
+        case SpvOpTypeForwardPointer:
+        case SpvOpConstantTrue:
+        case SpvOpConstantFalse:
+        case SpvOpConstant:
+        case SpvOpConstantComposite:
+        case SpvOpConstantSampler:
+        case SpvOpConstantNull:
+        case SpvOpSpecConstantTrue:
+        case SpvOpSpecConstantFalse:
+        case SpvOpSpecConstant:
+        case SpvOpSpecConstantComposite:
+        case SpvOpSpecConstantOp:
+          return _.diag(SPV_ERROR_INVALID_LAYOUT) << "Invalid Layout";
+        case SpvOpVariable: {
+          const uint32_t* storage_class =
+              inst->words + inst->operands[2].offset;
+          if (*storage_class != SpvStorageClassFunction)
+            return _.diag(SPV_ERROR_INVALID_LAYOUT)
+                   << "All OpVariable instructions in a function must have a "
+                      "storage class of Function[7]";
+          break;
+        }
+        default:
+          return SPV_SUCCESS;
+      }
+    }
+  }
+  return SPV_SUCCESS;
+}
+
+// Shame
+#define CHECK_RESULT(EXPRESSION)                \
+  do{                                           \
+    spv_result_t ret = EXPRESSION;              \
+    if(ret) return ret;                         \
+} while(false);
+
 spv_result_t ProcessInstructions(void* user_data,
                                  const spv_parsed_instruction_t* inst) {
   ValidationState_t& _ = *(reinterpret_cast<ValidationState_t*>(user_data));
@@ -447,10 +550,13 @@ spv_result_t ProcessInstructions(void* user_data,
   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);
+  spv_result_t ret = SPV_SUCCESS;
+  CHECK_RESULT(ModuleLayoutPass(_, inst))
+  CHECK_RESULT(SsaPass(_, can_have_forward_declared_ids, inst))
+
+  return ret;
 }
 
 } // anonymous namespace
index 0779b2e..5690607 100644 (file)
 // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 // MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
 
+#include "headers/spirv.h"
 #include "validate_types.h"
 
+#include <algorithm>
 #include <map>
 #include <string>
 #include <unordered_set>
 #include <vector>
 
+using std::find;
 using std::string;
-using std::vector;
 using std::unordered_set;
+using std::vector;
+
+namespace {
+const vector<vector<SpvOp>>& GetModuleOrder() {
+  // See Section 2.4
+  // clang-format off
+  static const vector<vector<SpvOp>> moduleOrder = {
+    {SpvOpCapability},
+    {SpvOpExtension},
+    {SpvOpExtInstImport},
+    {SpvOpMemoryModel},
+    {SpvOpEntryPoint},
+    {SpvOpExecutionMode},
+    {
+      // first set of debug instructions
+      SpvOpSourceContinued,
+      SpvOpSource,
+      SpvOpSourceExtension,
+      SpvOpString,
+    },
+    {
+      // second set of debug instructions
+      SpvOpName,
+      SpvOpMemberName
+    },
+    {
+      // annotation instructions
+      SpvOpDecorate,
+      SpvOpMemberDecorate,
+      SpvOpGroupDecorate,
+      SpvOpGroupMemberDecorate,
+      SpvOpDecorationGroup
+    },
+    {
+      // All type and constant instructions
+      SpvOpTypeVoid,
+      SpvOpTypeBool,
+      SpvOpTypeInt,
+      SpvOpTypeFloat,
+      SpvOpTypeVector,
+      SpvOpTypeMatrix,
+      SpvOpTypeImage,
+      SpvOpTypeSampler,
+      SpvOpTypeSampledImage,
+      SpvOpTypeArray,
+      SpvOpTypeRuntimeArray,
+      SpvOpTypeStruct,
+      SpvOpTypeOpaque,
+      SpvOpTypePointer,
+      SpvOpTypeFunction,
+      SpvOpTypeEvent,
+      SpvOpTypeDeviceEvent,
+      SpvOpTypeReserveId,
+      SpvOpTypeQueue,
+      SpvOpTypePipe,
+      SpvOpTypeForwardPointer,
+      SpvOpConstantTrue,
+      SpvOpConstantFalse,
+      SpvOpConstant,
+      SpvOpConstantComposite,
+      SpvOpConstantSampler,
+      SpvOpConstantNull,
+      SpvOpSpecConstantTrue,
+      SpvOpSpecConstantFalse,
+      SpvOpSpecConstant,
+      SpvOpSpecConstantComposite,
+      SpvOpSpecConstantOp,
+      SpvOpVariable,
+      SpvOpLine
+    }
+  };
+  // clang-format on
+
+  return moduleOrder;
+}
+}
 
 namespace libspirv {
 
 ValidationState_t::ValidationState_t(spv_diagnostic* diag, uint32_t options)
-    : diagnostic_(diag), instruction_counter_(0), validation_flags_(options) {}
+    : diagnostic_(diag),
+      instruction_counter_(0),
+      defined_ids_{},
+      unresolved_forward_ids_{},
+      validation_flags_(options),
+      operand_names_{},
+      module_layout_order_stage_(0),
+      current_layout_stage_(ModuleLayoutSection::kModule) {}
 
 spv_result_t ValidationState_t::defineId(uint32_t id) {
   if (defined_ids_.find(id) == end(defined_ids_)) {
@@ -82,7 +167,6 @@ vector<uint32_t> ValidationState_t::unresolvedForwardIds() const {
   return out;
 }
 
-//
 bool ValidationState_t::isDefinedId(uint32_t id) const {
   return defined_ids_.find(id) != end(defined_ids_);
 }
@@ -96,6 +180,24 @@ int ValidationState_t::incrementInstructionCount() {
   return instruction_counter_++;
 }
 
+ModuleLayoutSection ValidationState_t::getLayoutStage() const {
+  return current_layout_stage_;
+}
+
+void ValidationState_t::progressToNextLayoutStageOrder() {
+  module_layout_order_stage_ +=
+      module_layout_order_stage_ < GetModuleOrder().size();
+  if (module_layout_order_stage_ >= GetModuleOrder().size()) {
+    current_layout_stage_ = libspirv::ModuleLayoutSection::kFunction;
+  }
+}
+
+bool ValidationState_t::isOpcodeInCurrentLayoutStage(SpvOp op) {
+  const vector<SpvOp>& currentStage =
+      GetModuleOrder()[module_layout_order_stage_];
+  return end(currentStage) != find(begin(currentStage), end(currentStage), op);
+}
+
 libspirv::DiagnosticStream ValidationState_t::diag(
     spv_result_t error_code) const {
   return libspirv::DiagnosticStream(
index add6b29..d9e31e3 100644 (file)
 
 namespace libspirv {
 
+// This enum represents the sections of a SPIRV module. The MODULE section
+// contains instructions who's scope spans the entire module. The FUNCTION
+// section includes SPIRV function and function definitions
+enum class ModuleLayoutSection {
+  kModule,    // < Module scope instructions are executed
+  kFunction,  // < Function scope instructions are executed
+};
+
 class ValidationState_t {
  public:
   ValidationState_t(spv_diagnostic* diag, uint32_t options);
@@ -75,6 +83,15 @@ class ValidationState_t {
   // Increments the instruction count. Used for diagnostic
   int incrementInstructionCount();
 
+  // Returns the current layout section which is being processed
+  ModuleLayoutSection getLayoutStage() const;
+
+  // Increments the module_layout_order_stage_
+  void progressToNextLayoutStageOrder();
+
+  // Determines if the op instruction is part of the current stage
+  bool isOpcodeInCurrentLayoutStage(SpvOp op);
+
   libspirv::DiagnosticStream diag(spv_result_t error_code) const;
 
  private:
@@ -92,6 +109,13 @@ class ValidationState_t {
   uint32_t validation_flags_;
 
   std::map<uint32_t, std::string> operand_names_;
+
+  // The stage which is being processed by the validation. Partially based on
+  // Section 2.4. Logical Layout of a Module
+  uint8_t module_layout_order_stage_;
+
+  // The section of the code being processed
+  ModuleLayoutSection current_layout_stage_;
 };
 }
 
diff --git a/test/Validate.Layout.cpp b/test/Validate.Layout.cpp
new file mode 100644 (file)
index 0000000..7c82ada
--- /dev/null
@@ -0,0 +1,224 @@
+// 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 Logical Layout
+
+#include "gmock/gmock.h"
+#include "UnitSPIRV.h"
+#include "ValidateFixtures.h"
+
+#include <functional>
+#include <sstream>
+#include <string>
+#include <utility>
+
+using std::function;
+using std::ostream_iterator;
+using std::pair;
+using std::stringstream;
+using std::string;
+using std::tie;
+using std::tuple;
+using std::vector;
+
+using ::testing::HasSubstr;
+
+using pred_type = function<bool(int)>;
+using ValidateLayout =
+    spvtest::ValidateBase<tuple<int, tuple<string, pred_type, pred_type>>,
+                          SPV_VALIDATE_LAYOUT_BIT>;
+
+namespace {
+
+// returns true if order is equal to VAL
+template <int VAL>
+bool Equals(int order) {
+  return order == VAL;
+}
+
+// returns true if order is between MIN and MAX(inclusive)
+template <int MIN, int MAX>
+struct Range {
+  bool operator()(int order) { return order >= MIN && order <= MAX; }
+};
+
+template <typename... T>
+bool RangeSet(int order) {
+  for (bool val : {T()(order)...})
+    if (!val) return val;
+  return false;
+}
+
+// SPIRV source used to test the logical layout
+const vector<string>& getInstructions() {
+  // clang-format off
+  static const vector<string> instructions = {
+    "OpCapability Matrix",
+    "OpExtension \"TestExtension\"",
+    "%inst = OpExtInstImport \"GLSL.std.450\"",
+    "OpMemoryModel Logical GLSL450",
+    "OpEntryPoint GLCompute %func \"\"",
+    "OpExecutionMode %func LocalSize 1 1 1",
+    "%str = OpString \"Test String\"",
+    "OpSource GLSL 450 %str \"uniform vec3 var = vec3(4.0);\"",
+    "OpSourceContinued \"void main(){return;}\"",
+    "OpSourceExtension \"Test extension\"",
+    "OpName %id \"MyID\"",
+    "OpMemberName %struct 1 \"my_member\"",
+    "OpDecorate %dgrp RowMajor",
+    "OpMemberDecorate %struct 1 RowMajor",
+    "%dgrp   = OpDecorationGroup",
+    "OpGroupDecorate %dgrp %mat33 %mat44",
+    "%intt   =  OpTypeInt 32 1",
+    "%floatt =  OpTypeFloat 32",
+    "%voidt  =  OpTypeVoid",
+    "%boolt  =  OpTypeBool",
+    "%vec4   =  OpTypeVector %intt 4",
+    "%vec3   =  OpTypeVector %intt 3",
+    "%mat33  =  OpTypeMatrix %vec3 3",
+    "%mat44  =  OpTypeMatrix %vec4 4",
+    "%struct =  OpTypeStruct %intt %mat33",
+    "%vfunct = OpTypeFunction %voidt",
+    "%viifunct =  OpTypeFunction %voidt %intt %intt",
+    "%one      =  OpConstant %intt 1",
+    // TODO(umar): OpConstant fails because the type is not defined
+    // TODO(umar): OpGroupMemberDecorate
+    "OpLine %str 3 4",
+    "%func   = OpFunction %voidt None %vfunct",
+    "OpFunctionEnd",
+    "%func2   = OpFunction %voidt None %viifunct",
+    "%funcp1 = OpFunctionParameter %intt",
+    "%funcp2 = OpFunctionParameter %intt",
+    "%fLabel = OpLabel",
+    "          OpNop",
+    "OpReturn",
+    "OpFunctionEnd"
+  };
+  return instructions;
+}
+
+pred_type All = Range<0, 1000>();
+
+INSTANTIATE_TEST_CASE_P(InstructionsOrder,
+    ValidateLayout,
+    ::testing::Combine(::testing::Range((int)0, (int)getInstructions().size()),
+    //                                   | Instruction              | Line(s) valid     | Lines to compile
+    ::testing::Values( make_tuple( string("OpCapability")           , Equals<0>         , All)
+                     , make_tuple(string("OpExtension")             , Equals<1>         , All)
+                     , make_tuple(string("OpExtInstImport")         , Equals<2>         , All)
+                     , make_tuple(string("OpMemoryModel")           , Equals<3>         , All)
+                     , make_tuple(string("OpEntryPoint")            , Equals<4>         , All)
+                     , make_tuple(string("OpExecutionMode")         , Equals<5>         , All)
+                     , make_tuple(string("OpSource ")               , Range<6, 9>()     , All)
+                     , make_tuple(string("OpSourceContinued ")      , Range<6, 9>()     , All)
+                     , make_tuple(string("OpSourceExtension ")      , Range<6, 9>()     , All)
+                     , make_tuple(string("OpString ")               , Range<6, 9>()     , All)
+                     , make_tuple(string("OpName ")                 , Range<10, 11>()   , All)
+                     , make_tuple(string("OpMemberName ")           , Range<10, 11>()   , All)
+                     , make_tuple(string("OpDecorate ")             , Range<12, 15>()   , All)
+                     , make_tuple(string("OpMemberDecorate ")       , Range<12, 15>()   , All)
+                     , make_tuple(string("OpGroupDecorate ")        , Range<12, 15>()   , All)
+                     , make_tuple(string("OpDecorationGroup")       , Range<12, 15>()   , All)
+                     , make_tuple(string("OpTypeBool")              , Range<16, 28>()   , All)
+                     , make_tuple(string("OpTypeVoid")              , Range<16, 28>()   , All)
+                     , make_tuple(string("OpTypeFloat")             , Range<16, 28>()   , All)
+                     , make_tuple(string("OpTypeInt")               , Range<16, 28>()   , static_cast<pred_type>(Range<0, 25>()))
+                     , make_tuple(string("OpTypeVector %intt 4")    , Range<16, 28>()   , All)
+                     , make_tuple(string("OpTypeMatrix %vec4 4")    , Range<16, 28>()   , All)
+                     , make_tuple(string("OpTypeStruct")            , Range<16, 28>()   , All)
+                     , make_tuple(string("%vfunct = OpTypeFunction"), Range<16, 28>()   , All)
+                     , make_tuple(string("OpConstant")              , Range<19, 28>()   , static_cast<pred_type>(Range<19, 100>()))
+                   //, make_tuple(string("OpLabel")                 , RangeSet<Range<29,31>, Range<35, 36>, >   , All)
+    )));
+// clang-format on
+
+// Creates a new vector which removes the string if the substr is found in the
+// instructions vector and reinserts it in the location specified by order.
+// NOTE: This will not work correctly if there are two instances of substr in
+// instructions
+vector<string> GenerateCode(string substr, int order) {
+  vector<string> code(getInstructions().size());
+  vector<string> inst(1);
+  partition_copy(begin(getInstructions()), end(getInstructions()), begin(code),
+                 begin(inst), [=](const string& str) {
+                   return string::npos == str.find(substr);
+                 });
+
+  code.insert(begin(code) + order, inst.front());
+  return code;
+}
+
+// This test will check the logical layout of a binary by removing each
+// instruction in the pair of the INSTANTIATE_TEST_CASE_P call and moving it in
+// the SPIRV source formed by combining the vector "instructions"
+//
+// NOTE: The test will only execute with the SPV_VALIDATE_LAYOUT_BIT flag so SSA
+// and other tests are not performed
+TEST_P(ValidateLayout, Layout) {
+  int order;
+  string instruction;
+  pred_type pred;
+  pred_type test_pred;  // Predicate to determine if the test should be build
+  tuple<string, pred_type, pred_type> testCase;
+
+  tie(order, testCase) = GetParam();
+  tie(instruction, pred, test_pred) = testCase;
+
+  // Skip test which break the code generation
+  if (!test_pred(order)) return;
+
+  vector<string> code = GenerateCode(instruction, order);
+
+  stringstream ss;
+  copy(begin(code), end(code), ostream_iterator<string>(ss, "\n"));
+
+  // printf("code: \n%s\n", ss.str().c_str());
+  CompileSuccessfully(ss.str());
+  if (pred(order)) {
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions())
+        << "Order: " << order << "\nInstruction: " << instruction;
+  } else {
+    ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions())
+        << "Order: " << order << "\nInstruction: " << instruction;
+  }
+}
+
+TEST_F(ValidateLayout, DISABLED_MemoryModelMissing) {
+  string str = R"(
+    OpCapability Matrix
+    OpExtension "TestExtension"
+    %inst = OpExtInstImport "GLSL.std.450"
+    OpEntryPoint GLCompute %func ""
+    OpExecutionMode %func LocalSize 1 1 1
+    )";
+
+  CompileSuccessfully(str);
+  ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions());
+}
+
+// TODO(umar): Test optional instructions
+// TODO(umar): Test logical layout of functions
+}
index e0a6ebc..ab29643 100644 (file)
@@ -43,7 +43,8 @@ using std::stringstream;
 namespace {
 
 using Validate =
-    spvtest::ValidateBase<pair<string, bool>, SPV_VALIDATE_SSA_BIT>;
+    spvtest::ValidateBase<pair<string, bool>,
+                          SPV_VALIDATE_SSA_BIT | SPV_VALIDATE_LAYOUT_BIT>;
 
 TEST_F(Validate, Default) {
   char str[] = R"(
index 284fefe..0f34291 100644 (file)
 #include "UnitSPIRV.h"
 #include "ValidateFixtures.h"
 
+#include <functional>
+#include <utility>
+#include <tuple>
+
 namespace spvtest {
 
 template <typename T, uint32_t OPTIONS>
@@ -74,7 +78,11 @@ std::string ValidateBase<T, OPTIONS>::getDiagnosticString() {
 }
 
 template class spvtest::ValidateBase<std::pair<std::string, bool>,
-                                     SPV_VALIDATE_SSA_BIT>;
-template class spvtest::ValidateBase<bool,
-                                    SPV_VALIDATE_SSA_BIT>;
+                                     SPV_VALIDATE_SSA_BIT |
+                                         SPV_VALIDATE_LAYOUT_BIT>;
+template class spvtest::ValidateBase<bool, SPV_VALIDATE_SSA_BIT>;
+template class spvtest::ValidateBase<
+    std::tuple<int, std::tuple<std::string, std::function<bool(int)>,
+                               std::function<bool(int)>>>,
+    SPV_VALIDATE_LAYOUT_BIT>;
 }