<< " requires one of these capabilities: " << required_capabilities;
}
+// Returns capabilities that enable an opcode. An empty result is interpreted
+// as no prohibition of use of the opcode. If the result is non-empty, then
+// the opcode may only be used if at least one of the capabilities is specified
+// by the module.
+CapabilitySet EnablingCapabilitiesForOp(const ValidationState_t& state,
+ SpvOp opcode) {
+ // Exceptions for SPV_AMD_shader_ballot
+ switch (opcode) {
+ // Normally these would require Group capability
+ case SpvOpGroupIAddNonUniformAMD:
+ case SpvOpGroupFAddNonUniformAMD:
+ case SpvOpGroupFMinNonUniformAMD:
+ case SpvOpGroupUMinNonUniformAMD:
+ case SpvOpGroupSMinNonUniformAMD:
+ case SpvOpGroupFMaxNonUniformAMD:
+ case SpvOpGroupUMaxNonUniformAMD:
+ case SpvOpGroupSMaxNonUniformAMD:
+ if (state.HasExtension(libspirv::kSPV_AMD_shader_ballot))
+ return CapabilitySet();
+ break;
+ default:
+ break;
+ }
+ // Look it up in the grammar
+ spv_opcode_desc opcode_desc = {};
+ if (SPV_SUCCESS == state.grammar().lookupOpcode(opcode, &opcode_desc)) {
+ CapabilitySet opcode_caps(opcode_desc->numCapabilities,
+ opcode_desc->capabilities);
+ return opcode_caps;
+ }
+ return CapabilitySet();
+}
+
// Returns an operand's required capabilities.
CapabilitySet RequiredCapabilities(const ValidationState_t& state,
spv_operand_type_t type, uint32_t operand) {
CapabilitySet result(operand_desc->numCapabilities,
operand_desc->capabilities);
- // Allow FPRoundingMode decoration if requested
+ // Allow FPRoundingMode decoration if requested.
if (state.features().free_fp_rounding_mode &&
type == SPV_OPERAND_TYPE_DECORATION &&
operand_desc->value == SpvDecorationFPRoundingMode) {
return CapabilitySet();
}
+ // Allow certain group operations if requested.
+ if (state.features().group_ops_reduce_and_scans &&
+ type == SPV_OPERAND_TYPE_GROUP_OPERATION &&
+ (operand <= uint32_t(SpvGroupOperationExclusiveScan))) {
+ return CapabilitySet();
+ }
return result;
}
spv_result_t CapabilityCheck(ValidationState_t& _,
const spv_parsed_instruction_t* inst) {
- spv_opcode_desc opcode_desc = {};
const SpvOp opcode = static_cast<SpvOp>(inst->opcode);
- if (SPV_SUCCESS == _.grammar().lookupOpcode(opcode, &opcode_desc)) {
- CapabilitySet opcode_caps(opcode_desc->numCapabilities,
- opcode_desc->capabilities);
- if (!_.HasAnyOfCapabilities(opcode_caps))
- return _.diag(SPV_ERROR_INVALID_CAPABILITY)
- << "Opcode " << spvOpcodeString(opcode)
- << " requires one of these capabilities: "
- << ToString(opcode_caps, _.grammar());
+ CapabilitySet opcode_caps = EnablingCapabilitiesForOp(_, opcode);
+ if (!_.HasAnyOfCapabilities(opcode_caps)) {
+ return _.diag(SPV_ERROR_INVALID_CAPABILITY)
+ << "Opcode " << spvOpcodeString(opcode)
+ << " requires one of these capabilities: "
+ << ToString(opcode_caps, _.grammar());
}
for (int i = 0; i < inst->num_operands; ++i) {
const auto& operand = inst->operands[i];
using ::testing::HasSubstr;
using ::testing::Not;
using ::testing::Values;
+using ::testing::ValuesIn;
using std::string;
EXPECT_THAT(getDiagnosticString(), HasSubstr("SPV_KHR_device_group"));
}
+
+using ValidateAMDShaderBallotCapabilities = spvtest::ValidateBase<string>;
+
+// Returns a vector of strings for the prefix of a SPIR-V assembly shader
+// that can use the group instructions introduced by SPV_AMD_shader_ballot.
+std::vector<string> ShaderPartsForAMDShaderBallot() {
+ return std::vector<string>{R"(
+ OpCapability Shader
+ OpCapability Linkage
+ )",
+ R"(
+ OpMemoryModel Logical GLSL450
+ %float = OpTypeFloat 32
+ %uint = OpTypeInt 32 0
+ %int = OpTypeInt 32 1
+ %scope = OpConstant %uint 3
+ %uint_const = OpConstant %uint 42
+ %int_const = OpConstant %uint 45
+ %float_const = OpConstant %float 3.5
+
+ %void = OpTypeVoid
+ %fn_ty = OpTypeFunction %void
+ %fn = OpFunction %void None %fn_ty
+ %entry = OpLabel
+ )"};
+}
+
+// Returns a list of SPIR-V assembly strings, where each uses only types
+// and IDs that can fit with a shader made from parts from the result
+// of ShaderPartsForAMDShaderBallot.
+std::vector<string> AMDShaderBallotGroupInstructions() {
+ return std::vector<string>{
+ "%iadd_reduce = OpGroupIAddNonUniformAMD %uint %scope Reduce %uint_const",
+ "%iadd_iscan = OpGroupIAddNonUniformAMD %uint %scope InclusiveScan %uint_const",
+ "%iadd_escan = OpGroupIAddNonUniformAMD %uint %scope ExclusiveScan %uint_const",
+
+ "%fadd_reduce = OpGroupFAddNonUniformAMD %float %scope Reduce %float_const",
+ "%fadd_iscan = OpGroupFAddNonUniformAMD %float %scope InclusiveScan %float_const",
+ "%fadd_escan = OpGroupFAddNonUniformAMD %float %scope ExclusiveScan %float_const",
+
+ "%fmin_reduce = OpGroupFMinNonUniformAMD %float %scope Reduce %float_const",
+ "%fmin_iscan = OpGroupFMinNonUniformAMD %float %scope InclusiveScan %float_const",
+ "%fmin_escan = OpGroupFMinNonUniformAMD %float %scope ExclusiveScan %float_const",
+
+ "%umin_reduce = OpGroupUMinNonUniformAMD %uint %scope Reduce %uint_const",
+ "%umin_iscan = OpGroupUMinNonUniformAMD %uint %scope InclusiveScan %uint_const",
+ "%umin_escan = OpGroupUMinNonUniformAMD %uint %scope ExclusiveScan %uint_const",
+
+ "%smin_reduce = OpGroupUMinNonUniformAMD %int %scope Reduce %int_const",
+ "%smin_iscan = OpGroupUMinNonUniformAMD %int %scope InclusiveScan %int_const",
+ "%smin_escan = OpGroupUMinNonUniformAMD %int %scope ExclusiveScan %int_const",
+
+ "%fmax_reduce = OpGroupFMaxNonUniformAMD %float %scope Reduce %float_const",
+ "%fmax_iscan = OpGroupFMaxNonUniformAMD %float %scope InclusiveScan %float_const",
+ "%fmax_escan = OpGroupFMaxNonUniformAMD %float %scope ExclusiveScan %float_const",
+
+ "%umax_reduce = OpGroupUMaxNonUniformAMD %uint %scope Reduce %uint_const",
+ "%umax_iscan = OpGroupUMaxNonUniformAMD %uint %scope InclusiveScan %uint_const",
+ "%umax_escan = OpGroupUMaxNonUniformAMD %uint %scope ExclusiveScan %uint_const",
+
+ "%smax_reduce = OpGroupUMaxNonUniformAMD %int %scope Reduce %int_const",
+ "%smax_iscan = OpGroupUMaxNonUniformAMD %int %scope InclusiveScan %int_const",
+ "%smax_escan = OpGroupUMaxNonUniformAMD %int %scope ExclusiveScan %int_const"
+ };
+}
+
+TEST_P(ValidateAMDShaderBallotCapabilities, ExpectSuccess) {
+ // Succeed because the module specifies the SPV_AMD_shader_ballot extension.
+ auto parts = ShaderPartsForAMDShaderBallot();
+
+ const string assembly = parts[0] + "OpExtension \"SPV_AMD_shader_ballot\"\n" +
+ parts[1] + GetParam() + "\nOpReturn OpFunctionEnd";
+
+ CompileSuccessfully(assembly.c_str());
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
+}
+
+INSTANTIATE_TEST_CASE_P(ExpectSuccess, ValidateAMDShaderBallotCapabilities,
+ ValuesIn(AMDShaderBallotGroupInstructions()));
+
+TEST_P(ValidateAMDShaderBallotCapabilities, ExpectFailure) {
+ // Fail because the module does not specify the SPV_AMD_shader_ballot extension.
+ auto parts = ShaderPartsForAMDShaderBallot();
+
+ const string assembly =
+ parts[0] + parts[1] + GetParam() + "\nOpReturn OpFunctionEnd";
+
+ CompileSuccessfully(assembly.c_str());
+ EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY, ValidateInstructions());
+
+ // Make sure we get an appropriate error message.
+ // Find just the opcode name, skipping over the "Op" part.
+ auto prefix_with_opcode = GetParam().substr(GetParam().find("Group"));
+ auto opcode = prefix_with_opcode.substr(0, prefix_with_opcode.find(' '));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr(string("Opcode " + opcode +
+ " requires one of these capabilities: Groups")));
+}
+
+INSTANTIATE_TEST_CASE_P(ExpectFailure, ValidateAMDShaderBallotCapabilities,
+ ValuesIn(AMDShaderBallotGroupInstructions()));
+
} // anonymous namespace