Add support for SPV_KHR_subgroup_rotate (#4786)

- Add assembler/disassembler support
- Add validator support

Signed-off-by: Kevin Petit <kevin.petit@arm.com>
Change-Id: Iffcedd5d5e636a0e128a5906ffe634dd85727de1
This commit is contained in:
Kévin Petit 2022-05-05 13:58:05 +01:00 committed by GitHub
parent 2c7fb9707b
commit 7014be600c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 431 additions and 0 deletions

View File

@ -528,6 +528,7 @@ bool spvOpcodeIsNonUniformGroupOperation(SpvOp opcode) {
case SpvOpGroupNonUniformLogicalXor:
case SpvOpGroupNonUniformQuadBroadcast:
case SpvOpGroupNonUniformQuadSwap:
case SpvOpGroupNonUniformRotateKHR:
return true;
default:
return false;

View File

@ -63,6 +63,59 @@ spv_result_t ValidateGroupNonUniformBallotBitCount(ValidationState_t& _,
return SPV_SUCCESS;
}
spv_result_t ValidateGroupNonUniformRotateKHR(ValidationState_t& _,
const Instruction* inst) {
// Scope is already checked by ValidateExecutionScope() above.
const uint32_t result_type = inst->type_id();
if (!_.IsIntScalarOrVectorType(result_type) &&
!_.IsFloatScalarOrVectorType(result_type) &&
!_.IsBoolScalarOrVectorType(result_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected Result Type to be a scalar or vector of "
"floating-point, integer or boolean type.";
}
const uint32_t value_type = _.GetTypeId(inst->GetOperandAs<uint32_t>(3));
if (value_type != result_type) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Result Type must be the same as the type of Value.";
}
const uint32_t delta_type = _.GetTypeId(inst->GetOperandAs<uint32_t>(4));
if (!_.IsUnsignedIntScalarType(delta_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Delta must be a scalar of integer type, whose Signedness "
"operand is 0.";
}
if (inst->words().size() > 6) {
const uint32_t cluster_size_op_id = inst->GetOperandAs<uint32_t>(5);
const uint32_t cluster_size_type = _.GetTypeId(cluster_size_op_id);
if (!_.IsUnsignedIntScalarType(cluster_size_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "ClusterSize must be a scalar of integer type, whose "
"Signedness operand is 0.";
}
uint64_t cluster_size;
if (!_.GetConstantValUint64(cluster_size_op_id, &cluster_size)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "ClusterSize must come from a constant instruction.";
}
if ((cluster_size == 0) || ((cluster_size & (cluster_size - 1)) != 0)) {
return _.diag(SPV_WARNING, inst)
<< "Behavior is undefined unless ClusterSize is at least 1 and a "
"power of 2.";
}
// TODO(kpet) Warn about undefined behavior when ClusterSize is greater than
// the declared SubGroupSize
}
return SPV_SUCCESS;
}
} // namespace
// Validates correctness of non-uniform group instructions.
@ -79,6 +132,8 @@ spv_result_t NonUniformPass(ValidationState_t& _, const Instruction* inst) {
switch (opcode) {
case SpvOpGroupNonUniformBallotBitCount:
return ValidateGroupNonUniformBallotBitCount(_, inst);
case SpvOpGroupNonUniformRotateKHR:
return ValidateGroupNonUniformRotateKHR(_, inst);
default:
break;
}

View File

@ -1075,5 +1075,27 @@ INSTANTIATE_TEST_SUITE_P(
{1, 2, 3, SpvGroupOperationReduce, 4})},
})));
// SPV_KHR_subgroup_rotate
INSTANTIATE_TEST_SUITE_P(
SPV_KHR_subgroup_rotate, ExtensionRoundTripTest,
Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_6,
SPV_ENV_VULKAN_1_0, SPV_ENV_VULKAN_1_1, SPV_ENV_VULKAN_1_2,
SPV_ENV_VULKAN_1_3, SPV_ENV_OPENCL_2_1),
ValuesIn(std::vector<AssemblyCase>{
{"OpExtension \"SPV_KHR_subgroup_rotate\"\n",
MakeInstruction(SpvOpExtension,
MakeVector("SPV_KHR_subgroup_rotate"))},
{"OpCapability GroupNonUniformRotateKHR\n",
MakeInstruction(SpvOpCapability,
{SpvCapabilityGroupNonUniformRotateKHR})},
{"%2 = OpGroupNonUniformRotateKHR %1 %3 %4 %5\n",
MakeInstruction(SpvOpGroupNonUniformRotateKHR,
{1, 2, 3, 4, 5})},
{"%2 = OpGroupNonUniformRotateKHR %1 %3 %4 %5 %6\n",
MakeInstruction(SpvOpGroupNonUniformRotateKHR,
{1, 2, 3, 4, 5, 6})},
})));
} // namespace
} // namespace spvtools

View File

@ -45,6 +45,7 @@ add_spvtools_unittest(TARGET val_abcde
val_extension_spv_khr_integer_dot_product.cpp
val_extension_spv_khr_bit_instructions.cpp
val_extension_spv_khr_terminate_invocation.cpp
val_extension_spv_khr_subgroup_rotate.cpp
val_ext_inst_test.cpp
val_ext_inst_debug_test.cpp
${VAL_TEST_COMMON_SRCS}

View File

@ -0,0 +1,352 @@
// Copyright (c) 2022 The Khronos Group Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string>
#include <vector>
#include "gmock/gmock.h"
#include "test/val/val_fixtures.h"
namespace spvtools {
namespace val {
namespace {
using ::testing::HasSubstr;
using ::testing::Values;
using ::testing::ValuesIn;
struct Case {
std::vector<std::string> caps;
bool shader;
std::string result_type;
std::string scope;
std::string delta;
std::string cluster_size;
std::string expected_error; // empty for no error.
};
inline std::ostream& operator<<(std::ostream& out, Case c) {
out << "\nSPV_KHR_subgroup_rotate Case{{";
for (auto& cap : c.caps) {
out << cap;
}
out << "} ";
out << (c.shader ? "shader " : "kernel ");
out << c.result_type + " ";
out << c.scope + " ";
out << c.delta + " ";
out << c.cluster_size + " ";
out << "err'" << c.expected_error << "'";
out << "}";
return out;
}
std::string AssemblyForCase(const Case& c) {
std::ostringstream ss;
if (c.shader) {
ss << "OpCapability Shader\n";
} else {
ss << "OpCapability Kernel\n";
ss << "OpCapability Addresses\n";
}
for (auto& cap : c.caps) {
ss << "OpCapability " << cap << "\n";
}
ss << "OpExtension \"SPV_KHR_subgroup_rotate\"\n";
if (c.shader) {
ss << "OpMemoryModel Logical GLSL450\n";
ss << "OpEntryPoint GLCompute %main \"main\"\n";
} else {
ss << "OpMemoryModel Physical32 OpenCL\n";
ss << "OpEntryPoint Kernel %main \"main\"\n";
}
ss << R"(
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%u32 = OpTypeInt 32 0
%float = OpTypeFloat 32
%ptr = OpTypePointer Function %u32
)";
if (c.shader) {
ss << "%i32 = OpTypeInt 32 1\n";
}
ss << R"(
%u32_0 = OpConstant %u32 0
%u32_1 = OpConstant %u32 1
%u32_15 = OpConstant %u32 15
%u32_16 = OpConstant %u32 16
%u32_undef = OpUndef %u32
%u32_spec_1 = OpSpecConstant %u32 1
%u32_spec_16 = OpSpecConstant %u32 16
%f32_1 = OpConstant %float 1.0
%subgroup = OpConstant %u32 3
%workgroup = OpConstant %u32 2
%invalid_scope = OpConstant %u32 1
%val = OpConstant %u32 42
)";
if (c.shader) {
ss << "%i32_1 = OpConstant %i32 1\n";
}
ss << R"(
%main = OpFunction %void None %void_fn
%entry = OpLabel
)";
ss << "%unused = OpGroupNonUniformRotateKHR ";
ss << c.result_type + " ";
ss << c.scope;
ss << " %val ";
ss << c.delta;
ss << " " + c.cluster_size;
ss << "\n";
ss << R"(
OpReturn
OpFunctionEnd
)";
return ss.str();
}
using ValidateSpvKHRSubgroupRotate = spvtest::ValidateBase<Case>;
TEST_P(ValidateSpvKHRSubgroupRotate, Base) {
const auto& c = GetParam();
const auto& assembly = AssemblyForCase(c);
CompileSuccessfully(assembly);
if (c.expected_error.empty()) {
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
} else {
EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(), HasSubstr(c.expected_error));
}
}
INSTANTIATE_TEST_SUITE_P(
Valid, ValidateSpvKHRSubgroupRotate,
::testing::Values(
Case{
{"GroupNonUniformRotateKHR"}, false, "%u32", "%subgroup", "%u32_1"},
Case{{"GroupNonUniformRotateKHR"}, true, "%u32", "%subgroup", "%u32_1"},
Case{{"GroupNonUniformRotateKHR"},
false,
"%u32",
"%subgroup",
"%u32_1",
"%u32_16"},
Case{{"GroupNonUniformRotateKHR"},
true,
"%u32",
"%subgroup",
"%u32_1",
"%u32_16"},
Case{{"GroupNonUniformRotateKHR"},
false,
"%u32",
"%subgroup",
"%u32_spec_1",
"%u32_16"},
Case{{"GroupNonUniformRotateKHR"},
true,
"%u32",
"%subgroup",
"%u32_1",
"%u32_spec_16"},
Case{{"GroupNonUniformRotateKHR"},
false,
"%u32",
"%workgroup",
"%u32_1"},
Case{
{"GroupNonUniformRotateKHR"}, true, "%u32", "%workgroup", "%u32_1"},
Case{{"GroupNonUniformRotateKHR"},
false,
"%u32",
"%workgroup",
"%u32_spec_1"},
Case{{"GroupNonUniformRotateKHR"},
true,
"%u32",
"%workgroup",
"%u32_spec_1"}));
INSTANTIATE_TEST_SUITE_P(
RequiresCapability, ValidateSpvKHRSubgroupRotate,
::testing::Values(Case{{},
false,
"%u32",
"%subgroup",
"%u32_1",
"",
"Opcode GroupNonUniformRotateKHR requires one of "
"these capabilities: "
"GroupNonUniformRotateKHR"},
Case{{},
true,
"%u32",
"%subgroup",
"%u32_1",
"",
"Opcode GroupNonUniformRotateKHR requires one of "
"these capabilities: "
"GroupNonUniformRotateKHR"}));
TEST_F(ValidateSpvKHRSubgroupRotate, RequiresExtension) {
const std::string str = R"(
OpCapability GroupNonUniformRotateKHR
)";
CompileSuccessfully(str.c_str());
EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"1st operand of Capability: operand GroupNonUniformRotateKHR(6026) "
"requires one of these extensions: SPV_KHR_subgroup_rotate"));
}
INSTANTIATE_TEST_SUITE_P(
InvalidExecutionScope, ValidateSpvKHRSubgroupRotate,
::testing::Values(
Case{{"GroupNonUniformRotateKHR"},
false,
"%u32",
"%invalid_scope",
"%u32_1",
"",
"Execution scope is limited to Subgroup or Workgroup"},
Case{{"GroupNonUniformRotateKHR"},
true,
"%u32",
"%invalid_scope",
"%u32_1",
"",
"Execution scope is limited to Subgroup or Workgroup"}));
INSTANTIATE_TEST_SUITE_P(
InvalidResultType, ValidateSpvKHRSubgroupRotate,
::testing::Values(Case{{"GroupNonUniformRotateKHR"},
false,
"%ptr",
"%subgroup",
"%u32_1",
"",
"Expected Result Type to be a scalar or vector of "
"floating-point, integer or boolean type"},
Case{{"GroupNonUniformRotateKHR"},
true,
"%ptr",
"%subgroup",
"%u32_1",
"",
"Expected Result Type to be a scalar or vector of "
"floating-point, integer or boolean type"}));
INSTANTIATE_TEST_SUITE_P(
MismatchedResultAndValueTypes, ValidateSpvKHRSubgroupRotate,
::testing::Values(
Case{{"GroupNonUniformRotateKHR"},
false,
"%float",
"%subgroup",
"%u32_1",
"",
"Result Type must be the same as the type of Value"},
Case{{"GroupNonUniformRotateKHR"},
true,
"%float",
"%subgroup",
"%u32_1",
"",
"Result Type must be the same as the type of Value"}));
INSTANTIATE_TEST_SUITE_P(
InvalidDelta, ValidateSpvKHRSubgroupRotate,
::testing::Values(Case{{"GroupNonUniformRotateKHR"},
false,
"%u32",
"%subgroup",
"%f32_1",
"",
"Delta must be a scalar of integer type, whose "
"Signedness operand is 0"},
Case{{"GroupNonUniformRotateKHR"},
true,
"%u32",
"%subgroup",
"%f32_1",
"",
"Delta must be a scalar of integer type, whose "
"Signedness operand is 0"},
Case{{"GroupNonUniformRotateKHR"},
true,
"%u32",
"%subgroup",
"%i32_1",
"",
"Delta must be a scalar of integer type, whose "
"Signedness operand is 0"}));
INSTANTIATE_TEST_SUITE_P(
InvalidClusterSize, ValidateSpvKHRSubgroupRotate,
::testing::Values(
Case{{"GroupNonUniformRotateKHR"},
false,
"%u32",
"%subgroup",
"%u32_1",
"%f32_1",
"ClusterSize must be a scalar of integer type, whose Signedness "
"operand is 0"},
Case{{"GroupNonUniformRotateKHR"},
true,
"%u32",
"%subgroup",
"%u32_1",
"%i32_1",
"ClusterSize must be a scalar of integer type, whose Signedness "
"operand is 0"},
Case{{"GroupNonUniformRotateKHR"},
true,
"%u32",
"%subgroup",
"%u32_1",
"%u32_0",
"Behavior is undefined unless ClusterSize is at least 1 and a "
"power of 2"},
Case{{"GroupNonUniformRotateKHR"},
true,
"%u32",
"%subgroup",
"%u32_1",
"%u32_15",
"Behavior is undefined unless ClusterSize is at least 1 and a "
"power of 2"},
Case{{"GroupNonUniformRotateKHR"},
true,
"%u32",
"%subgroup",
"%u32_1",
"%u32_undef",
"ClusterSize must come from a constant instruction"}));
} // namespace
} // namespace val
} // namespace spvtools