Validate duplicate decorations and execution modes (#5641)

* Disallow duplicate decorations generally

* Only FuncParamAttr and UserSemantic can be applied to the same target
  multiple times
* Unchecked: completely duplicate UserSemantic and FuncParamAttr

* Disallow duplicate execution modes generally
  * Exceptions for float controls, float controls2 and some intel
    execution modes

* Fix invalid fuzzer transforms
diff --git a/source/fuzz/transformation_add_no_contraction_decoration.cpp b/source/fuzz/transformation_add_no_contraction_decoration.cpp
index 07a31e5..87393e9 100644
--- a/source/fuzz/transformation_add_no_contraction_decoration.cpp
+++ b/source/fuzz/transformation_add_no_contraction_decoration.cpp
@@ -36,6 +36,11 @@
   if (!instr) {
     return false;
   }
+  // |instr| must not be decorated with NoContraction.
+  if (ir_context->get_decoration_mgr()->HasDecoration(
+          message_.result_id(), spv::Decoration::NoContraction)) {
+    return false;
+  }
   // The instruction must be arithmetic.
   return IsArithmetic(instr->opcode());
 }
diff --git a/source/fuzz/transformation_add_relaxed_decoration.cpp b/source/fuzz/transformation_add_relaxed_decoration.cpp
index 6cd4ecb..601546c 100644
--- a/source/fuzz/transformation_add_relaxed_decoration.cpp
+++ b/source/fuzz/transformation_add_relaxed_decoration.cpp
@@ -36,6 +36,11 @@
   if (!instr) {
     return false;
   }
+  // |instr| must not be decorated with RelaxedPrecision.
+  if (ir_context->get_decoration_mgr()->HasDecoration(
+          message_.result_id(), spv::Decoration::RelaxedPrecision)) {
+    return false;
+  }
   opt::BasicBlock* cur_block = ir_context->get_instr_block(instr);
   // The instruction must have a block.
   if (cur_block == nullptr) {
@@ -46,6 +51,7 @@
           cur_block->id()))) {
     return false;
   }
+
   // The instruction must be numeric.
   return IsNumeric(instr->opcode());
 }
diff --git a/source/val/validate.cpp b/source/val/validate.cpp
index ccf26fb..3236807 100644
--- a/source/val/validate.cpp
+++ b/source/val/validate.cpp
@@ -144,6 +144,9 @@
   if (auto error = ValidateFloatControls2(_)) {
     return error;
   }
+  if (auto error = ValidateDuplicateExecutionModes(_)) {
+    return error;
+  }
 
   return SPV_SUCCESS;
 }
diff --git a/source/val/validate.h b/source/val/validate.h
index 52267c8..78093ce 100644
--- a/source/val/validate.h
+++ b/source/val/validate.h
@@ -94,6 +94,13 @@
 /// @return SPV_SUCCESS if no errors are found.
 spv_result_t ValidateFloatControls2(ValidationState_t& _);
 
+/// @brief Validates duplicated execution modes for each entry point.
+///
+/// @param[in] _ the validation state of the module
+///
+/// @return SPV_SUCCESS if no errors are found.
+spv_result_t ValidateDuplicateExecutionModes(ValidationState_t& _);
+
 /// @brief Validates memory instructions
 ///
 /// @param[in] _ the validation state of the module
diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp
index bb1fea5..0a7df65 100644
--- a/source/val/validate_decorations.cpp
+++ b/source/val/validate_decorations.cpp
@@ -1325,21 +1325,14 @@
 
 // Returns true if |decoration| cannot be applied to the same id more than once.
 bool AtMostOncePerId(spv::Decoration decoration) {
-  return decoration == spv::Decoration::ArrayStride;
+  return decoration != spv::Decoration::UserSemantic &&
+         decoration != spv::Decoration::FuncParamAttr;
 }
 
 // Returns true if |decoration| cannot be applied to the same member more than
 // once.
 bool AtMostOncePerMember(spv::Decoration decoration) {
-  switch (decoration) {
-    case spv::Decoration::Offset:
-    case spv::Decoration::MatrixStride:
-    case spv::Decoration::RowMajor:
-    case spv::Decoration::ColMajor:
-      return true;
-    default:
-      return false;
-  }
+  return decoration != spv::Decoration::UserSemantic;
 }
 
 spv_result_t CheckDecorationsCompatibility(ValidationState_t& vstate) {
diff --git a/source/val/validate_interfaces.cpp b/source/val/validate_interfaces.cpp
index 54ebfd7..ace548a 100644
--- a/source/val/validate_interfaces.cpp
+++ b/source/val/validate_interfaces.cpp
@@ -254,37 +254,24 @@
   // equal. Also track Patch and PerTaskNV decorations.
   bool has_location = false;
   uint32_t location = 0;
-  bool has_component = false;
   uint32_t component = 0;
   bool has_index = false;
   uint32_t index = 0;
   bool has_patch = false;
   bool has_per_task_nv = false;
   bool has_per_vertex_khr = false;
+  // Duplicate Location, Component, Index are checked elsewhere.
   for (auto& dec : _.id_decorations(variable->id())) {
     if (dec.dec_type() == spv::Decoration::Location) {
-      if (has_location && dec.params()[0] != location) {
-        return _.diag(SPV_ERROR_INVALID_DATA, variable)
-               << "Variable has conflicting location decorations";
-      }
       has_location = true;
       location = dec.params()[0];
     } else if (dec.dec_type() == spv::Decoration::Component) {
-      if (has_component && dec.params()[0] != component) {
-        return _.diag(SPV_ERROR_INVALID_DATA, variable)
-               << "Variable has conflicting component decorations";
-      }
-      has_component = true;
       component = dec.params()[0];
     } else if (dec.dec_type() == spv::Decoration::Index) {
       if (!is_output || !is_fragment) {
         return _.diag(SPV_ERROR_INVALID_DATA, variable)
                << "Index can only be applied to Fragment output variables";
       }
-      if (has_index && dec.params()[0] != index) {
-        return _.diag(SPV_ERROR_INVALID_DATA, variable)
-               << "Variable has conflicting index decorations";
-      }
       has_index = true;
       index = dec.params()[0];
     } else if (dec.dec_type() == spv::Decoration::BuiltIn) {
diff --git a/source/val/validate_mode_setting.cpp b/source/val/validate_mode_setting.cpp
index 199d8ed..8502fda 100644
--- a/source/val/validate_mode_setting.cpp
+++ b/source/val/validate_mode_setting.cpp
@@ -724,6 +724,25 @@
   return SPV_SUCCESS;
 }
 
+bool PerEntryExecutionMode(spv::ExecutionMode mode) {
+  switch (mode) {
+    // These execution modes can be specified multiple times per entry point.
+    case spv::ExecutionMode::DenormPreserve:
+    case spv::ExecutionMode::DenormFlushToZero:
+    case spv::ExecutionMode::SignedZeroInfNanPreserve:
+    case spv::ExecutionMode::RoundingModeRTE:
+    case spv::ExecutionMode::RoundingModeRTZ:
+    case spv::ExecutionMode::FPFastMathDefault:
+    case spv::ExecutionMode::RoundingModeRTPINTEL:
+    case spv::ExecutionMode::RoundingModeRTNINTEL:
+    case spv::ExecutionMode::FloatingPointModeALTINTEL:
+    case spv::ExecutionMode::FloatingPointModeIEEEINTEL:
+      return false;
+    default:
+      return true;
+  }
+}
+
 }  // namespace
 
 spv_result_t ValidateFloatControls2(ValidationState_t& _) {
@@ -808,5 +827,52 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t ValidateDuplicateExecutionModes(ValidationState_t& _) {
+  using PerEntryKey = std::tuple<spv::ExecutionMode, uint32_t>;
+  using PerOperandKey = std::tuple<spv::ExecutionMode, uint32_t, uint32_t>;
+  std::set<PerEntryKey> seen_per_entry;
+  std::set<PerOperandKey> seen_per_operand;
+
+  const auto lookupMode = [&_](spv::ExecutionMode mode) -> std::string {
+    spv_operand_desc desc = nullptr;
+    if (_.grammar().lookupOperand(SPV_OPERAND_TYPE_EXECUTION_MODE,
+                                  static_cast<uint32_t>(mode),
+                                  &desc) == SPV_SUCCESS) {
+      return std::string(desc->name);
+    }
+    return "Unknown";
+  };
+
+  for (const auto& inst : _.ordered_instructions()) {
+    if (inst.opcode() != spv::Op::OpExecutionMode &&
+        inst.opcode() != spv::Op::OpExecutionModeId) {
+      continue;
+    }
+
+    const auto entry = inst.GetOperandAs<uint32_t>(0);
+    const auto mode = inst.GetOperandAs<spv::ExecutionMode>(1);
+    if (PerEntryExecutionMode(mode)) {
+      if (!seen_per_entry.insert(std::make_tuple(mode, entry)).second) {
+        return _.diag(SPV_ERROR_INVALID_ID, &inst)
+               << lookupMode(mode)
+               << " execution mode must not be specified multiple times per "
+                  "entry point";
+      }
+    } else {
+      // Execution modes allowed multiple times all take a single operand.
+      const auto operand = inst.GetOperandAs<uint32_t>(2);
+      if (!seen_per_operand.insert(std::make_tuple(mode, entry, operand))
+               .second) {
+        return _.diag(SPV_ERROR_INVALID_ID, &inst)
+               << lookupMode(mode)
+               << " execution mode must not be specified multiple times for "
+                  "the same entry point and operands";
+      }
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/fuzz/transformation_add_no_contraction_decoration_test.cpp b/test/fuzz/transformation_add_no_contraction_decoration_test.cpp
index 4fc9d2d..1280b81 100644
--- a/test/fuzz/transformation_add_no_contraction_decoration_test.cpp
+++ b/test/fuzz/transformation_add_no_contraction_decoration_test.cpp
@@ -36,7 +36,6 @@
                OpName %8 "x"
                OpName %10 "y"
                OpName %14 "i"
-               OpDecorate %32 NoContraction
           %2 = OpTypeVoid
           %3 = OpTypeFunction %2
           %6 = OpTypeFloat 32
@@ -110,9 +109,8 @@
   ASSERT_FALSE(TransformationAddNoContractionDecoration(24).IsApplicable(
       context.get(), transformation_context));
 
-  // It is valid to add NoContraction to each of these ids (and it's fine to
-  // have duplicates of the decoration, in the case of 32).
-  for (uint32_t result_id : {32u, 32u, 27u, 29u, 39u}) {
+  // It is valid to add NoContraction to each of these ids.
+  for (uint32_t result_id : {32u, 27u, 29u, 39u}) {
     TransformationAddNoContractionDecoration transformation(result_id);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
@@ -134,8 +132,6 @@
                OpName %10 "y"
                OpName %14 "i"
                OpDecorate %32 NoContraction
-               OpDecorate %32 NoContraction
-               OpDecorate %32 NoContraction
                OpDecorate %27 NoContraction
                OpDecorate %29 NoContraction
                OpDecorate %39 NoContraction
diff --git a/test/fuzz/transformation_add_relaxed_decoration_test.cpp b/test/fuzz/transformation_add_relaxed_decoration_test.cpp
index c440882..979eeb7 100644
--- a/test/fuzz/transformation_add_relaxed_decoration_test.cpp
+++ b/test/fuzz/transformation_add_relaxed_decoration_test.cpp
@@ -86,9 +86,8 @@
   // Invalid: 28 is in a dead block, but returns bool (not numeric).
   ASSERT_FALSE(TransformationAddRelaxedDecoration(28).IsApplicable(
       context.get(), transformation_context));
-  // It is valid to add RelaxedPrecision to 25 (and it's fine to
-  // have a duplicate).
-  for (uint32_t result_id : {25u, 25u}) {
+  // It is valid to add RelaxedPrecision to 25
+  for (uint32_t result_id : {25u}) {
     TransformationAddRelaxedDecoration transformation(result_id);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
@@ -110,7 +109,6 @@
                OpName %10 "b"
                OpName %14 "c"
                OpDecorate %25 RelaxedPrecision
-               OpDecorate %25 RelaxedPrecision
           %2 = OpTypeVoid
           %3 = OpTypeFunction %2
           %6 = OpTypeInt 32 1
diff --git a/test/val/val_interfaces_test.cpp b/test/val/val_interfaces_test.cpp
index d75c20c..4f62be7 100644
--- a/test/val/val_interfaces_test.cpp
+++ b/test/val/val_interfaces_test.cpp
@@ -419,9 +419,10 @@
 )";
 
   CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
-  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Variable has conflicting location decorations"));
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("decorated with Location multiple times is not allowed"));
 }
 
 TEST_F(ValidateInterfacesTest, VulkanLocationsVariableAndMemberAssigned) {
@@ -505,9 +506,10 @@
 )";
 
   CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
-  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Member index 1 has conflicting location assignments"));
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("decorated with Location multiple times is not allowed"));
 }
 
 TEST_F(ValidateInterfacesTest, VulkanLocationsMissingAssignmentStructMember) {
@@ -1157,9 +1159,10 @@
 )";
 
   CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
-  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Variable has conflicting component decorations"));
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("decorated with Component multiple times is not allowed"));
 }
 
 TEST_F(ValidateInterfacesTest,
@@ -1186,10 +1189,10 @@
 )";
 
   CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
-  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Member index 0 has conflicting component assignments"));
+      HasSubstr("decorated with Component multiple times is not allowed"));
 }
 
 TEST_F(ValidateInterfacesTest, VulkanLocationsVariableConflictOutputIndex1) {
diff --git a/test/val/val_modes_test.cpp b/test/val/val_modes_test.cpp
index a0ea428..83a0503 100644
--- a/test/val/val_modes_test.cpp
+++ b/test/val/val_modes_test.cpp
@@ -1027,6 +1027,162 @@
                         "constant instructions."));
 }
 
+using AllowMultipleExecutionModes = spvtest::ValidateBase<std::string>;
+
+TEST_P(AllowMultipleExecutionModes, DifferentOperand) {
+  const std::string mode = GetParam();
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability DenormPreserve
+OpCapability DenormFlushToZero
+OpCapability SignedZeroInfNanPreserve
+OpCapability RoundingModeRTE
+OpCapability RoundingModeRTZ
+OpExtension "SPV_KHR_float_controls"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpExecutionMode %main )" + mode +
+                            R"( 16
+OpExecutionMode %main )" + mode +
+                            R"( 32
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(AllowMultipleExecutionModes, SameOperand) {
+  const std::string mode = GetParam();
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability DenormPreserve
+OpCapability DenormFlushToZero
+OpCapability SignedZeroInfNanPreserve
+OpCapability RoundingModeRTE
+OpCapability RoundingModeRTZ
+OpExtension "SPV_KHR_float_controls"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpExecutionMode %main )" + mode +
+                            R"( 32
+OpExecutionMode %main )" + mode +
+                            R"( 32
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("execution mode must not be specified multiple times "
+                        "for the same entry point and operands"));
+}
+
+INSTANTIATE_TEST_SUITE_P(MultipleFloatControlsExecModes,
+                         AllowMultipleExecutionModes,
+                         Values("DenormPreserve", "DenormFlushToZero",
+                                "SignedZeroInfNanPreserve", "RoundingModeRTE",
+                                "RoundingModeRTZ"));
+
+using MultipleExecModes = spvtest::ValidateBase<std::string>;
+
+TEST_P(MultipleExecModes, DuplicateMode) {
+  const std::string mode = GetParam();
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpExecutionMode %main )" + mode +
+                            R"(
+OpExecutionMode %main )" + mode +
+                            R"(
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("execution mode must not be specified multiple times "
+                        "per entry point"));
+}
+
+INSTANTIATE_TEST_SUITE_P(MultipleFragmentExecMode, MultipleExecModes,
+                         Values("DepthReplacing", "DepthGreater", "DepthLess",
+                                "DepthUnchanged"));
+
+TEST_F(ValidateMode, FloatControls2FPFastMathDefaultSameOperand) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability FloatControls2
+OpExtension "SPV_KHR_float_controls2"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpExecutionModeId %main FPFastMathDefault %float %none
+OpExecutionModeId %main FPFastMathDefault %float %none
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%int = OpTypeInt 32 0
+%none = OpConstant %int 0
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_2);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_2));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("execution mode must not be specified multiple times "
+                        "for the same entry point and operands"));
+}
+
+TEST_F(ValidateMode, FloatControls2FPFastMathDefaultDifferentOperand) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability FloatControls2
+OpExtension "SPV_KHR_float_controls2"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpExecutionModeId %main FPFastMathDefault %float %none
+OpExecutionModeId %main FPFastMathDefault %half %none
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%int = OpTypeInt 32 0
+%none = OpConstant %int 0
+%half = OpTypeFloat 16
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_2);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_2));
+}
+
 TEST_F(ValidateMode, FragmentShaderInterlockVertexBad) {
   const std::string spirv = R"(
 OpCapability Shader