Repository: KhronosGroup/SPIRV-Tools (language: python)

Issue:
spirv-val does not validate OpGroupNonUniform{All,Any,AllEqual}
This would have caught invalid SPIR-V being generated by clspv: https://github.com/google/clspv/issues/1140.


Discussion / hints:

Fix patch:
diff --git a/source/val/validate_non_uniform.cpp b/source/val/validate_non_uniform.cpp
index af04e76af0..2c36ce3324 100644
--- a/source/val/validate_non_uniform.cpp
+++ b/source/val/validate_non_uniform.cpp
@@ -26,6 +26,207 @@ namespace spvtools {
 namespace val {
 namespace {
 
+spv_result_t ValidateGroupNonUniformElect(ValidationState_t& _,
+                                          const Instruction* inst) {
+  if (!_.IsBoolScalarType(inst->type_id())) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Result must be a boolean scalar type";
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateGroupNonUniformAnyAll(ValidationState_t& _,
+                                           const Instruction* inst) {
+  if (!_.IsBoolScalarType(inst->type_id())) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Result must be a boolean scalar type";
+  }
+
+  if (!_.IsBoolScalarType(_.GetOperandTypeId(inst, 3))) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Predicate must be a boolean scalar type";
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateGroupNonUniformAllEqual(ValidationState_t& _,
+                                             const Instruction* inst) {
+  if (!_.IsBoolScalarType(inst->type_id())) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Result must be a boolean scalar type";
+  }
+
+  const auto value_type = _.GetOperandTypeId(inst, 3);
+  if (!_.IsFloatScalarOrVectorType(value_type) &&
+      !_.IsIntScalarOrVectorType(value_type) &&
+      !_.IsBoolScalarOrVectorType(value_type)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Value must be a scalar or vector of integer, floating-point, or "
+              "boolean type";
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateGroupNonUniformBroadcastShuffle(ValidationState_t& _,
+                                                     const Instruction* inst) {
+  const auto type_id = inst->type_id();
+  if (!_.IsFloatScalarOrVectorType(type_id) &&
+      !_.IsIntScalarOrVectorType(type_id) &&
+      !_.IsBoolScalarOrVectorType(type_id)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Result must be a scalar or vector of integer, floating-point, "
+              "or boolean type";
+  }
+
+  const auto value_type_id = _.GetOperandTypeId(inst, 3);
+  if (value_type_id != type_id) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "The type of Value must match the Result type";
+  }
+
+  const auto GetOperandName = [](const spv::Op opcode) {
+    std::string operand;
+    switch (opcode) {
+      case spv::Op::OpGroupNonUniformBroadcast:
+      case spv::Op::OpGroupNonUniformShuffle:
+        operand = "Id";
+        break;
+      case spv::Op::OpGroupNonUniformShuffleXor:
+        operand = "Mask";
+        break;
+      case spv::Op::OpGroupNonUniformQuadBroadcast:
+        operand = "Index";
+        break;
+      case spv::Op::OpGroupNonUniformQuadSwap:
+        operand = "Direction";
+        break;
+      case spv::Op::OpGroupNonUniformShuffleUp:
+      case spv::Op::OpGroupNonUniformShuffleDown:
+      default:
+        operand = "Delta";
+        break;
+    }
+    return operand;
+  };
+
+  const auto id_type_id = _.GetOperandTypeId(inst, 4);
+  if (!_.IsUnsignedIntScalarType(id_type_id)) {
+    std::string operand = GetOperandName(inst->opcode());
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << operand << " must be an unsigned integer scalar";
+  }
+
+  const bool should_be_constant =
+      inst->opcode() == spv::Op::OpGroupNonUniformQuadSwap ||
+      ((inst->opcode() == spv::Op::OpGroupNonUniformBroadcast ||
+        inst->opcode() == spv::Op::OpGroupNonUniformQuadBroadcast) &&
+       _.version() < SPV_SPIRV_VERSION_WORD(1, 5));
+  if (should_be_constant) {
+    const auto id_id = inst->GetOperandAs<uint32_t>(4);
+    const auto id_op = _.GetIdOpcode(id_id);
+    if (!spvOpcodeIsConstant(id_op)) {
+      std::string operand = GetOperandName(inst->opcode());
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Before SPIR-V 1.5, " << operand
+             << " must be a constant instruction";
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateGroupNonUniformBroadcastFirst(ValidationState_t& _,
+                                                   const Instruction* inst) {
+  const auto type_id = inst->type_id();
+  if (!_.IsFloatScalarOrVectorType(type_id) &&
+      !_.IsIntScalarOrVectorType(type_id) &&
+      !_.IsBoolScalarOrVectorType(type_id)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Result must be a scalar or vector of integer, floating-point, "
+              "or boolean type";
+  }
+
+  const auto value_type_id = _.GetOperandTypeId(inst, 3);
+  if (value_type_id != type_id) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "The type of Value must match the Result type";
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateGroupNonUniformBallot(ValidationState_t& _,
+                                           const Instruction* inst) {
+  if (!_.IsUnsignedIntVectorType(inst->type_id())) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Result must be a 4-component unsigned integer vector";
+  }
+
+  if (_.GetDimension(inst->type_id()) != 4) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Result must be a 4-component unsigned integer vector";
+  }
+
+  const auto pred_type_id = _.GetOperandTypeId(inst, 3);
+  if (!_.IsBoolScalarType(pred_type_id)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Predicate must be a boolean scalar";
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateGroupNonUniformInverseBallot(ValidationState_t& _,
+                                                  const Instruction* inst) {
+  if (!_.IsBoolScalarType(inst->type_id())) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Result must be a boolean scalar";
+  }
+
+  const auto value_type_id = _.GetOperandTypeId(inst, 3);
+  if (!_.IsUnsignedIntVectorType(value_type_id)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Value must be a 4-component unsigned integer vector";
+  }
+
+  if (_.GetDimension(value_type_id) != 4) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Value must be a 4-component unsigned integer vector";
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateGroupNonUniformBallotBitExtract(ValidationState_t& _,
+                                                     const Instruction* inst) {
+  if (!_.IsBoolScalarType(inst->type_id())) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Result must be a boolean scalar";
+  }
+
+  const auto value_type_id = _.GetOperandTypeId(inst, 3);
+  if (!_.IsUnsignedIntVectorType(value_type_id)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Value must be a 4-component unsigned integer vector";
+  }
+
+  if (_.GetDimension(value_type_id) != 4) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Value must be a 4-component unsigned integer vector";
+  }
+
+  const auto id_type_id = _.GetOperandTypeId(inst, 4);
+  if (!_.IsUnsignedIntScalarType(id_type_id)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Id must be an unsigned integer scalar";
+  }
+
+  return SPV_SUCCESS;
+}
+
 spv_result_t ValidateGroupNonUniformBallotBitCount(ValidationState_t& _,
                                                    const Instruction* inst) {
   // Scope is already checked by ValidateExecutionScope() above.
@@ -60,6 +261,107 @@ spv_result_t ValidateGroupNonUniformBallotBitCount(ValidationState_t& _,
   return SPV_SUCCESS;
 }
 
+spv_result_t ValidateGroupNonUniformBallotFind(ValidationState_t& _,
+                                               const Instruction* inst) {
+  if (!_.IsUnsignedIntScalarType(inst->type_id())) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Result must be an unsigned integer scalar";
+  }
+
+  const auto value_type_id = _.GetOperandTypeId(inst, 3);
+  if (!_.IsUnsignedIntVectorType(value_type_id)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Value must be a 4-component unsigned integer vector";
+  }
+
+  if (_.GetDimension(value_type_id) != 4) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Value must be a 4-component unsigned integer vector";
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateGroupNonUniformArithmetic(ValidationState_t& _,
+                                               const Instruction* inst) {
+  const bool is_unsigned = inst->opcode() == spv::Op::OpGroupNonUniformUMin ||
+                           inst->opcode() == spv::Op::OpGroupNonUniformUMax;
+  const bool is_float = inst->opcode() == spv::Op::OpGroupNonUniformFAdd ||
+                        inst->opcode() == spv::Op::OpGroupNonUniformFMul ||
+                        inst->opcode() == spv::Op::OpGroupNonUniformFMin ||
+                        inst->opcode() == spv::Op::OpGroupNonUniformFMax;
+  const bool is_bool = inst->opcode() == spv::Op::OpGroupNonUniformLogicalAnd ||
+                       inst->opcode() == spv::Op::OpGroupNonUniformLogicalOr ||
+                       inst->opcode() == spv::Op::OpGroupNonUniformLogicalXor;
+  if (is_float) {
+    if (!_.IsFloatScalarOrVectorType(inst->type_id())) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Result must be a floating-point scalar or vector";
+    }
+  } else if (is_bool) {
+    if (!_.IsBoolScalarOrVectorType(inst->type_id())) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Result must be a boolean scalar or vector";
+    }
+  } else if (is_unsigned) {
+    if (!_.IsUnsignedIntScalarOrVectorType(inst->type_id())) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Result must be an unsigned integer scalar or vector";
+    }
+  } else if (!_.IsIntScalarOrVectorType(inst->type_id())) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Result must be an integer scalar or vector";
+  }
+
+  const auto value_type_id = _.GetOperandTypeId(inst, 4);
+  if (value_type_id != inst->type_id()) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "The type of Value must match the Result type";
+  }
+
+  const auto group_op = inst->GetOperandAs<spv::GroupOperation>(3);
+  bool is_clustered_reduce = group_op == spv::GroupOperation::ClusteredReduce;
+  bool is_partitioned_nv =
+      group_op == spv::GroupOperation::PartitionedReduceNV ||
+      group_op == spv::GroupOperation::PartitionedInclusiveScanNV ||
+      group_op == spv::GroupOperation::PartitionedExclusiveScanNV;
+  if (inst->operands().size() <= 5) {
+    if (is_clustered_reduce) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "ClusterSize must be present when Operation is ClusteredReduce";
+    } else if (is_partitioned_nv) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Ballot must be present when Operation is PartitionedReduceNV, "
+                "PartitionedInclusiveScanNV, or PartitionedExclusiveScanNV";
+    }
+  } else {
+    const auto operand_id = inst->GetOperandAs<uint32_t>(5);
+    const auto* operand = _.FindDef(operand_id);
+    if (is_partitioned_nv) {
+      if (!operand || !_.IsIntScalarOrVectorType(operand->type_id())) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Ballot must be a 4-component integer vector";
+      }
+
+      if (_.GetDimension(operand->type_id()) != 4) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Ballot must be a 4-component integer vector";
+      }
+    } else {
+      if (!operand || !_.IsUnsignedIntScalarType(operand->type_id())) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "ClusterSize must be an unsigned integer scalar";
+      }
+
+      if (!spvOpcodeIsConstant(operand->opcode())) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "ClusterSize must be a constant instruction";
+      }
+    }
+  }
+  return SPV_SUCCESS;
+}
+
 spv_result_t ValidateGroupNonUniformRotateKHR(ValidationState_t& _,
                                               const Instruction* inst) {
   // Scope is already checked by ValidateExecutionScope() above.
@@ -120,15 +422,58 @@ spv_result_t NonUniformPass(ValidationState_t& _, const Instruction* inst) {
   const spv::Op opcode = inst->opcode();
 
   if (spvOpcodeIsNonUniformGroupOperation(opcode)) {
-    const uint32_t execution_scope = inst->word(3);
+    const uint32_t execution_scope = inst->GetOperandAs<uint32_t>(2);
     if (auto error = ValidateExecutionScope(_, inst, execution_scope)) {
       return error;
     }
   }
 
   switch (opcode) {
+    case spv::Op::OpGroupNonUniformElect:
+      return ValidateGroupNonUniformElect(_, inst);
+    case spv::Op::OpGroupNonUniformAny:
+    case spv::Op::OpGroupNonUniformAll:
+      return ValidateGroupNonUniformAnyAll(_, inst);
+    case spv::Op::OpGroupNonUniformAllEqual:
+      return ValidateGroupNonUniformAllEqual(_, inst);
+    case spv::Op::OpGroupNonUniformBroadcast:
+    case spv::Op::OpGroupNonUniformShuffle:
+    case spv::Op::OpGroupNonUniformShuffleXor:
+    case spv::Op::OpGroupNonUniformShuffleUp:
+    case spv::Op::OpGroupNonUniformShuffleDown:
+    case spv::Op::OpGroupNonUniformQuadBroadcast:
+    case spv::Op::OpGroupNonUniformQuadSwap:
+      return ValidateGroupNonUniformBroadcastShuffle(_, inst);
+    case spv::Op::OpGroupNonUniformBroadcastFirst:
+      return ValidateGroupNonUniformBroadcastFirst(_, inst);
+    case spv::Op::OpGroupNonUniformBallot:
+      return ValidateGroupNonUniformBallot(_, inst);
+    case spv::Op::OpGroupNonUniformInverseBallot:
+      return ValidateGroupNonUniformInverseBallot(_, inst);
+    case spv::Op::OpGroupNonUniformBallotBitExtract:
+      return ValidateGroupNonUniformBallotBitExtract(_, inst);
     case spv::Op::OpGroupNonUniformBallotBitCount:
       return ValidateGroupNonUniformBallotBitCount(_, inst);
+    case spv::Op::OpGroupNonUniformBallotFindLSB:
+    case spv::Op::OpGroupNonUniformBallotFindMSB:
+      return ValidateGroupNonUniformBallotFind(_, inst);
+    case spv::Op::OpGroupNonUniformIAdd:
+    case spv::Op::OpGroupNonUniformFAdd:
+    case spv::Op::OpGroupNonUniformIMul:
+    case spv::Op::OpGroupNonUniformFMul:
+    case spv::Op::OpGroupNonUniformSMin:
+    case spv::Op::OpGroupNonUniformUMin:
+    case spv::Op::OpGroupNonUniformFMin:
+    case spv::Op::OpGroupNonUniformSMax:
+    case spv::Op::OpGroupNonUniformUMax:
+    case spv::Op::OpGroupNonUniformFMax:
+    case spv::Op::OpGroupNonUniformBitwiseAnd:
+    case spv::Op::OpGroupNonUniformBitwiseOr:
+    case spv::Op::OpGroupNonUniformBitwiseXor:
+    case spv::Op::OpGroupNonUniformLogicalAnd:
+    case spv::Op::OpGroupNonUniformLogicalOr:
+    case spv::Op::OpGroupNonUniformLogicalXor:
+      return ValidateGroupNonUniformArithmetic(_, inst);
     case spv::Op::OpGroupNonUniformRotateKHR:
       return ValidateGroupNonUniformRotateKHR(_, inst);
     default:
diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp
index cde8aaadaf..cf8a2689f0 100644
--- a/source/val/validation_state.cpp
+++ b/source/val/validation_state.cpp
@@ -1009,6 +1009,23 @@ bool ValidationState_t::IsUnsignedIntVectorType(uint32_t id) const {
   return false;
 }
 
+bool ValidationState_t::IsUnsignedIntScalarOrVectorType(uint32_t id) const {
+  const Instruction* inst = FindDef(id);
+  if (!inst) {
+    return false;
+  }
+
+  if (inst->opcode() == spv::Op::OpTypeInt) {
+    return inst->GetOperandAs<uint32_t>(2) == 0;
+  }
+
+  if (inst->opcode() == spv::Op::OpTypeVector) {
+    return IsUnsignedIntScalarType(GetComponentType(id));
+  }
+
+  return false;
+}
+
 bool ValidationState_t::IsSignedIntScalarType(uint32_t id) const {
   const Instruction* inst = FindDef(id);
   return inst && inst->opcode() == spv::Op::OpTypeInt && inst->word(3) == 1;
diff --git a/source/val/validation_state.h b/source/val/validation_state.h
index bfae821478..1a64a41c99 100644
--- a/source/val/validation_state.h
+++ b/source/val/validation_state.h
@@ -602,6 +602,7 @@ class ValidationState_t {
   bool IsIntScalarOrVectorType(uint32_t id) const;
   bool IsUnsignedIntScalarType(uint32_t id) const;
   bool IsUnsignedIntVectorType(uint32_t id) const;
+  bool IsUnsignedIntScalarOrVectorType(uint32_t id) const;
   bool IsSignedIntScalarType(uint32_t id) const;
   bool IsSignedIntVectorType(uint32_t id) const;
   bool IsBoolScalarType(uint32_t id) const;


Test patch:
diff --git a/test/opt/amd_ext_to_khr.cpp b/test/opt/amd_ext_to_khr.cpp
index 3340e898ce..a520d600b1 100644
--- a/test/opt/amd_ext_to_khr.cpp
+++ b/test/opt/amd_ext_to_khr.cpp
@@ -26,15 +26,17 @@ using AmdExtToKhrTest = PassTest<::testing::Test>;
 
 using ::testing::HasSubstr;
 
-std::string GetTest(std::string op_code, std::string new_op_code) {
+std::string GetTest(std::string op_code, std::string new_op_code,
+                    bool is_float = false) {
   const std::string text = R"(
 ; CHECK: OpCapability Shader
 ; CHECK-NOT: OpExtension "SPV_AMD_shader_ballot"
 ; CHECK: OpFunction
 ; CHECK-NEXT: OpLabel
-; CHECK-NEXT: [[undef:%\w+]] = OpUndef %uint
+; CHECK-NEXT: [[undef:%\w+]] = OpUndef %
 ; CHECK-NEXT: )" + new_op_code +
-                           R"( %uint %uint_3 Reduce [[undef]]
+                           " %" + (is_float ? "float" : "uint") +
+                           R"( %uint_3 Reduce [[undef]]
                OpCapability Shader
                OpCapability Groups
                OpExtension "SPV_AMD_shader_ballot"
@@ -44,12 +46,15 @@ std::string GetTest(std::string op_code, std::string new_op_code) {
        %void = OpTypeVoid
           %3 = OpTypeFunction %void
        %uint = OpTypeInt 32 0
+      %float = OpTypeFloat 32
      %uint_3 = OpConstant %uint 3
           %1 = OpFunction %void None %3
           %6 = OpLabel
-          %7 = OpUndef %uint
+          %7 = OpUndef %)" +
+                           (is_float ? "float" : "uint") + R"(
           %8 = )" + op_code +
-                           R"( %uint %uint_3 Reduce %7
+                           " %" + (is_float ? "float" : "uint") +
+                           R"( %uint_3 Reduce %7
                OpReturn
                OpFunctionEnd
 
@@ -64,7 +69,7 @@ TEST_F(AmdExtToKhrTest, ReplaceGroupIAddNonUniformAMD) {
 }
 TEST_F(AmdExtToKhrTest, ReplaceGroupFAddNonUniformAMD) {
   std::string text =
-      GetTest("OpGroupFAddNonUniformAMD", "OpGroupNonUniformFAdd");
+      GetTest("OpGroupFAddNonUniformAMD", "OpGroupNonUniformFAdd", true);
   SinglePassRunAndMatch<AmdExtensionToKhrPass>(text, true);
 }
 TEST_F(AmdExtToKhrTest, ReplaceGroupUMinNonUniformAMD) {
@@ -79,7 +84,7 @@ TEST_F(AmdExtToKhrTest, ReplaceGroupSMinNonUniformAMD) {
 }
 TEST_F(AmdExtToKhrTest, ReplaceGroupFMinNonUniformAMD) {
   std::string text =
-      GetTest("OpGroupFMinNonUniformAMD", "OpGroupNonUniformFMin");
+      GetTest("OpGroupFMinNonUniformAMD", "OpGroupNonUniformFMin", true);
   SinglePassRunAndMatch<AmdExtensionToKhrPass>(text, true);
 }
 TEST_F(AmdExtToKhrTest, ReplaceGroupUMaxNonUniformAMD) {
@@ -94,7 +99,7 @@ TEST_F(AmdExtToKhrTest, ReplaceGroupSMaxNonUniformAMD) {
 }
 TEST_F(AmdExtToKhrTest, ReplaceGroupFMaxNonUniformAMD) {
   std::string text =
-      GetTest("OpGroupFMaxNonUniformAMD", "OpGroupNonUniformFMax");
+      GetTest("OpGroupFMaxNonUniformAMD", "OpGroupNonUniformFMax", true);
   SinglePassRunAndMatch<AmdExtensionToKhrPass>(text, true);
 }
 
diff --git a/test/val/val_non_uniform_test.cpp b/test/val/val_non_uniform_test.cpp
index af571d3a9f..a020500d66 100644
--- a/test/val/val_non_uniform_test.cpp
+++ b/test/val/val_non_uniform_test.cpp
@@ -44,6 +44,8 @@ OpCapability GroupNonUniformShuffleRelative
 OpCapability GroupNonUniformArithmetic
 OpCapability GroupNonUniformClustered
 OpCapability GroupNonUniformQuad
+OpCapability GroupNonUniformPartitionedNV
+OpExtension "SPV_NV_shader_subgroup_partitioned"
 )";
 
   ss << capabilities_and_extensions;
@@ -62,16 +64,27 @@ OpCapability GroupNonUniformQuad
 %float = OpTypeFloat 32
 %u32vec4 = OpTypeVector %u32 4
 %u32vec3 = OpTypeVector %u32 3
+%v2bool = OpTypeVector %bool 2
+%v4float = OpTypeVector %float 4
+%struct = OpTypeStruct %int
+%v4int = OpTypeVector %int 4
 
 %true = OpConstantTrue %bool
 %false = OpConstantFalse %bool
 
 %u32_0 = OpConstant %u32 0
+%int_0 = OpConstant %int 0
 
 %float_0 = OpConstant %float 0
 
 %u32vec4_null = OpConstantComposite %u32vec4 %u32_0 %u32_0 %u32_0 %u32_0
 %u32vec3_null = OpConstantComposite %u32vec3 %u32_0 %u32_0 %u32_0
+%v2bool_false = OpConstantNull %v2bool
+%v4float_null = OpConstantNull %v4float
+%struct_null = OpConstantNull %struct
+%v4int_null = OpConstantComposite %v4int %int_0 %int_0 %int_0 %int_0
+
+%u32_undef = OpUndef %u32
 
 %cross_device = OpConstant %u32 0
 %device = OpConstant %u32 1
@@ -122,6 +135,32 @@ std::string ConvertScope(spv::Scope scope) {
   }
 }
 
+std::string ConvertMatch(const std::string& type) {
+  if (type == "%bool") {
+    return "%true";
+  } else if (type == "%u32") {
+    return "%u32_0";
+  } else if (type == "%int") {
+    return "%int_0";
+  } else if (type == "%float") {
+    return "%float_0";
+  } else if (type == "%u32vec4") {
+    return "%u32vec4_null";
+  } else if (type == "%u32vec3") {
+    return "%u32vec3_null";
+  } else if (type == "%v2bool") {
+    return "%v2bool_false";
+  } else if (type == "%v4float") {
+    return "%v4float_null";
+  } else if (type == "%struct") {
+    return "%struct_null";
+  } else if (type == "%v4int") {
+    return "%v4int_null";
+  }
+
+  return "INVALID";
+}
+
 TEST_P(GroupNonUniform, Vulkan1p1) {
   std::string opcode = std::get<0>(GetParam());
   std::string type = std::get<1>(GetParam());
@@ -129,6 +168,13 @@ TEST_P(GroupNonUniform, Vulkan1p1) {
   std::string args = std::get<3>(GetParam());
   std::string error = std::get<4>(GetParam());
 
+  const std::string match = "match_res";
+  size_t pos = std::string::npos;
+  while ((pos = args.find(match)) != std::string::npos) {
+    const std::string replace = ConvertMatch(type);
+    args = args.substr(0, pos) + replace + args.substr(pos + match.size());
+  }
+
   std::ostringstream sstr;
   sstr << "%result = " << opcode << " ";
   sstr << type << " ";
@@ -162,6 +208,13 @@ TEST_P(GroupNonUniform, Spirv1p3) {
   std::string args = std::get<3>(GetParam());
   std::string error = std::get<4>(GetParam());
 
+  const std::string match = "match_res";
+  size_t pos = std::string::npos;
+  while ((pos = args.find(match)) != std::string::npos) {
+    const std::string replace = ConvertMatch(type);
+    args = args.substr(0, pos) + replace + args.substr(pos + match.size());
+  }
+
   std::ostringstream sstr;
   sstr << "%result = " << opcode << " ";
   sstr << type << " ";
@@ -292,6 +345,530 @@ INSTANTIATE_TEST_SUITE_P(GroupNonUniformBallotBitCountBadValue, GroupNonUniform,
                                  Values("Expected Value to be a vector of four "
                                         "components of integer type scalar")));
 
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformElectGood, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformElect"),
+                                 Values("%bool"), Values(spv::Scope::Subgroup),
+                                 Values(""), Values("")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformElectBadResultType, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformElect"),
+            Values("%void", "%u32", "%int", "%float", "%u32vec4", "%u32vec3",
+                   "%v2bool", "%v4float", "%struct"),
+            Values(spv::Scope::Subgroup), Values(""),
+            Values("Result must be a boolean scalar type")));
+
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformAnyAllGood, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformAny",
+                                        "OpGroupNonUniformAll"),
+                                 Values("%bool"), Values(spv::Scope::Subgroup),
+                                 Values("%true", "%false"), Values("")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformAnyAllBadResultType, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformAny", "OpGroupNonUniformAll"),
+            Values("%void", "%u32", "%int", "%float", "%u32vec4", "%u32vec3",
+                   "%v2bool", "%v4float", "%struct"),
+            Values(spv::Scope::Subgroup), Values("%true"),
+            Values("Result must be a boolean scalar type")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformAnyAllBadOperand, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformAny", "OpGroupNonUniformAll"),
+            Values("%bool"), Values(spv::Scope::Subgroup),
+            Values("%u32_0", "%int_0", "%float_0", "%u32vec4_null",
+                   "%u32vec3_null", "%v2bool_false", "%v4float_null",
+                   "%struct_null"),
+            Values("Predicate must be a boolean scalar type")));
+
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformAllEqualGood, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformAllEqual"),
+                                 Values("%bool"), Values(spv::Scope::Subgroup),
+                                 Values("%true", "%false"), Values("")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformAllEqualBadResultType, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformAllEqual"),
+            Values("%void", "%u32", "%int", "%float", "%u32vec4", "%u32vec3",
+                   "%v2bool", "%v4float", "%struct"),
+            Values(spv::Scope::Subgroup), Values("%true"),
+            Values("Result must be a boolean scalar type")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformAllEqualBadOperand, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformAllEqual"), Values("%bool"),
+            Values(spv::Scope::Subgroup), Values("%struct_null"),
+            Values("Value must be a scalar or vector of integer, "
+                   "floating-point, or boolean type")));
+
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBroadcastGood, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBroadcast",
+                                        "OpGroupNonUniformQuadBroadcast",
+                                        "OpGroupNonUniformQuadSwap"),
+                                 Values("%bool", "%u32", "%int", "%float",
+                                        "%u32vec4", "%u32vec3", "%v2bool",
+                                        "%v4float", "%v4int"),
+                                 Values(spv::Scope::Subgroup),
+                                 Values("match_res %u32_0"), Values("")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformBroadcastShuffleBadResultType, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformBroadcast", "OpGroupNonUniformShuffle",
+                   "OpGroupNonUniformShuffleXor", "OpGroupNonUniformShuffleUp",
+                   "OpGroupNonUniformShuffleDown",
+                   "OpGroupNonUniformQuadBroadcast",
+                   "OpGroupNonUniformQuadSwap"),
+            Values("%void", "%struct"), Values(spv::Scope::Subgroup),
+            Values("%u32_0 %u32_0"),
+            Values("Result must be a scalar or vector of integer, "
+                   "floating-point, or boolean type")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformBroadcastShuffleBadOperand1, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformBroadcast", "OpGroupNonUniformShuffle",
+                   "OpGroupNonUniformShuffleXor", "OpGroupNonUniformShuffleUp",
+                   "OpGroupNonUniformShuffleDown",
+                   "OpGroupNonUniformQuadBroadcast",
+                   "OpGroupNonUniformQuadSwap"),
+            Values("%bool"), Values(spv::Scope::Subgroup),
+            Values("%u32_0 %u32_0", "%int_0 %u32_0", "%float_0 %u32_0",
+                   "%u32vec4_null %u32_0", "%u32vec3_null %u32_0",
+                   "%v2bool_false %u32_0", "%v4float_null %u32_0",
+                   "%struct_null %u32_0", "%v4int_null %u32_0"),
+            Values("The type of Value must match the Result type")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformBroadcastShuffleBadOperand2, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformBroadcast", "OpGroupNonUniformShuffle",
+                   "OpGroupNonUniformShuffleXor", "OpGroupNonUniformShuffleUp",
+                   "OpGroupNonUniformShuffleDown",
+                   "OpGroupNonUniformQuadBroadcast",
+                   "OpGroupNonUniformQuadSwap"),
+            Values("%bool"), Values(spv::Scope::Subgroup),
+            Values("%true %true", "%true %int_0", "%true %float_0",
+                   "%true %u32vec4_null", "%true %u32vec3_null",
+                   "%true %v4float_null", "%true %v2bool_false",
+                   "%true %struct_null", "%true %v4int_null"),
+            Values("must be an unsigned integer scalar")));
+
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBroadcastShuffleOperand2NotConstant,
+                         GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBroadcast",
+                                        "OpGroupNonUniformQuadBroadcast",
+                                        "OpGroupNonUniformQuadSwap"),
+                                 Values("%bool"), Values(spv::Scope::Subgroup),
+                                 Values("%true %u32_undef"),
+                                 Values("must be a constant instruction")));
+
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBroadcastFirstGood, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBroadcastFirst"),
+                                 Values("%bool", "%u32", "%int", "%float",
+                                        "%u32vec4", "%u32vec3", "%v2bool",
+                                        "%v4float", "%v4int"),
+                                 Values(spv::Scope::Subgroup),
+                                 Values("match_res"), Values("")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformBroadcasFirsttBadResultType, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformBroadcastFirst"),
+            Values("%void", "%struct"), Values(spv::Scope::Subgroup),
+            Values("%u32_0"),
+            Values("Result must be a scalar or vector of integer, "
+                   "floating-point, or boolean type")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformBroadcastBadOperand, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformBroadcastFirst"), Values("%bool"),
+            Values(spv::Scope::Subgroup),
+            Values("%u32_0", "%int_0", "%float_0", "%u32vec4_null",
+                   "%u32vec3_null", "%v2bool_false", "%v4float_null",
+                   "%struct_null", "%v4int_null"),
+            Values("The type of Value must match the Result type")));
+
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBallotGood, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBallot"),
+                                 Values("%u32vec4"),
+                                 Values(spv::Scope::Subgroup),
+                                 Values("%true", "%false"), Values("")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformBallotBadResultType, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformBallot"),
+            Values("%void", "%bool", "%u32", "%int", "%float", "%u32vec3",
+                   "%v2bool", "%v4float", "%struct", "%v4int"),
+            Values(spv::Scope::Subgroup), Values("%true", "%false"),
+            Values("Result must be a 4-component unsigned integer vector")));
+
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBallotBadOperand, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBallot"),
+                                 Values("%u32vec4"),
+                                 Values(spv::Scope::Subgroup),
+                                 Values("%u32_0", "%int_0", "%float_0",
+                                        "%u32vec4_null", "%u32vec3_null",
+                                        "%v2bool_false", "%v4float_null",
+                                        "%struct_null", "%v4int_null"),
+                                 Values("Predicate must be a boolean scalar")));
+
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformInverseBallotGood, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformInverseBallot"),
+                                 Values("%bool"), Values(spv::Scope::Subgroup),
+                                 Values("%u32vec4_null"), Values("")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformInverseBallotBadResultType, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformInverseBallot"),
+            Values("%void", "%u32", "%int", "%float", "%u32vec4", "%u32vec3",
+                   "%v2bool", "%v4float", "%struct", "%v4int"),
+            Values(spv::Scope::Subgroup), Values("%u32vec4_null"),
+            Values("Result must be a boolean scalar")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformInverseBallotBadOperand, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformInverseBallot"), Values("%bool"),
+            Values(spv::Scope::Subgroup),
+            Values("%true", "%false", "%u32_0", "%int_0", "%float_0",
+                   "%u32vec3_null", "%v2bool_false", "%v4float_null",
+                   "%struct_null", "%v4int_null"),
+            Values("Value must be a 4-component unsigned integer vector")));
+
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBallotBitExtractGood, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBallotBitExtract"),
+                                 Values("%bool"), Values(spv::Scope::Subgroup),
+                                 Values("%u32vec4_null %u32_0"), Values("")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformBallotBitExtractBadResultType, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformBallotBitExtract"),
+            Values("%void", "%u32", "%int", "%float", "%u32vec4", "%u32vec3",
+                   "%v2bool", "%v4float", "%struct", "%v4int"),
+            Values(spv::Scope::Subgroup), Values("%u32vec4_null %u32_0"),
+            Values("Result must be a boolean scalar")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformBallotBitExtractBadOperand1, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformBallotBitExtract"), Values("%bool"),
+            Values(spv::Scope::Subgroup),
+            Values("%true %u32_0", "%false %u32_0", "%u32_0 %u32_0",
+                   "%int_0 %u32_0", "%float_0 %u32_0", "%u32vec3_null %u32_0",
+                   "%v2bool_false %u32_0", "%v4float_null %u32_0",
+                   "%struct_null %u32_0", "%v4int_null %u32_0"),
+            Values("Value must be a 4-component unsigned integer vector")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformBallotBitExtractBadOperand2, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformBallotBitExtract"), Values("%bool"),
+            Values(spv::Scope::Subgroup),
+            Values("%u32vec4_null %true", "%u32vec4_null %false",
+                   "%u32vec4_null %int_0", "%u32vec4_null %float_0",
+                   "%u32vec4_null %u32vec3_null", "%u32vec4_null %v2bool_false",
+                   "%u32vec4_null %v4float_null", "%u32vec4_null %struct_null",
+                   "%u32vec4_null %v4int_null"),
+            Values("Id must be an unsigned integer scalar")));
+
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBallotFindGood, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBallotFindLSB",
+                                        "OpGroupNonUniformBallotFindMSB"),
+                                 Values("%u32"), Values(spv::Scope::Subgroup),
+                                 Values("%u32vec4_null"), Values("")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformBallotFindBadResultType, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformBallotFindLSB",
+                   "OpGroupNonUniformBallotFindMSB"),
+            Values("%void", "%bool", "%int", "%float", "%u32vec4", "%u32vec3",
+                   "%v2bool", "%v4float", "%struct", "%v4int"),
+            Values(spv::Scope::Subgroup), Values("%u32vec4_null"),
+            Values("Result must be an unsigned integer scalar")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformBallotFindBadOperand, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformBallotFindLSB",
+                   "OpGroupNonUniformBallotFindMSB"),
+            Values("%u32"), Values(spv::Scope::Subgroup),
+            Values("%true", "%false", "%u32_0", "%int_0", "%float_0",
+                   "%u32vec3_null", "%v2bool_false", "%v4float_null",
+                   "%struct_null", "%v4int_null"),
+            Values("Value must be a 4-component unsigned integer vector")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformIntegerArithmeticGood, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformIAdd", "OpGroupNonUniformIMul",
+                   "OpGroupNonUniformSMin", "OpGroupNonUniformSMax",
+                   "OpGroupNonUniformBitwiseAnd", "OpGroupNonUniformBitwiseOr",
+                   "OpGroupNonUniformBitwiseXor"),
+            Values("%u32", "%int", "%u32vec4", "%u32vec3", "%v4int"),
+            Values(spv::Scope::Subgroup),
+            Values("Reduce match_res", "InclusiveScan match_res",
+                   "ExclusiveScan match_res",
+                   "ClusteredReduce match_res %u32_0",
+                   "PartitionedReduceNV match_res %u32vec4_null",
+                   "PartitionedInclusiveScanNV match_res %u32vec4_null",
+                   "PartitionedExclusiveScanNV match_res %v4int_null"),
+            Values("")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformIntegerArithmeticBadResultType, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformIAdd", "OpGroupNonUniformIMul",
+                   "OpGroupNonUniformSMin", "OpGroupNonUniformSMax",
+                   "OpGroupNonUniformBitwiseAnd", "OpGroupNonUniformBitwiseOr",
+                   "OpGroupNonUniformBitwiseXor"),
+            Values("%bool", "%float", "%v4float", "%struct"),
+            Values(spv::Scope::Subgroup),
+            Values("Reduce match_res", "InclusiveScan match_res",
+                   "ExclusiveScan match_res",
+                   "ClusteredReduce match_res %u32_0"),
+            Values("Result must be an integer scalar or vector")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformIntegerArithmeticBadValue, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformIAdd", "OpGroupNonUniformIMul",
+                   "OpGroupNonUniformSMin", "OpGroupNonUniformSMax",
+                   "OpGroupNonUniformBitwiseAnd", "OpGroupNonUniformBitwiseOr",
+                   "OpGroupNonUniformBitwiseXor"),
+            Values("%int", "%u32vec4", "%u32vec3", "%v4int"),
+            Values(spv::Scope::Subgroup),
+            Values("Reduce %u32_0", "InclusiveScan %u32_0",
+                   "ExclusiveScan %u32_0", "ClusteredReduce %u32_0 %u32_0"),
+            Values("The type of Value must match the Result type")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformIntegerArithmeticMissingClusterSize, GroupNonUniform,
+    Combine(
+        Values("OpGroupNonUniformIAdd", "OpGroupNonUniformIMul",
+               "OpGroupNonUniformSMin", "OpGroupNonUniformUMin",
+               "OpGroupNonUniformSMax", "OpGroupNonUniformUMax",
+               "OpGroupNonUniformBitwiseAnd", "OpGroupNonUniformBitwiseOr",
+               "OpGroupNonUniformBitwiseXor"),
+        Values("%u32"), Values(spv::Scope::Subgroup),
+        Values("ClusteredReduce match_res"),
+        Values(
+            "ClusterSize must be present when Operation is ClusteredReduce")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformIntegerArithmeticMissingBallot, GroupNonUniform,
+    Combine(
+        Values("OpGroupNonUniformIAdd", "OpGroupNonUniformIMul",
+               "OpGroupNonUniformSMin", "OpGroupNonUniformUMin",
+               "OpGroupNonUniformSMax", "OpGroupNonUniformUMax",
+               "OpGroupNonUniformBitwiseAnd", "OpGroupNonUniformBitwiseOr",
+               "OpGroupNonUniformBitwiseXor"),
+        Values("%u32"), Values(spv::Scope::Subgroup),
+        Values("PartitionedReduceNV match_res",
+               "PartitionedInclusiveScanNV match_res",
+               "PartitionedExclusiveScanNV match_res"),
+        Values("Ballot must be present when Operation is PartitionedReduceNV, "
+               "PartitionedInclusiveScanNV, or PartitionedExclusiveScanNV")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformIntegerArithmeticBadClusterSizeType, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformIAdd", "OpGroupNonUniformIMul",
+                   "OpGroupNonUniformSMin", "OpGroupNonUniformUMin",
+                   "OpGroupNonUniformSMax", "OpGroupNonUniformUMax",
+                   "OpGroupNonUniformBitwiseAnd", "OpGroupNonUniformBitwiseOr",
+                   "OpGroupNonUniformBitwiseXor"),
+            Values("%u32"), Values(spv::Scope::Subgroup),
+            Values("ClusteredReduce match_res %true",
+                   "ClusteredReduce match_res %false",
+                   "ClusteredReduce match_res %int_0",
+                   "ClusteredReduce match_res %float_0",
+                   "ClusteredReduce match_res %u32vec4_null",
+                   "ClusteredReduce match_res %u32vec3_null",
+                   "ClusteredReduce match_res %v2bool_false",
+                   "ClusteredReduce match_res %v4float_null",
+                   "ClusteredReduce match_res %struct_null",
+                   "ClusteredReduce match_res %v4int_null"),
+            Values("ClusterSize must be an unsigned integer scalar")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformIntegerArithmeticBadBallotType, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformIAdd", "OpGroupNonUniformIMul",
+                   "OpGroupNonUniformSMin", "OpGroupNonUniformUMin",
+                   "OpGroupNonUniformSMax", "OpGroupNonUniformUMax",
+                   "OpGroupNonUniformBitwiseAnd", "OpGroupNonUniformBitwiseOr",
+                   "OpGroupNonUniformBitwiseXor"),
+            Values("%u32"), Values(spv::Scope::Subgroup),
+            Values("PartitionedReduceNV match_res %true",
+                   "PartitionedReduceNV match_res %false",
+                   "PartitionedReduceNV match_res %int_0",
+                   "PartitionedReduceNV match_res %float_0",
+                   "PartitionedReduceNV match_res %u32_0",
+                   "PartitionedReduceNV match_res %u32vec3_null",
+                   "PartitionedReduceNV match_res %v2bool_false",
+                   "PartitionedReduceNV match_res %v4float_null",
+                   "PartitionedReduceNV match_res %struct_null"),
+            Values("Ballot must be a 4-component integer vector")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformIntegerArithmeticClusterSizeNotConstant, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformIAdd", "OpGroupNonUniformIMul",
+                   "OpGroupNonUniformSMin", "OpGroupNonUniformUMin",
+                   "OpGroupNonUniformSMax", "OpGroupNonUniformUMax",
+                   "OpGroupNonUniformBitwiseAnd", "OpGroupNonUniformBitwiseOr",
+                   "OpGroupNonUniformBitwiseXor"),
+            Values("%u32"), Values(spv::Scope::Subgroup),
+            Values("ClusteredReduce match_res %u32_undef"),
+            Values("ClusterSize must be a constant instruction")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformUnsignedIntegerArithmeticGood, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformUMin", "OpGroupNonUniformUMax"),
+            Values("%u32", "%u32vec4", "%u32vec3"),
+            Values(spv::Scope::Subgroup),
+            Values("Reduce match_res", "InclusiveScan match_res",
+                   "ExclusiveScan match_res",
+                   "ClusteredReduce match_res %u32_0"),
+            Values("")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformUnsignedIntegerArithmeticBadResultType, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformUMin", "OpGroupNonUniformUMax"),
+            Values("%bool", "%int", "%float", "%v4float", "%struct", "%v4int"),
+            Values(spv::Scope::Subgroup),
+            Values("Reduce match_res", "InclusiveScan match_res",
+                   "ExclusiveScan match_res",
+                   "ClusteredReduce match_res %u32_0"),
+            Values("Result must be an unsigned integer scalar or vector")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformUnsignedIntegerArithmeticBadValue, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformUMin", "OpGroupNonUniformUMax"),
+            Values("%u32vec4", "%u32vec3"), Values(spv::Scope::Subgroup),
+            Values("Reduce %u32_0", "InclusiveScan %u32_0",
+                   "ExclusiveScan %u32_0", "ClusteredReduce %u32_0 %u32_0"),
+            Values("The type of Value must match the Result type")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformFloatArithmeticGood, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformFAdd", "OpGroupNonUniformFMul",
+                   "OpGroupNonUniformFMin", "OpGroupNonUniformFMax"),
+            Values("%float", "%v4float"), Values(spv::Scope::Subgroup),
+            Values("Reduce match_res", "InclusiveScan match_res",
+                   "ExclusiveScan match_res",
+                   "ClusteredReduce match_res %u32_0"),
+            Values("")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformFloatArithmeticBadResultType, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformFAdd", "OpGroupNonUniformFMul",
+                   "OpGroupNonUniformFMin", "OpGroupNonUniformFMax"),
+            Values("%bool", "%u32", "%int", "%u32vec4", "%u32vec3", "%struct",
+                   "%v4int"),
+            Values(spv::Scope::Subgroup),
+            Values("Reduce match_res", "InclusiveScan match_res",
+                   "ExclusiveScan match_res",
+                   "ClusteredReduce match_res %u32_0"),
+            Values("Result must be a floating-point scalar or vector")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformFloatArithmeticBadValue, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformFAdd", "OpGroupNonUniformFMul",
+                   "OpGroupNonUniformFMin", "OpGroupNonUniformFMax"),
+            Values("%v4float"), Values(spv::Scope::Subgroup),
+            Values("Reduce %float_0", "InclusiveScan %float_0",
+                   "ExclusiveScan %float_0", "ClusteredReduce %float_0 %u32_0"),
+            Values("The type of Value must match the Result type")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformFloatArithmeticMissingClusterSize, GroupNonUniform,
+    Combine(
+        Values("OpGroupNonUniformFAdd", "OpGroupNonUniformFMul",
+               "OpGroupNonUniformFMin", "OpGroupNonUniformFMax"),
+        Values("%float"), Values(spv::Scope::Subgroup),
+        Values("ClusteredReduce match_res"),
+        Values(
+            "ClusterSize must be present when Operation is ClusteredReduce")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformFloatArithmeticBadClusterSizeType, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformFAdd", "OpGroupNonUniformFMul",
+                   "OpGroupNonUniformFMin", "OpGroupNonUniformFMax"),
+            Values("%float"), Values(spv::Scope::Subgroup),
+            Values("ClusteredReduce match_res %true",
+                   "ClusteredReduce match_res %false",
+                   "ClusteredReduce match_res %int_0",
+                   "ClusteredReduce match_res %float_0",
+                   "ClusteredReduce match_res %u32vec4_null",
+                   "ClusteredReduce match_res %u32vec3_null",
+                   "ClusteredReduce match_res %v2bool_false",
+                   "ClusteredReduce match_res %v4float_null",
+                   "ClusteredReduce match_res %struct_null",
+                   "ClusteredReduce match_res %v4int_null"),
+            Values("ClusterSize must be an unsigned integer scalar")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformFloatArithmeticClusterSizeNotConstant, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformFAdd", "OpGroupNonUniformFMul",
+                   "OpGroupNonUniformFMin", "OpGroupNonUniformFMax"),
+            Values("%float"), Values(spv::Scope::Subgroup),
+            Values("ClusteredReduce match_res %u32_undef"),
+            Values("ClusterSize must be a constant instruction")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformBooleanArithmeticGood, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformLogicalAnd", "OpGroupNonUniformLogicalOr",
+                   "OpGroupNonUniformLogicalXor"),
+            Values("%bool", "%v2bool"), Values(spv::Scope::Subgroup),
+            Values("Reduce match_res", "InclusiveScan match_res",
+                   "ExclusiveScan match_res",
+                   "ClusteredReduce match_res %u32_0"),
+            Values("")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformBooleanArithmeticBadResultType, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformLogicalAnd", "OpGroupNonUniformLogicalOr",
+                   "OpGroupNonUniformLogicalXor"),
+            Values("%u32", "%int", "%float", "%u32vec4", "%u32vec3", "%struct",
+                   "%v4float", "%v4int"),
+            Values(spv::Scope::Subgroup),
+            Values("Reduce match_res", "InclusiveScan match_res",
+                   "ExclusiveScan match_res",
+                   "ClusteredReduce match_res %u32_0"),
+            Values("Result must be a boolean scalar or vector")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformBooleanArithmeticBadValue, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformLogicalAnd", "OpGroupNonUniformLogicalOr",
+                   "OpGroupNonUniformLogicalXor"),
+            Values("%v2bool"), Values(spv::Scope::Subgroup),
+            Values("Reduce %true", "InclusiveScan %true",
+                   "ExclusiveScan %false", "ClusteredReduce %false %u32_0"),
+            Values("The type of Value must match the Result type")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformBooleanArithmeticMissingClusterSize, GroupNonUniform,
+    Combine(
+        Values("OpGroupNonUniformLogicalAnd", "OpGroupNonUniformLogicalOr",
+               "OpGroupNonUniformLogicalXor"),
+        Values("%bool"), Values(spv::Scope::Subgroup),
+        Values("ClusteredReduce match_res"),
+        Values(
+            "ClusterSize must be present when Operation is ClusteredReduce")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformBooleanArithmeticBadClusterSizeType, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformLogicalAnd", "OpGroupNonUniformLogicalOr",
+                   "OpGroupNonUniformLogicalXor"),
+            Values("%bool"), Values(spv::Scope::Subgroup),
+            Values("ClusteredReduce match_res %true",
+                   "ClusteredReduce match_res %false",
+                   "ClusteredReduce match_res %int_0",
+                   "ClusteredReduce match_res %float_0",
+                   "ClusteredReduce match_res %u32vec4_null",
+                   "ClusteredReduce match_res %u32vec3_null",
+                   "ClusteredReduce match_res %v2bool_false",
+                   "ClusteredReduce match_res %v4float_null",
+                   "ClusteredReduce match_res %struct_null",
+                   "ClusteredReduce match_res %v4int_null"),
+            Values("ClusterSize must be an unsigned integer scalar")));
+
+INSTANTIATE_TEST_SUITE_P(
+    GroupNonUniformBooleanArithmeticClusterSizeNotConstant, GroupNonUniform,
+    Combine(Values("OpGroupNonUniformLogicalAnd", "OpGroupNonUniformLogicalOr",
+                   "OpGroupNonUniformLogicalXor"),
+            Values("%bool"), Values(spv::Scope::Subgroup),
+            Values("ClusteredReduce match_res %u32_undef"),
+            Values("ClusterSize must be a constant instruction")));
+
 TEST_F(ValidateGroupNonUniform, VulkanGroupNonUniformBallotBitCountOperation) {
   std::string test = R"(
 OpCapability Shader
@@ -327,6 +904,146 @@ OpFunctionEnd
           "be only: Reduce, InclusiveScan, or ExclusiveScan."));
 }
 
+TEST_F(ValidateGroupNonUniform, BroadcastNonConstantSpv1p4) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability GroupNonUniformBallot
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %var
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%subgroup = OpConstant %int 3
+%struct = OpTypeStruct %int
+%ptr_struct = OpTypePointer StorageBuffer %struct
+%ptr_int = OpTypePointer StorageBuffer %int
+%var = OpVariable %ptr_struct StorageBuffer
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%gep = OpAccessChain %ptr_int %var %int_0
+%ld = OpLoad %int %gep
+%broadcast = OpGroupNonUniformBroadcast %int %subgroup %int_0 %ld
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Before SPIR-V 1.5, Id must be a constant instruction"));
+}
+
+TEST_F(ValidateGroupNonUniform, BroadcastNonConstantSpv1p5) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability GroupNonUniformBallot
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %var
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%subgroup = OpConstant %int 3
+%struct = OpTypeStruct %int
+%ptr_struct = OpTypePointer StorageBuffer %struct
+%ptr_int = OpTypePointer StorageBuffer %int
+%var = OpVariable %ptr_struct StorageBuffer
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%gep = OpAccessChain %ptr_int %var %int_0
+%ld = OpLoad %int %gep
+%broadcast = OpGroupNonUniformBroadcast %int %subgroup %int_0 %ld
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_5);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
+}
+
+TEST_F(ValidateGroupNonUniform, QuadBroadcastNonConstantSpv1p4) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability GroupNonUniformQuad
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %var
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%subgroup = OpConstant %int 3
+%struct = OpTypeStruct %int
+%ptr_struct = OpTypePointer StorageBuffer %struct
+%ptr_int = OpTypePointer StorageBuffer %int
+%var = OpVariable %ptr_struct StorageBuffer
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%gep = OpAccessChain %ptr_int %var %int_0
+%ld = OpLoad %int %gep
+%broadcast = OpGroupNonUniformQuadBroadcast %int %subgroup %int_0 %ld
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Before SPIR-V 1.5, Index must be a constant instruction"));
+}
+
+TEST_F(ValidateGroupNonUniform, QuadBroadcastNonConstantSpv1p5) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability GroupNonUniformQuad
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %var
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%subgroup = OpConstant %int 3
+%struct = OpTypeStruct %int
+%ptr_struct = OpTypePointer StorageBuffer %struct
+%ptr_int = OpTypePointer StorageBuffer %int
+%var = OpVariable %ptr_struct StorageBuffer
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%gep = OpAccessChain %ptr_int %var %int_0
+%ld = OpLoad %int %gep
+%broadcast = OpGroupNonUniformQuadBroadcast %int %subgroup %int_0 %ld
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_5);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools