Add validation for SPV_EXT_fragment_shader_interlock (#2650)

diff --git a/Android.mk b/Android.mk
index 06d8811..8e44378 100644
--- a/Android.mk
+++ b/Android.mk
@@ -62,6 +62,7 @@
 		source/val/validate_instruction.cpp \
 		source/val/validate_memory.cpp \
 		source/val/validate_memory_semantics.cpp \
+		source/val/validate_misc.cpp \
 		source/val/validate_mode_setting.cpp \
 		source/val/validate_layout.cpp \
 		source/val/validate_literals.cpp \
diff --git a/BUILD.gn b/BUILD.gn
index 5c6f311..fb9e8a4 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -422,6 +422,7 @@
     "source/val/validate_logicals.cpp",
     "source/val/validate_memory.cpp",
     "source/val/validate_memory_semantics.cpp",
+    "source/val/validate_misc.cpp",
     "source/val/validate_mode_setting.cpp",
     "source/val/validate_non_uniform.cpp",
     "source/val/validate_primitives.cpp",
diff --git a/DEPS b/DEPS
index 3c5361f..9a8e922 100644
--- a/DEPS
+++ b/DEPS
@@ -11,7 +11,7 @@
   'googletest_revision': '98a0d007d7092b72eea0e501bb9ad17908a1a036',
   'testing_revision': '340252637e2e7c72c0901dcbeeacfff419e19b59',
   're2_revision': '6cf8ccd82dbaab2668e9b13596c68183c9ecd13f',
-  'spirv_headers_revision': 'e74c389f81915d0a48d6df1af83c3862c5ad85ab',
+  'spirv_headers_revision': '8b911bd2ba37677037b38c9bd286c7c05701bcda',
 }
 
 deps = {
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index 93938f0..bd4c465 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -299,6 +299,7 @@
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_logicals.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_memory.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_memory_semantics.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_misc.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_mode_setting.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_non_uniform.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_primitives.cpp
diff --git a/source/val/function.cpp b/source/val/function.cpp
index 90893b3..af1498d 100644
--- a/source/val/function.cpp
+++ b/source/val/function.cpp
@@ -389,5 +389,29 @@
   return return_value;
 }
 
+bool Function::CheckLimitations(const ValidationState_t& _,
+                                const Function* entry_point,
+                                std::string* reason) const {
+  bool return_value = true;
+  std::stringstream ss_reason;
+
+  for (const auto& is_compatible : limitations_) {
+    std::string message;
+    if (!is_compatible(_, entry_point, &message)) {
+      if (!reason) return false;
+      return_value = false;
+      if (!message.empty()) {
+        ss_reason << message << "\n";
+      }
+    }
+  }
+
+  if (!return_value && reason) {
+    *reason = ss_reason.str();
+  }
+
+  return return_value;
+}
+
 }  // namespace val
 }  // namespace spvtools
diff --git a/source/val/function.h b/source/val/function.h
index 9cda2ff..0d6873d 100644
--- a/source/val/function.h
+++ b/source/val/function.h
@@ -216,6 +216,16 @@
     execution_model_limitations_.push_back(is_compatible);
   }
 
+  /// Registers limitation with an |is_compatible| functor.
+  void RegisterLimitation(std::function<bool(const ValidationState_t& _,
+                                             const Function*, std::string*)>
+                              is_compatible) {
+    limitations_.push_back(is_compatible);
+  }
+
+  bool CheckLimitations(const ValidationState_t& _, const Function* entry_point,
+                        std::string* reason) const;
+
   /// Returns true if the given execution model passes the limitations stored in
   /// execution_model_limitations_. Returns false otherwise and fills optional
   /// |reason| parameter.
@@ -376,6 +386,12 @@
   std::list<std::function<bool(SpvExecutionModel, std::string*)>>
       execution_model_limitations_;
 
+  /// Stores limitations imposed by instructions used within the function.
+  /// Similar to execution_model_limitations_;
+  std::list<std::function<bool(const ValidationState_t& _, const Function*,
+                               std::string*)>>
+      limitations_;
+
   /// Stores ids of all functions called from this function.
   std::set<uint32_t> function_call_targets_;
 };
diff --git a/source/val/validate.cpp b/source/val/validate.cpp
index 4a730da..0840662 100644
--- a/source/val/validate.cpp
+++ b/source/val/validate.cpp
@@ -366,7 +366,7 @@
 
     // Keep these passes in the order they appear in the SPIR-V specification
     // sections to maintain test consistency.
-    // Miscellaneous
+    if (auto error = MiscPass(*vstate, &instruction)) return error;
     if (auto error = DebugPass(*vstate, &instruction)) return error;
     if (auto error = AnnotationPass(*vstate, &instruction)) return error;
     if (auto error = ExtensionPass(*vstate, &instruction)) return error;
diff --git a/source/val/validate.h b/source/val/validate.h
index fe357a2..aaae570 100644
--- a/source/val/validate.h
+++ b/source/val/validate.h
@@ -199,6 +199,9 @@
 /// Validates correctness of function instructions.
 spv_result_t FunctionPass(ValidationState_t& _, const Instruction* inst);
 
+/// Validates correctness of miscellaneous instructions.
+spv_result_t MiscPass(ValidationState_t& _, const Instruction* inst);
+
 /// Validates execution limitations.
 ///
 /// Verifies execution models are allowed for all functionality they contain.
diff --git a/source/val/validate_builtins.cpp b/source/val/validate_builtins.cpp
index bcd691f..568d729 100644
--- a/source/val/validate_builtins.cpp
+++ b/source/val/validate_builtins.cpp
@@ -2810,7 +2810,11 @@
     case SpvBuiltInWorldToObjectNV:
     case SpvBuiltInHitTNV:
     case SpvBuiltInHitKindNV:
-    case SpvBuiltInIncomingRayFlagsNV: {
+    case SpvBuiltInIncomingRayFlagsNV:
+    case SpvBuiltInWarpsPerSMNV:
+    case SpvBuiltInSMCountNV:
+    case SpvBuiltInWarpIDNV:
+    case SpvBuiltInSMIDNV: {
       // No validation rules (for the moment).
       break;
     }
diff --git a/source/val/validate_execution_limitations.cpp b/source/val/validate_execution_limitations.cpp
index d449307..aac1c49 100644
--- a/source/val/validate_execution_limitations.cpp
+++ b/source/val/validate_execution_limitations.cpp
@@ -53,6 +53,17 @@
         }
       }
     }
+
+    std::string reason;
+    if (!func->CheckLimitations(_, _.function(entry_id), &reason)) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "OpEntryPoint Entry Point <id> '" << _.getIdName(entry_id)
+             << "'s callgraph contains function <id> "
+             << _.getIdName(inst->id())
+             << ", which cannot be used with the current execution "
+                "modes:\n"
+             << reason;
+    }
   }
   return SPV_SUCCESS;
 }
diff --git a/source/val/validate_misc.cpp b/source/val/validate_misc.cpp
new file mode 100644
index 0000000..dcc65f3
--- /dev/null
+++ b/source/val/validate_misc.cpp
@@ -0,0 +1,81 @@
+// Copyright (c) 2018 Google LLC.
+// Copyright (c) 2019 NVIDIA Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/val/validate.h"
+
+#include "source/opcode.h"
+#include "source/spirv_target_env.h"
+#include "source/val/instruction.h"
+#include "source/val/validation_state.h"
+
+namespace spvtools {
+namespace val {
+
+spv_result_t MiscPass(ValidationState_t& _, const Instruction* inst) {
+  switch (inst->opcode()) {
+    case SpvOpBeginInvocationInterlockEXT:
+    case SpvOpEndInvocationInterlockEXT:
+      _.function(inst->function()->id())
+          ->RegisterExecutionModelLimitation(
+              SpvExecutionModelFragment,
+              "OpBeginInvocationInterlockEXT/OpEndInvocationInterlockEXT "
+              "require Fragment execution model");
+
+      _.function(inst->function()->id())
+          ->RegisterLimitation([](const ValidationState_t& state,
+                                  const Function* entry_point,
+                                  std::string* message) {
+            const auto* execution_modes =
+                state.GetExecutionModes(entry_point->id());
+
+            auto find_interlock = [](const SpvExecutionMode& mode) {
+              switch (mode) {
+                case SpvExecutionModePixelInterlockOrderedEXT:
+                case SpvExecutionModePixelInterlockUnorderedEXT:
+                case SpvExecutionModeSampleInterlockOrderedEXT:
+                case SpvExecutionModeSampleInterlockUnorderedEXT:
+                case SpvExecutionModeShadingRateInterlockOrderedEXT:
+                case SpvExecutionModeShadingRateInterlockUnorderedEXT:
+                  return true;
+                default:
+                  return false;
+              }
+            };
+
+            bool found = false;
+            if (execution_modes) {
+              auto i = std::find_if(execution_modes->begin(),
+                                    execution_modes->end(), find_interlock);
+              found = (i != execution_modes->end());
+            }
+
+            if (!found) {
+              *message =
+                  "OpBeginInvocationInterlockEXT/OpEndInvocationInterlockEXT "
+                  "require a fragment shader interlock execution mode.";
+              return false;
+            }
+            return true;
+          });
+      break;
+    default:
+      break;
+  }
+
+  return SPV_SUCCESS;
+}
+
+}  // namespace val
+}  // namespace spvtools
diff --git a/source/val/validate_mode_setting.cpp b/source/val/validate_mode_setting.cpp
index 943629d..cbcf11a 100644
--- a/source/val/validate_mode_setting.cpp
+++ b/source/val/validate_mode_setting.cpp
@@ -90,6 +90,26 @@
                     "one of DepthGreater, DepthLess or DepthUnchanged "
                     "execution modes.";
         }
+        if (execution_modes &&
+            1 < std::count_if(
+                    execution_modes->begin(), execution_modes->end(),
+                    [](const SpvExecutionMode& mode) {
+                      switch (mode) {
+                        case SpvExecutionModePixelInterlockOrderedEXT:
+                        case SpvExecutionModePixelInterlockUnorderedEXT:
+                        case SpvExecutionModeSampleInterlockOrderedEXT:
+                        case SpvExecutionModeSampleInterlockUnorderedEXT:
+                        case SpvExecutionModeShadingRateInterlockOrderedEXT:
+                        case SpvExecutionModeShadingRateInterlockUnorderedEXT:
+                          return true;
+                        default:
+                          return false;
+                      }
+                    })) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Fragment execution model entry points can specify at most "
+                    "one fragment shader interlock execution mode.";
+        }
         break;
       case SpvExecutionModelTessellationControl:
       case SpvExecutionModelTessellationEvaluation:
@@ -376,6 +396,12 @@
     case SpvExecutionModeDepthGreater:
     case SpvExecutionModeDepthLess:
     case SpvExecutionModeDepthUnchanged:
+    case SpvExecutionModePixelInterlockOrderedEXT:
+    case SpvExecutionModePixelInterlockUnorderedEXT:
+    case SpvExecutionModeSampleInterlockOrderedEXT:
+    case SpvExecutionModeSampleInterlockUnorderedEXT:
+    case SpvExecutionModeShadingRateInterlockOrderedEXT:
+    case SpvExecutionModeShadingRateInterlockUnorderedEXT:
       if (!std::all_of(models->begin(), models->end(),
                        [](const SpvExecutionModel& model) {
                          return model == SpvExecutionModelFragment;
diff --git a/test/val/val_modes_test.cpp b/test/val/val_modes_test.cpp
index bc157a4..c5b1a37 100644
--- a/test/val/val_modes_test.cpp
+++ b/test/val/val_modes_test.cpp
@@ -1002,6 +1002,105 @@
                         "constant instructions."));
 }
 
+TEST_F(ValidateMode, FragmentShaderInterlockVertexBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability FragmentShaderPixelInterlockEXT
+OpExtension "SPV_EXT_fragment_shader_interlock"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpExecutionMode %main PixelInterlockOrderedEXT
+)" + kVoidFunction;
+
+  CompileSuccessfully(spirv);
+  EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Execution mode can only be used with the Fragment execution model"));
+}
+
+TEST_F(ValidateMode, FragmentShaderInterlockTooManyModesBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability FragmentShaderPixelInterlockEXT
+OpCapability FragmentShaderSampleInterlockEXT
+OpExtension "SPV_EXT_fragment_shader_interlock"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpExecutionMode %main PixelInterlockOrderedEXT
+OpExecutionMode %main SampleInterlockOrderedEXT
+)" + kVoidFunction;
+
+  CompileSuccessfully(spirv);
+  EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Fragment execution model entry points can specify at most "
+                "one fragment shader interlock execution mode"));
+}
+
+TEST_F(ValidateMode, FragmentShaderInterlockNoModeBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability FragmentShaderPixelInterlockEXT
+OpExtension "SPV_EXT_fragment_shader_interlock"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entryf = OpLabel
+OpBeginInvocationInterlockEXT
+OpEndInvocationInterlockEXT
+OpReturn
+OpFunctionEnd
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%1 = OpFunctionCall %void %func
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_THAT(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpBeginInvocationInterlockEXT/OpEndInvocationInterlockEXT require a "
+          "fragment shader interlock execution mode"));
+}
+
+TEST_F(ValidateMode, FragmentShaderInterlockGood) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability FragmentShaderPixelInterlockEXT
+OpExtension "SPV_EXT_fragment_shader_interlock"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpExecutionMode %main PixelInterlockOrderedEXT
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entryf = OpLabel
+OpBeginInvocationInterlockEXT
+OpEndInvocationInterlockEXT
+OpReturn
+OpFunctionEnd
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%1 = OpFunctionCall %void %func
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_THAT(SPV_SUCCESS, ValidateInstructions());
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools