blob: 4a78f54fe82f6dc03108c08bfb26579d641b130e [file] [log] [blame] [edit]
// Copyright 2026 LunarG 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 "gmock/gmock.h"
#include "test/val/val_fixtures.h"
namespace spvtools {
namespace val {
namespace {
using ::testing::HasSubstr;
using ValidateGroup = spvtest::ValidateBase<bool>;
std::string GenerateShaderCode(const std::string& body, bool is_64_bit = true) {
std::ostringstream ss;
ss << R"(
OpCapability Kernel
OpCapability Addresses
OpCapability Linkage
OpCapability Groups
OpCapability Float64
OpCapability Int64
)";
if (is_64_bit) {
ss << "OpMemoryModel Physical64 OpenCL";
} else {
ss << "OpMemoryModel Physical32 OpenCL";
}
ss << R"(
OpEntryPoint Kernel %main "main"
%bool = OpTypeBool
%float = OpTypeFloat 32
%float64 = OpTypeFloat 64
%uint = OpTypeInt 32 0
%uint64 = OpTypeInt 64 0
%null_uint = OpConstantNull %uint
%uint_0 = OpConstant %uint 0
%uint_1 = OpConstant %uint 1
%uint_2 = OpConstant %uint 2
%uint64_1 = OpConstant %uint64 1
%float_2 = OpConstant %float 2
%uint_array = OpTypeArray %uint %uint_2
%void = OpTypeVoid
%event = OpTypeEvent
%null_event = OpConstantNull %event
%workgroup_float_ptr = OpTypePointer Workgroup %float
%workgroup_float_var = OpVariable %workgroup_float_ptr Workgroup
%workgroup_bool_ptr = OpTypePointer Workgroup %bool
%workgroup_bool_var = OpVariable %workgroup_bool_ptr Workgroup
%cross_float_ptr = OpTypePointer CrossWorkgroup %float
%cross_float_var = OpVariable %cross_float_ptr CrossWorkgroup
%cross_uint_ptr = OpTypePointer CrossWorkgroup %uint
%cross_uint_var = OpVariable %cross_uint_ptr CrossWorkgroup
%uniform_float_ptr = OpTypePointer UniformConstant %float
%uniform_float_var = OpVariable %uniform_float_ptr UniformConstant
%func_event_ptr = OpTypePointer Function %event
%fn = OpTypeFunction %void
%true = OpConstantTrue %bool
%main = OpFunction %void None %fn
%label = OpLabel
)";
ss << body;
ss << R"(
OpReturn
OpFunctionEnd)";
return ss.str();
}
TEST_F(ValidateGroup, AllAnyGood) {
const std::string ss = R"(
%x = OpGroupAll %bool %uint_2 %true
%y = OpGroupAny %bool %uint_2 %true
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateGroup, FloatGood) {
const std::string ss = R"(
%a = OpGroupFAdd %float %uint_2 Reduce %float_2
%b = OpGroupFMin %float %uint_2 Reduce %float_2
%c = OpGroupFMax %float %uint_2 Reduce %float_2
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateGroup, IntGood) {
const std::string ss = R"(
%a = OpGroupIAdd %uint %uint_2 Reduce %uint_2
%b = OpGroupSMin %uint %uint_2 Reduce %uint_2
%c = OpGroupSMax %uint %uint_2 Reduce %uint_2
%d = OpGroupUMin %uint %uint_2 Reduce %uint_2
%e = OpGroupUMax %uint %uint_2 Reduce %uint_2
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateGroup, BroadcastGood) {
const std::string ss = R"(
%a = OpGroupBroadcast %uint %uint_2 %uint_2 %uint_0
%b = OpGroupBroadcast %float %uint_2 %float_2 %uint_0
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateGroup, AllResult) {
const std::string ss = R"(
%x = OpGroupAll %uint %uint_2 %true
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Result must be a boolean scalar type"));
}
TEST_F(ValidateGroup, AllPredicate) {
const std::string ss = R"(
%x = OpGroupAll %bool %uint_2 %uint_2
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Predicate must be a boolean scalar type"));
}
TEST_F(ValidateGroup, AnyResult) {
const std::string ss = R"(
%x = OpGroupAny %uint %uint_2 %true
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Result must be a boolean scalar type"));
}
TEST_F(ValidateGroup, AnyPredicate) {
const std::string ss = R"(
%x = OpGroupAny %bool %uint_2 %uint_2
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Predicate must be a boolean scalar type"));
}
TEST_F(ValidateGroup, FAddWithInt) {
const std::string ss = R"(
%a = OpGroupFAdd %uint %uint_2 Reduce %uint_2
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Result must be a scalar or vector of float type"));
}
TEST_F(ValidateGroup, FMaxWidthMismatch) {
const std::string ss = R"(
%a = OpGroupFAdd %float64 %uint_2 Reduce %float_2
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("The type of X must match the Result type"));
}
TEST_F(ValidateGroup, IAddWithFloat) {
const std::string ss = R"(
%a = OpGroupIAdd %float %uint_2 Reduce %float_2
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Result must be a scalar or vector of integer type"));
}
TEST_F(ValidateGroup, UMinWithArray) {
const std::string ss = R"(
%a = OpGroupUMin %uint_array %uint_2 Reduce %float_2
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Result must be a scalar or vector of integer type"));
}
TEST_F(ValidateGroup, SMaxWidthMismatch) {
const std::string ss = R"(
%c = OpGroupSMax %uint64 %uint_2 Reduce %uint_2
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("The type of X must match the Result type"));
}
TEST_F(ValidateGroup, BroadcastArray) {
const std::string ss = R"(
%a = OpGroupBroadcast %uint_array %uint_2 %uint_2 %uint_0
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Result must be a scalar or vector of integer, "
"floating-point, or boolean type"));
}
TEST_F(ValidateGroup, BroadcastMismatch) {
const std::string ss = R"(
%b = OpGroupBroadcast %uint %uint_2 %float_2 %uint_0
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("The type of Value must match the Result type"));
}
TEST_F(ValidateGroup, AsyncCopyWaitEventsGood) {
const std::string ss = R"(
OpCapability Kernel
OpCapability Addresses
OpCapability Int64
OpCapability Int8
OpCapability Linkage
OpMemoryModel Physical64 OpenCL
OpEntryPoint Kernel %async_example "async_example"
OpExecutionMode %async_example ContractionOff
OpDecorate %26 Alignment 4
OpDecorate %29 Alignment 8
OpDecorate %async_example_local_data Alignment 4
%float = OpTypeFloat 32
%_ptr_CrossWorkgroup_float = OpTypePointer CrossWorkgroup %float
%void = OpTypeVoid
%5 = OpTypeFunction %void %_ptr_CrossWorkgroup_float
%spirv_Event = OpTypeEvent
%_ptr_Workgroup_float = OpTypePointer Workgroup %float
%ulong = OpTypeInt 64 0
%uint = OpTypeInt 32 0
%_ptr_Function_spirv_Event = OpTypePointer Function %spirv_Event
%uint_64 = OpConstant %uint 64
%_arr_float_uint_64 = OpTypeArray %float %uint_64
%_ptr_Workgroup__arr_float_uint_64 = OpTypePointer Workgroup %_arr_float_uint_64
%ulong_1 = OpConstant %ulong 1
%ulong_64 = OpConstant %ulong 64
%uint_1 = OpConstant %uint 1
%uint_2 = OpConstant %uint 2
%uchar = OpTypeInt 8 0
%_ptr_Function_uchar = OpTypePointer Function %uchar
%async_example_local_data = OpVariable %_ptr_Workgroup__arr_float_uint_64 Workgroup
%24 = OpConstantNull %spirv_Event
%async_example = OpFunction %void None %5
%26 = OpFunctionParameter %_ptr_CrossWorkgroup_float
%54 = OpLabel
%29 = OpVariable %_ptr_Function_spirv_Event Function
%30 = OpBitcast %_ptr_Workgroup_float %async_example_local_data
%31 = OpBitcast %_ptr_Function_uchar %29
%32 = OpGroupAsyncCopy %spirv_Event %uint_2 %30 %26 %ulong_64 %ulong_1 %24
OpStore %29 %32 Aligned 8
OpGroupWaitEvents %uint_2 %uint_1 %29
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(ss);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateGroup, AsyncCopyResultType) {
const std::string ss = R"(
%a = OpGroupAsyncCopy %uint %uint_2 %workgroup_float_var %cross_float_var %uint64_1 %uint64_1 %null_event
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("The result type must be OpTypeEvent"));
}
TEST_F(ValidateGroup, AsyncCopyDestinationPointer) {
const std::string ss = R"(
%a = OpGroupAsyncCopy %event %uint_2 %null_uint %cross_float_var %uint64_1 %uint64_1 %null_event
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Expected Destination to be a pointer"));
}
TEST_F(ValidateGroup, AsyncCopyDestinationUniform) {
const std::string ss = R"(
%a = OpGroupAsyncCopy %event %uint_2 %uniform_float_var %cross_float_var %uint64_1 %uint64_1 %null_event
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Expected Destination to be a pointer with storage "
"class Workgroup or CrossWorkgroup"));
}
TEST_F(ValidateGroup, AsyncCopyDestinationBool) {
const std::string ss = R"(
%a = OpGroupAsyncCopy %event %uint_2 %workgroup_bool_var %cross_float_var %uint64_1 %uint64_1 %null_event
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Expected Destination to be a pointer to scalar or "
"vector of floating-point type or integer type"));
}
TEST_F(ValidateGroup, AsyncCopyDestinationSourceTypes) {
const std::string ss = R"(
%a = OpGroupAsyncCopy %event %uint_2 %workgroup_float_var %cross_uint_var %uint64_1 %uint64_1 %null_event
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Expected Destination and Source to be the same type"));
}
TEST_F(ValidateGroup, AsyncCopyBothWorkgroup) {
const std::string ss = R"(
%a = OpGroupAsyncCopy %event %uint_2 %workgroup_float_var %workgroup_float_var %uint64_1 %uint64_1 %null_event
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("If Destination storage class is Workgroup, then the "
"Source storage class must be CrossWorkgroup."));
}
TEST_F(ValidateGroup, AsyncCopyBothCrossWorkgroup) {
const std::string ss = R"(
%a = OpGroupAsyncCopy %event %uint_2 %cross_float_var %cross_float_var %uint64_1 %uint64_1 %null_event
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("If Destination storage class is CrossWorkgroup, then "
"the Source storage class must be Workgroup"));
}
TEST_F(ValidateGroup, AsyncCopyEventType) {
const std::string ss = R"(
%a = OpGroupAsyncCopy %event %uint_2 %workgroup_float_var %cross_float_var %uint64_1 %uint64_1 %null_uint
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Expected Event to be type OpTypeEvent"));
}
TEST_F(ValidateGroup, AsyncCopyNumElement32Bit) {
const std::string ss = R"(
%a = OpGroupAsyncCopy %event %uint_2 %workgroup_float_var %cross_float_var %uint_1 %uint64_1 %null_event
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("NumElements must be a 64-bit int scalar when "
"Addressing Model is Physical64"));
}
TEST_F(ValidateGroup, AsyncCopyStride32Bit) {
const std::string ss = R"(
%a = OpGroupAsyncCopy %event %uint_2 %workgroup_float_var %cross_float_var %uint64_1 %uint_1 %null_event
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Stride must be a 64-bit int scalar when Addressing "
"Model is Physical64"));
}
TEST_F(ValidateGroup, AsyncCopyNumElement64Bit) {
const std::string ss = R"(
%a = OpGroupAsyncCopy %event %uint_2 %workgroup_float_var %cross_float_var %uint64_1 %uint_1 %null_event
)";
CompileSuccessfully(GenerateShaderCode(ss, false));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("NumElements must be a 32-bit int scalar when "
"Addressing Model is Physical32"));
}
TEST_F(ValidateGroup, AsyncCopyStride64Bit) {
const std::string ss = R"(
%a = OpGroupAsyncCopy %event %uint_2 %workgroup_float_var %cross_float_var %uint_1 %uint64_1 %null_event
)";
CompileSuccessfully(GenerateShaderCode(ss, false));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Stride must be a 32-bit int scalar when Addressing "
"Model is Physical32"));
}
TEST_F(ValidateGroup, GroupWaitEventsNumEvents) {
const std::string ss = R"(
%a = OpVariable %func_event_ptr Function
OpGroupWaitEvents %uint_2 %uint64_1 %a
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Expected Num Events to be a 32-bit int scalar"));
}
TEST_F(ValidateGroup, GroupWaitEventsEventList) {
const std::string ss = R"(
OpGroupWaitEvents %uint_2 %uint_1 %null_uint
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Expected Events List to be a pointer"));
}
TEST_F(ValidateGroup, GroupWaitEventsEventListType) {
const std::string ss = R"(
OpGroupWaitEvents %uint_2 %uint_1 %uniform_float_var
)";
CompileSuccessfully(GenerateShaderCode(ss));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Expected Events List to be a pointer to OpTypeEvent"));
}
} // namespace
} // namespace val
} // namespace spvtools