Add builtin validation for SPV_NV_shader_sm_builtins (#2656)

Also add a Builtin test generator variant that takes
capabilities and extensions.

Tests
 - verify that the SMCountNV, SMIDNV, WarpsPerSMNV, and WarpIDNV Builtins are
   accepted as Inputs in Vertex, Fragment, TessControl, TessEval, Geometry,
   and Compute.
 - verify that the SMCountNV, SMIDNV, WarpsPerSMNV, and WarpIDNV Builtins are
   accepted as Inputs in MeshNV and TaskNV shaders.
 - verify that the SMCountNV, SMIDNV, WarpsPerSMNV, and WarpIDNV Builtins are
   accepted as Inputs in the 6 ray tracing stages
 - verify that the SMCountNV, SMIDNV, WarpsPerSMNV, and WarpIDNV Builtins are
   NOT accepted as Outputs.
 - verify that the SMCountNV, SMIDNV, WarpsPerSMNV, and WarpIDNV Builtins are
   NOT accepted as non-scalar integers (f32, uvec3)
 - verify that the SMCountNV, SMIDNV, WarpsPerSMNV, and WarpIDNV Builtins are
   NOT accepted as non-32-bit integers (u64)
diff --git a/.appveyor.yml b/.appveyor.yml
index 196f545..77cd89c 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -63,7 +63,7 @@
   - ninja install
 
 test_script:
-  - ctest -C %CONFIGURATION% --output-on-failure --timeout 300
+  - ctest -C %CONFIGURATION% --output-on-failure --timeout 310
 
 after_test:
   # Zip build artifacts for uploading and deploying
diff --git a/source/val/validate_builtins.cpp b/source/val/validate_builtins.cpp
index 568d729..0656065 100644
--- a/source/val/validate_builtins.cpp
+++ b/source/val/validate_builtins.cpp
@@ -208,6 +208,8 @@
   // Used for GlobalInvocationId, LocalInvocationId, NumWorkgroups, WorkgroupId.
   spv_result_t ValidateComputeShaderI32Vec3InputAtDefinition(
       const Decoration& decoration, const Instruction& inst);
+  spv_result_t ValidateSMBuiltinsAtDefinition(const Decoration& decoration,
+                                              const Instruction& inst);
 
   // The following section contains functions which are called when id defined
   // by |referenced_inst| is
@@ -332,6 +334,11 @@
       const Instruction& referenced_inst,
       const Instruction& referenced_from_inst);
 
+  spv_result_t ValidateSMBuiltinsAtReference(
+      const Decoration& decoration, const Instruction& built_in_inst,
+      const Instruction& referenced_inst,
+      const Instruction& referenced_from_inst);
+
   // Validates that |built_in_inst| is not (even indirectly) referenced from
   // within a function which can be called with |execution_model|.
   //
@@ -2654,6 +2661,61 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t BuiltInsValidator::ValidateSMBuiltinsAtDefinition(
+    const Decoration& decoration, const Instruction& inst) {
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (spv_result_t error = ValidateI32(
+            decoration, inst,
+            [this, &inst,
+             &decoration](const std::string& message) -> spv_result_t {
+              return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << "According to the "
+                     << spvLogStringForEnv(_.context()->target_env)
+                     << " spec BuiltIn "
+                     << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                      decoration.params()[0])
+                     << " variable needs to be a 32-bit int scalar. "
+                     << message;
+            })) {
+      return error;
+    }
+  }
+
+  // Seed at reference checks with this built-in.
+  return ValidateSMBuiltinsAtReference(decoration, inst, inst, inst);
+}
+
+spv_result_t BuiltInsValidator::ValidateSMBuiltinsAtReference(
+    const Decoration& decoration, const Instruction& built_in_inst,
+    const Instruction& referenced_inst,
+    const Instruction& referenced_from_inst) {
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
+    if (storage_class != SpvStorageClassMax &&
+        storage_class != SpvStorageClassInput) {
+      return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << spvLogStringForEnv(_.context()->target_env)
+             << " spec allows BuiltIn "
+             << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                              decoration.params()[0])
+             << " to be only used for "
+                "variables with Input storage class. "
+             << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                 referenced_from_inst)
+             << " " << GetStorageClassDesc(referenced_from_inst);
+    }
+  }
+
+  if (function_id_ == 0) {
+    // Propagate this rule to all dependant ids in the global scope.
+    id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind(
+        &BuiltInsValidator::ValidateSMBuiltinsAtReference, this, decoration,
+        built_in_inst, referenced_from_inst, std::placeholders::_1));
+  }
+
+  return SPV_SUCCESS;
+}
+
 spv_result_t BuiltInsValidator::ValidateSingleBuiltInAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
   const SpvBuiltIn label = SpvBuiltIn(decoration.params()[0]);
@@ -2748,6 +2810,12 @@
     case SpvBuiltInLocalInvocationIndex: {
       return ValidateLocalInvocationIndexAtDefinition(decoration, inst);
     }
+    case SpvBuiltInWarpsPerSMNV:
+    case SpvBuiltInSMCountNV:
+    case SpvBuiltInWarpIDNV:
+    case SpvBuiltInSMIDNV: {
+      return ValidateSMBuiltinsAtDefinition(decoration, inst);
+    }
     case SpvBuiltInWorkDim:
     case SpvBuiltInGlobalSize:
     case SpvBuiltInEnqueuedWorkgroupSize:
@@ -2810,11 +2878,7 @@
     case SpvBuiltInWorldToObjectNV:
     case SpvBuiltInHitTNV:
     case SpvBuiltInHitKindNV:
-    case SpvBuiltInIncomingRayFlagsNV:
-    case SpvBuiltInWarpsPerSMNV:
-    case SpvBuiltInSMCountNV:
-    case SpvBuiltInWarpIDNV:
-    case SpvBuiltInSMIDNV: {
+    case SpvBuiltInIncomingRayFlagsNV: {
       // No validation rules (for the moment).
       break;
     }
diff --git a/test/val/val_builtins_test.cpp b/test/val/val_builtins_test.cpp
index cff9d4a..df048ad 100644
--- a/test/val/val_builtins_test.cpp
+++ b/test/val/val_builtins_test.cpp
@@ -62,7 +62,10 @@
     std::tuple<const char*, const char*, const char*, const char*, TestResult>>;
 using ValidateWebGPUCombineBuiltInArrayedVariable = spvtest::ValidateBase<
     std::tuple<const char*, const char*, const char*, const char*, TestResult>>;
-
+using ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult =
+    spvtest::ValidateBase<
+        std::tuple<const char*, const char*, const char*, const char*,
+                   const char*, const char*, TestResult>>;
 
 bool InitializerRequired(spv_target_env env, const char* const storage_class) {
   return spvIsWebGPUEnv(env) && (strncmp(storage_class, "Output", 6) == 0 ||
@@ -74,11 +77,20 @@
                                      const char* const built_in,
                                      const char* const execution_model,
                                      const char* const storage_class,
+                                     const char* const capabilities,
+                                     const char* const extensions,
                                      const char* const data_type) {
   CodeGenerator generator =
       spvIsWebGPUEnv(env) ? CodeGenerator::GetWebGPUShaderCodeGenerator()
                           : CodeGenerator::GetDefaultShaderCodeGenerator();
 
+  if (capabilities) {
+    generator.capabilities_ += capabilities;
+  }
+  if (extensions) {
+    generator.extensions_ += extensions;
+  }
+
   generator.before_types_ = "OpMemberDecorate %built_in_type 0 BuiltIn ";
   generator.before_types_ += built_in;
   generator.before_types_ += "\n";
@@ -144,8 +156,9 @@
   const char* const data_type = std::get<3>(GetParam());
   const TestResult& test_result = std::get<4>(GetParam());
 
-  CodeGenerator generator = GetInMainCodeGenerator(
-      SPV_ENV_VULKAN_1_0, built_in, execution_model, storage_class, data_type);
+  CodeGenerator generator =
+      GetInMainCodeGenerator(SPV_ENV_VULKAN_1_0, built_in, execution_model,
+                             storage_class, NULL, NULL, data_type);
 
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(test_result.validation_result,
@@ -165,8 +178,9 @@
   const char* const data_type = std::get<3>(GetParam());
   const TestResult& test_result = std::get<4>(GetParam());
 
-  CodeGenerator generator = GetInMainCodeGenerator(
-      SPV_ENV_WEBGPU_0, built_in, execution_model, storage_class, data_type);
+  CodeGenerator generator =
+      GetInMainCodeGenerator(SPV_ENV_WEBGPU_0, built_in, execution_model,
+                             storage_class, NULL, NULL, data_type);
 
   CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
   ASSERT_EQ(test_result.validation_result,
@@ -179,15 +193,50 @@
   }
 }
 
+TEST_P(
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    InMain) {
+  const char* const built_in = std::get<0>(GetParam());
+  const char* const execution_model = std::get<1>(GetParam());
+  const char* const storage_class = std::get<2>(GetParam());
+  const char* const data_type = std::get<3>(GetParam());
+  const char* const capabilities = std::get<4>(GetParam());
+  const char* const extensions = std::get<5>(GetParam());
+  const TestResult& test_result = std::get<6>(GetParam());
+
+  CodeGenerator generator = GetInMainCodeGenerator(
+      SPV_ENV_VULKAN_1_0, built_in, execution_model, storage_class,
+      capabilities, extensions, data_type);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(test_result.validation_result,
+            ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  if (test_result.error_str) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str));
+  }
+  if (test_result.error_str2) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
+  }
+}
+
 CodeGenerator GetInFunctionCodeGenerator(spv_target_env env,
                                          const char* const built_in,
                                          const char* const execution_model,
                                          const char* const storage_class,
+                                         const char* const capabilities,
+                                         const char* const extensions,
                                          const char* const data_type) {
   CodeGenerator generator =
       spvIsWebGPUEnv(env) ? CodeGenerator::GetWebGPUShaderCodeGenerator()
                           : CodeGenerator::GetDefaultShaderCodeGenerator();
 
+  if (capabilities) {
+    generator.capabilities_ += capabilities;
+  }
+  if (extensions) {
+    generator.extensions_ += extensions;
+  }
+
   generator.before_types_ = "OpMemberDecorate %built_in_type 0 BuiltIn ";
   generator.before_types_ += built_in;
   generator.before_types_ += "\n";
@@ -267,8 +316,9 @@
   const char* const data_type = std::get<3>(GetParam());
   const TestResult& test_result = std::get<4>(GetParam());
 
-  CodeGenerator generator = GetInFunctionCodeGenerator(
-      SPV_ENV_VULKAN_1_0, built_in, execution_model, storage_class, data_type);
+  CodeGenerator generator =
+      GetInFunctionCodeGenerator(SPV_ENV_VULKAN_1_0, built_in, execution_model,
+                                 storage_class, NULL, NULL, data_type);
 
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(test_result.validation_result,
@@ -288,8 +338,9 @@
   const char* const data_type = std::get<3>(GetParam());
   const TestResult& test_result = std::get<4>(GetParam());
 
-  CodeGenerator generator = GetInFunctionCodeGenerator(
-      SPV_ENV_WEBGPU_0, built_in, execution_model, storage_class, data_type);
+  CodeGenerator generator =
+      GetInFunctionCodeGenerator(SPV_ENV_WEBGPU_0, built_in, execution_model,
+                                 storage_class, NULL, NULL, data_type);
 
   CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
   ASSERT_EQ(test_result.validation_result,
@@ -302,15 +353,50 @@
   }
 }
 
+TEST_P(
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    InFunction) {
+  const char* const built_in = std::get<0>(GetParam());
+  const char* const execution_model = std::get<1>(GetParam());
+  const char* const storage_class = std::get<2>(GetParam());
+  const char* const data_type = std::get<3>(GetParam());
+  const char* const capabilities = std::get<4>(GetParam());
+  const char* const extensions = std::get<5>(GetParam());
+  const TestResult& test_result = std::get<6>(GetParam());
+
+  CodeGenerator generator = GetInFunctionCodeGenerator(
+      SPV_ENV_VULKAN_1_0, built_in, execution_model, storage_class,
+      capabilities, extensions, data_type);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(test_result.validation_result,
+            ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  if (test_result.error_str) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str));
+  }
+  if (test_result.error_str2) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
+  }
+}
+
 CodeGenerator GetVariableCodeGenerator(spv_target_env env,
                                        const char* const built_in,
                                        const char* const execution_model,
                                        const char* const storage_class,
+                                       const char* const capabilities,
+                                       const char* const extensions,
                                        const char* const data_type) {
   CodeGenerator generator =
       spvIsWebGPUEnv(env) ? CodeGenerator::GetWebGPUShaderCodeGenerator()
                           : CodeGenerator::GetDefaultShaderCodeGenerator();
 
+  if (capabilities) {
+    generator.capabilities_ += capabilities;
+  }
+  if (extensions) {
+    generator.extensions_ += extensions;
+  }
+
   generator.before_types_ = "OpDecorate %built_in_var BuiltIn ";
   generator.before_types_ += built_in;
   generator.before_types_ += "\n";
@@ -373,8 +459,9 @@
   const char* const data_type = std::get<3>(GetParam());
   const TestResult& test_result = std::get<4>(GetParam());
 
-  CodeGenerator generator = GetVariableCodeGenerator(
-      SPV_ENV_VULKAN_1_0, built_in, execution_model, storage_class, data_type);
+  CodeGenerator generator =
+      GetVariableCodeGenerator(SPV_ENV_VULKAN_1_0, built_in, execution_model,
+                               storage_class, NULL, NULL, data_type);
 
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(test_result.validation_result,
@@ -394,8 +481,9 @@
   const char* const data_type = std::get<3>(GetParam());
   const TestResult& test_result = std::get<4>(GetParam());
 
-  CodeGenerator generator = GetVariableCodeGenerator(
-      SPV_ENV_WEBGPU_0, built_in, execution_model, storage_class, data_type);
+  CodeGenerator generator =
+      GetVariableCodeGenerator(SPV_ENV_WEBGPU_0, built_in, execution_model,
+                               storage_class, NULL, NULL, data_type);
 
   CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
   ASSERT_EQ(test_result.validation_result,
@@ -408,6 +496,32 @@
   }
 }
 
+TEST_P(
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Variable) {
+  const char* const built_in = std::get<0>(GetParam());
+  const char* const execution_model = std::get<1>(GetParam());
+  const char* const storage_class = std::get<2>(GetParam());
+  const char* const data_type = std::get<3>(GetParam());
+  const char* const capabilities = std::get<4>(GetParam());
+  const char* const extensions = std::get<5>(GetParam());
+  const TestResult& test_result = std::get<6>(GetParam());
+
+  CodeGenerator generator = GetVariableCodeGenerator(
+      SPV_ENV_VULKAN_1_0, built_in, execution_model, storage_class,
+      capabilities, extensions, data_type);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(test_result.validation_result,
+            ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  if (test_result.error_str) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str));
+  }
+  if (test_result.error_str2) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
+  }
+}
+
 INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceOutputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
@@ -2028,6 +2142,81 @@
                               "needs to be a 32-bit float array",
                               "components are not float scalar"))));
 
+INSTANTIATE_TEST_SUITE_P(
+    SMBuiltinsInputSuccess,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("SMCountNV", "SMIDNV", "WarpsPerSMNV", "WarpIDNV"),
+            Values("Vertex", "Fragment", "TessellationControl",
+                   "TessellationEvaluation", "Geometry", "GLCompute"),
+            Values("Input"), Values("%u32"),
+            Values("OpCapability ShaderSMBuiltinsNV\n"),
+            Values("OpExtension \"SPV_NV_shader_sm_builtins\"\n"),
+            Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    SMBuiltinsInputMeshSuccess,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(
+        Values("SMCountNV", "SMIDNV", "WarpsPerSMNV", "WarpIDNV"),
+        Values("MeshNV", "TaskNV"), Values("Input"), Values("%u32"),
+        Values("OpCapability ShaderSMBuiltinsNV\nOpCapability MeshShadingNV\n"),
+        Values("OpExtension \"SPV_NV_shader_sm_builtins\"\nOpExtension "
+               "\"SPV_NV_mesh_shader\"\n"),
+        Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    SMBuiltinsInputRaySuccess,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(
+        Values("SMCountNV", "SMIDNV", "WarpsPerSMNV", "WarpIDNV"),
+        Values("RayGenerationNV", "IntersectionNV", "AnyHitNV", "ClosestHitNV",
+               "MissNV", "CallableNV"),
+        Values("Input"), Values("%u32"),
+        Values("OpCapability ShaderSMBuiltinsNV\nOpCapability RayTracingNV\n"),
+        Values("OpExtension \"SPV_NV_shader_sm_builtins\"\nOpExtension "
+               "\"SPV_NV_ray_tracing\"\n"),
+        Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    SMBuiltinsNotInput,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("SMCountNV", "SMIDNV", "WarpsPerSMNV", "WarpIDNV"),
+            Values("Vertex", "Fragment", "TessellationControl",
+                   "TessellationEvaluation", "Geometry", "GLCompute"),
+            Values("Output"), Values("%u32"),
+            Values("OpCapability ShaderSMBuiltinsNV\n"),
+            Values("OpExtension \"SPV_NV_shader_sm_builtins\"\n"),
+            Values(TestResult(
+                SPV_ERROR_INVALID_DATA,
+                "to be only used for variables with Input storage class",
+                "uses storage class Output"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    SMBuiltinsNotIntScalar,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("SMCountNV", "SMIDNV", "WarpsPerSMNV", "WarpIDNV"),
+            Values("Vertex", "Fragment", "TessellationControl",
+                   "TessellationEvaluation", "Geometry", "GLCompute"),
+            Values("Input"), Values("%f32", "%u32vec3"),
+            Values("OpCapability ShaderSMBuiltinsNV\n"),
+            Values("OpExtension \"SPV_NV_shader_sm_builtins\"\n"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 32-bit int scalar",
+                              "is not an int scalar"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    SMBuiltinsNotInt32,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("SMCountNV", "SMIDNV", "WarpsPerSMNV", "WarpIDNV"),
+            Values("Vertex", "Fragment", "TessellationControl",
+                   "TessellationEvaluation", "Geometry", "GLCompute"),
+            Values("Input"), Values("%u64"),
+            Values("OpCapability ShaderSMBuiltinsNV\n"),
+            Values("OpExtension \"SPV_NV_shader_sm_builtins\"\n"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 32-bit int scalar",
+                              "has bit width 64"))));
+
 CodeGenerator GetWorkgroupSizeSuccessGenerator(spv_target_env env) {
   CodeGenerator generator =
       env == SPV_ENV_WEBGPU_0 ? CodeGenerator::GetWebGPUShaderCodeGenerator()