Validator: FPRoundingMode decoration (#1482)

This commit checks the following when Shader capability exists:
"The FPRoundingMode decoration can be applied only to a width-only
conversion instruction that is used as the Object operand of an
OpStore storing through a pointer to a 16-bit floating-point object
in the StorageBuffer, Uniform, PushConstant, Input, or Output
Storage Classes.".
diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp
index cfb38ef..c4c390a 100644
--- a/source/val/validate_decorations.cpp
+++ b/source/val/validate_decorations.cpp
@@ -864,7 +864,82 @@
       }
     }
   }
+  return SPV_SUCCESS;
+}
 
+spv_result_t CheckDecorationsOfConversions(ValidationState_t& vstate) {
+  // Validates FPRoundingMode decoration for Shader Capabilities
+  if (!vstate.HasCapability(SpvCapabilityShader)) return SPV_SUCCESS;
+
+  for (const auto& kv : vstate.id_decorations()) {
+    const uint32_t id = kv.first;
+    const auto& decorations = kv.second;
+    if (decorations.empty()) {
+      continue;
+    }
+
+    const Instruction* inst = vstate.FindDef(id);
+    assert(inst);
+
+    // Validates FPRoundingMode decoration
+    for (const auto& decoration : decorations) {
+      if (decoration.dec_type() != SpvDecorationFPRoundingMode) {
+        continue;
+      }
+
+      // Validates width-only conversion instruction for floating-point object
+      // i.e., OpFConvert
+      if (inst->opcode() != SpvOpFConvert) {
+        return vstate.diag(SPV_ERROR_INVALID_ID, inst)
+               << "FPRoundingMode decoration can be applied only to a "
+                  "width-only conversion instruction for floating-point "
+                  "object.";
+      }
+
+      // Validates Object operand of an OpStore
+      for (const auto& use : inst->uses()) {
+        const auto store = use.first;
+        if (store->opcode() != SpvOpStore) {
+          return vstate.diag(SPV_ERROR_INVALID_ID, inst)
+                 << "FPRoundingMode decoration can be applied only to the "
+                    "Object operand of an OpStore.";
+        }
+
+        if (use.second != 2) {
+          return vstate.diag(SPV_ERROR_INVALID_ID, inst)
+                 << "FPRoundingMode decoration can be applied only to the "
+                    "Object operand of an OpStore.";
+        }
+
+        const auto ptr_inst = vstate.FindDef(store->GetOperandAs<uint32_t>(0));
+        const auto ptr_type =
+            vstate.FindDef(ptr_inst->GetOperandAs<uint32_t>(0));
+
+        const auto half_float_id = ptr_type->GetOperandAs<uint32_t>(2);
+        if (!vstate.IsFloatScalarType(half_float_id) ||
+            vstate.GetBitWidth(half_float_id) != 16) {
+          return vstate.diag(SPV_ERROR_INVALID_ID, inst)
+                 << "FPRoundingMode decoration can be applied only to the "
+                    "Object operand of an OpStore storing through a pointer to "
+                    "a 16-bit floating-point object.";
+        }
+
+        // Validates storage class of the pointer to the OpStore
+        const auto storage = ptr_type->GetOperandAs<uint32_t>(1);
+        if (storage != SpvStorageClassStorageBuffer &&
+            storage != SpvStorageClassUniform &&
+            storage != SpvStorageClassPushConstant &&
+            storage != SpvStorageClassInput &&
+            storage != SpvStorageClassOutput) {
+          return vstate.diag(SPV_ERROR_INVALID_ID, inst)
+                 << "FPRoundingMode decoration can be applied only to the "
+                    "Object operand of an OpStore in the StorageBuffer, "
+                    "Uniform, PushConstant, Input, or Output Storage "
+                    "Classes.";
+        }
+      }
+    }
+  }
   return SPV_SUCCESS;
 }
 
@@ -879,6 +954,7 @@
   if (auto error = CheckDescriptorSetArrayOfArrays(vstate)) return error;
   if (auto error = CheckVulkanMemoryModelDeprecatedDecorations(vstate))
     return error;
+  if (auto error = CheckDecorationsOfConversions(vstate)) return error;
   return SPV_SUCCESS;
 }
 
diff --git a/test/val/val_data_test.cpp b/test/val/val_data_test.cpp
index 88e0578..e6bb673 100644
--- a/test/val/val_data_test.cpp
+++ b/test/val/val_data_test.cpp
@@ -614,11 +614,24 @@
         OpCapability Linkage
         OpCapability )") +
                         cap + R"(
+        OpExtension "SPV_KHR_storage_buffer_storage_class"
+        OpExtension "SPV_KHR_variable_pointers"
         OpExtension "SPV_KHR_16bit_storage"
         OpMemoryModel Logical GLSL450
-        OpDecorate %2 FPRoundingMode )" + mode + R"(
-        %1 = OpTypeFloat 32
-        %2 = OpConstant %1 1.25
+        OpDecorate %_ FPRoundingMode )" + mode + R"(
+        %half = OpTypeFloat 16
+        %float = OpTypeFloat 32
+        %float_1_25 = OpConstant %float 1.25
+        %half_ptr = OpTypePointer StorageBuffer %half
+        %half_ptr_var = OpVariable %half_ptr StorageBuffer
+        %void = OpTypeVoid
+        %func = OpTypeFunction %void
+        %main = OpFunction %void None %func
+        %main_entry = OpLabel
+        %_ = OpFConvert %half %float_1_25
+        OpStore %half_ptr_var %_
+        OpReturn
+        OpFunctionEnd
       )";
       CompileSuccessfully(str.c_str());
       ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
@@ -631,11 +644,24 @@
     for (const auto env : {SPV_ENV_VULKAN_1_0, SPV_ENV_VULKAN_1_1}) {
       std::string str = std::string(R"(
         OpCapability Shader
+        OpExtension "SPV_KHR_storage_buffer_storage_class"
+        OpExtension "SPV_KHR_variable_pointers"
         OpMemoryModel Logical GLSL450
-        OpDecorate %2 FPRoundingMode )") +
+        OpDecorate %_ FPRoundingMode )") +
                         mode + R"(
-        %1 = OpTypeFloat 32
-        %2 = OpConstant %1 1.25
+        %half = OpTypeFloat 16
+        %float = OpTypeFloat 32
+        %float_1_25 = OpConstant %float 1.25
+        %half_ptr = OpTypePointer StorageBuffer %half
+        %half_ptr_var = OpVariable %half_ptr StorageBuffer
+        %void = OpTypeVoid
+        %func = OpTypeFunction %void
+        %main = OpFunction %void None %func
+        %main_entry = OpLabel
+        %_ = OpFConvert %half %float_1_25
+        OpStore %half_ptr_var %_
+        OpReturn
+        OpFunctionEnd
       )";
       CompileSuccessfully(str.c_str());
       ASSERT_EQ(SPV_ERROR_INVALID_CAPABILITY, ValidateInstructions(env));
diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp
index 6f3f401..21803f9 100644
--- a/test/val/val_decoration_test.cpp
+++ b/test/val/val_decoration_test.cpp
@@ -3145,6 +3145,271 @@
                         "banned when using "
                         "the Vulkan memory model."));
 }
+
+TEST_F(ValidateDecorations, FPRoundingModeGood) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageBuffer16BitAccess
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpExtension "SPV_KHR_variable_pointers"
+OpExtension "SPV_KHR_16bit_storage"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpDecorate %_ FPRoundingMode RTE
+%half = OpTypeFloat 16
+%float = OpTypeFloat 32
+%float_1_25 = OpConstant %float 1.25
+%half_ptr = OpTypePointer StorageBuffer %half
+%half_ptr_var = OpVariable %half_ptr StorageBuffer
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+%_ = OpFConvert %half %float_1_25
+OpStore %half_ptr_var %_
+OpReturn
+OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
+}
+
+TEST_F(ValidateDecorations, FPRoundingModeNotOpFConvert) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageBuffer16BitAccess
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpExtension "SPV_KHR_variable_pointers"
+OpExtension "SPV_KHR_16bit_storage"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpDecorate %_ FPRoundingMode RTE
+%short = OpTypeInt 16 1
+%int = OpTypeInt 32 1
+%int_17 = OpConstant %int 17
+%short_ptr = OpTypePointer StorageBuffer %short
+%short_ptr_var = OpVariable %short_ptr StorageBuffer
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+%_ = OpSConvert %short %int_17
+OpStore %short_ptr_var %_
+OpReturn
+OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("FPRoundingMode decoration can be applied only to a "
+                        "width-only conversion instruction for floating-point "
+                        "object."));
+}
+
+TEST_F(ValidateDecorations, FPRoundingModeNoOpStoreGood) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageBuffer16BitAccess
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpExtension "SPV_KHR_variable_pointers"
+OpExtension "SPV_KHR_16bit_storage"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpDecorate %_ FPRoundingMode RTE
+%half = OpTypeFloat 16
+%float = OpTypeFloat 32
+%float_1_25 = OpConstant %float 1.25
+%half_ptr = OpTypePointer StorageBuffer %half
+%half_ptr_var = OpVariable %half_ptr StorageBuffer
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+%_ = OpFConvert %half %float_1_25
+OpReturn
+OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
+}
+
+TEST_F(ValidateDecorations, FPRoundingModeFConvert64to16Good) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageBuffer16BitAccess
+OpCapability Float64
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpExtension "SPV_KHR_variable_pointers"
+OpExtension "SPV_KHR_16bit_storage"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpDecorate %_ FPRoundingMode RTE
+%half = OpTypeFloat 16
+%double = OpTypeFloat 64
+%double_1_25 = OpConstant %double 1.25
+%half_ptr = OpTypePointer StorageBuffer %half
+%half_ptr_var = OpVariable %half_ptr StorageBuffer
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+%_ = OpFConvert %half %double_1_25
+OpStore %half_ptr_var %_
+OpReturn
+OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
+}
+
+TEST_F(ValidateDecorations, FPRoundingModeNotStoreInFloat16) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageBuffer16BitAccess
+OpCapability Float64
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpExtension "SPV_KHR_variable_pointers"
+OpExtension "SPV_KHR_16bit_storage"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpDecorate %_ FPRoundingMode RTE
+%float = OpTypeFloat 32
+%double = OpTypeFloat 64
+%double_1_25 = OpConstant %double 1.25
+%float_ptr = OpTypePointer StorageBuffer %float
+%float_ptr_var = OpVariable %float_ptr StorageBuffer
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+%_ = OpFConvert %float %double_1_25
+OpStore %float_ptr_var %_
+OpReturn
+OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("FPRoundingMode decoration can be applied only to the "
+                        "Object operand of an OpStore storing through a "
+                        "pointer to a 16-bit floating-point object."));
+}
+
+TEST_F(ValidateDecorations, FPRoundingModeBadStorageClass) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageBuffer16BitAccess
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpExtension "SPV_KHR_variable_pointers"
+OpExtension "SPV_KHR_16bit_storage"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpDecorate %_ FPRoundingMode RTE
+%half = OpTypeFloat 16
+%float = OpTypeFloat 32
+%float_1_25 = OpConstant %float 1.25
+%half_ptr = OpTypePointer Private %half
+%half_ptr_var = OpVariable %half_ptr Private
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+%_ = OpFConvert %half %float_1_25
+OpStore %half_ptr_var %_
+OpReturn
+OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("FPRoundingMode decoration can be applied only to the "
+                "Object operand of an OpStore in the StorageBuffer, Uniform, "
+                "PushConstant, Input, or Output Storage Classes."));
+}
+
+TEST_F(ValidateDecorations, FPRoundingModeMultipleOpStoreGood) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageBuffer16BitAccess
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpExtension "SPV_KHR_variable_pointers"
+OpExtension "SPV_KHR_16bit_storage"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpDecorate %_ FPRoundingMode RTE
+%half = OpTypeFloat 16
+%float = OpTypeFloat 32
+%float_1_25 = OpConstant %float 1.25
+%half_ptr = OpTypePointer StorageBuffer %half
+%half_ptr_var_0 = OpVariable %half_ptr StorageBuffer
+%half_ptr_var_1 = OpVariable %half_ptr StorageBuffer
+%half_ptr_var_2 = OpVariable %half_ptr StorageBuffer
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+%_ = OpFConvert %half %float_1_25
+OpStore %half_ptr_var_0 %_
+OpStore %half_ptr_var_1 %_
+OpStore %half_ptr_var_2 %_
+OpReturn
+OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
+}
+
+TEST_F(ValidateDecorations, FPRoundingModeMultipleUsesBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageBuffer16BitAccess
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpExtension "SPV_KHR_variable_pointers"
+OpExtension "SPV_KHR_16bit_storage"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpDecorate %_ FPRoundingMode RTE
+%half = OpTypeFloat 16
+%float = OpTypeFloat 32
+%float_1_25 = OpConstant %float 1.25
+%half_ptr = OpTypePointer StorageBuffer %half
+%half_ptr_var_0 = OpVariable %half_ptr StorageBuffer
+%half_ptr_var_1 = OpVariable %half_ptr StorageBuffer
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+%_ = OpFConvert %half %float_1_25
+OpStore %half_ptr_var_0 %_
+%result = OpFAdd %half %_ %_
+OpStore %half_ptr_var_1 %_
+OpReturn
+OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("FPRoundingMode decoration can be applied only to the "
+                        "Object operand of an OpStore."));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools