// Copyright 2021 The SwiftShader Authors. All Rights Reserved. // // 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 "Device.hpp" #include "Driver.hpp" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "spirv-tools/libspirv.hpp" #include #include namespace { size_t alignUp(size_t val, size_t alignment) { return alignment * ((val + alignment - 1) / alignment); } } // anonymous namespace struct ComputeParams { size_t numElements; int localSizeX; int localSizeY; int localSizeZ; friend std::ostream &operator<<(std::ostream &os, const ComputeParams ¶ms) { return os << "ComputeParams{" << "numElements: " << params.numElements << ", " << "localSizeX: " << params.localSizeX << ", " << "localSizeY: " << params.localSizeY << ", " << "localSizeZ: " << params.localSizeZ << "}"; } }; class ComputeTest : public testing::TestWithParam { protected: static Driver driver; static void SetUpTestSuite() { ASSERT_TRUE(driver.loadSwiftShader()); } static void TearDownTestSuite() { driver.unload(); } }; Driver ComputeTest::driver; std::vector compileSpirv(const char *assembly) { spvtools::SpirvTools core(SPV_ENV_VULKAN_1_0); core.SetMessageConsumer([](spv_message_level_t, const char *, const spv_position_t &p, const char *m) { FAIL() << p.line << ":" << p.column << ": " << m; }); std::vector spirv; EXPECT_TRUE(core.Assemble(assembly, &spirv)); EXPECT_TRUE(core.Validate(spirv)); // Warn if the disassembly does not match the source assembly. // We do this as debugging tests in the debugger is often made much harder // if the SSA names (%X) in the debugger do not match the source. std::string disassembled; core.Disassemble(spirv, &disassembled, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); if(disassembled != assembly) { printf("-- WARNING: Disassembly does not match assembly: ---\n\n"); auto splitLines = [](const std::string &str) -> std::vector { std::stringstream ss(str); std::vector out; std::string line; while(std::getline(ss, line, '\n')) { out.push_back(line); } return out; }; auto srcLines = splitLines(std::string(assembly)); auto disLines = splitLines(disassembled); for(size_t line = 0; line < srcLines.size() && line < disLines.size(); line++) { auto srcLine = (line < srcLines.size()) ? srcLines[line] : ""; auto disLine = (line < disLines.size()) ? disLines[line] : ""; if(srcLine != disLine) { printf("%zu: '%s' != '%s'\n", line, srcLine.c_str(), disLine.c_str()); } } printf("\n\n---\nExpected:\n\n%s", disassembled.c_str()); } return spirv; } #define VK_ASSERT(x) ASSERT_EQ(x, VK_SUCCESS) // Base class for compute tests that read from an input buffer and write to an // output buffer of same length. class SwiftShaderVulkanBufferToBufferComputeTest : public ComputeTest { public: void test(const std::string &shader, std::function input, std::function expected); }; void SwiftShaderVulkanBufferToBufferComputeTest::test( const std::string &shader, std::function input, std::function expected) { auto code = compileSpirv(shader.c_str()); const VkInstanceCreateInfo createInfo = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, // sType nullptr, // pNext 0, // flags nullptr, // pApplicationInfo 0, // enabledLayerCount nullptr, // ppEnabledLayerNames 0, // enabledExtensionCount nullptr, // ppEnabledExtensionNames }; VkInstance instance = VK_NULL_HANDLE; VK_ASSERT(driver.vkCreateInstance(&createInfo, nullptr, &instance)); ASSERT_TRUE(driver.resolve(instance)); std::unique_ptr device; VK_ASSERT(Device::CreateComputeDevice(&driver, instance, device)); ASSERT_TRUE(device->IsValid()); // struct Buffers // { // uint32_t pad0[63]; // uint32_t magic0; // uint32_t in[NUM_ELEMENTS]; // Aligned to 0x100 // uint32_t magic1; // uint32_t pad1[N]; // uint32_t magic2; // uint32_t out[NUM_ELEMENTS]; // Aligned to 0x100 // uint32_t magic3; // }; static constexpr uint32_t magic0 = 0x01234567; static constexpr uint32_t magic1 = 0x89abcdef; static constexpr uint32_t magic2 = 0xfedcba99; static constexpr uint32_t magic3 = 0x87654321; size_t numElements = GetParam().numElements; size_t alignElements = 0x100 / sizeof(uint32_t); size_t magic0Offset = alignElements - 1; size_t inOffset = 1 + magic0Offset; size_t magic1Offset = numElements + inOffset; size_t magic2Offset = alignUp(magic1Offset + 1, alignElements) - 1; size_t outOffset = 1 + magic2Offset; size_t magic3Offset = numElements + outOffset; size_t buffersTotalElements = alignUp(1 + magic3Offset, alignElements); size_t buffersSize = sizeof(uint32_t) * buffersTotalElements; VkDeviceMemory memory; VK_ASSERT(device->AllocateMemory(buffersSize, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &memory)); uint32_t *buffers; VK_ASSERT(device->MapMemory(memory, 0, buffersSize, 0, (void **)&buffers)); buffers[magic0Offset] = magic0; buffers[magic1Offset] = magic1; buffers[magic2Offset] = magic2; buffers[magic3Offset] = magic3; for(size_t i = 0; i < numElements; i++) { buffers[inOffset + i] = input((uint32_t)i); } device->UnmapMemory(memory); buffers = nullptr; VkBuffer bufferIn; VK_ASSERT(device->CreateStorageBuffer(memory, sizeof(uint32_t) * numElements, sizeof(uint32_t) * inOffset, &bufferIn)); VkBuffer bufferOut; VK_ASSERT(device->CreateStorageBuffer(memory, sizeof(uint32_t) * numElements, sizeof(uint32_t) * outOffset, &bufferOut)); VkShaderModule shaderModule; VK_ASSERT(device->CreateShaderModule(code, &shaderModule)); std::vector descriptorSetLayoutBindings = { { 0, // binding VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // descriptorType 1, // descriptorCount VK_SHADER_STAGE_COMPUTE_BIT, // stageFlags 0, // pImmutableSamplers }, { 1, // binding VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // descriptorType 1, // descriptorCount VK_SHADER_STAGE_COMPUTE_BIT, // stageFlags 0, // pImmutableSamplers } }; VkDescriptorSetLayout descriptorSetLayout; VK_ASSERT(device->CreateDescriptorSetLayout(descriptorSetLayoutBindings, &descriptorSetLayout)); VkPipelineLayout pipelineLayout; VK_ASSERT(device->CreatePipelineLayout(descriptorSetLayout, &pipelineLayout)); VkPipeline pipeline; VK_ASSERT(device->CreateComputePipeline(shaderModule, pipelineLayout, &pipeline)); VkDescriptorPool descriptorPool; VK_ASSERT(device->CreateStorageBufferDescriptorPool(2, &descriptorPool)); VkDescriptorSet descriptorSet; VK_ASSERT(device->AllocateDescriptorSet(descriptorPool, descriptorSetLayout, &descriptorSet)); std::vector descriptorBufferInfos = { { bufferIn, // buffer 0, // offset VK_WHOLE_SIZE, // range }, { bufferOut, // buffer 0, // offset VK_WHOLE_SIZE, // range } }; device->UpdateStorageBufferDescriptorSets(descriptorSet, descriptorBufferInfos); VkCommandPool commandPool; VK_ASSERT(device->CreateCommandPool(&commandPool)); VkCommandBuffer commandBuffer; VK_ASSERT(device->AllocateCommandBuffer(commandPool, &commandBuffer)); VK_ASSERT(device->BeginCommandBuffer(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, commandBuffer)); driver.vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); driver.vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); driver.vkCmdDispatch(commandBuffer, (uint32_t)(numElements / GetParam().localSizeX), 1, 1); VK_ASSERT(driver.vkEndCommandBuffer(commandBuffer)); VK_ASSERT(device->QueueSubmitAndWait(commandBuffer)); VK_ASSERT(device->MapMemory(memory, 0, buffersSize, 0, (void **)&buffers)); for(size_t i = 0; i < numElements; ++i) { auto got = buffers[i + outOffset]; EXPECT_EQ(expected((uint32_t)i), got) << "Unexpected output at " << i; } // Check for writes outside of bounds. EXPECT_EQ(buffers[magic0Offset], magic0); EXPECT_EQ(buffers[magic1Offset], magic1); EXPECT_EQ(buffers[magic2Offset], magic2); EXPECT_EQ(buffers[magic3Offset], magic3); device->UnmapMemory(memory); buffers = nullptr; device->FreeCommandBuffer(commandPool, commandBuffer); device->FreeMemory(memory); device->DestroyPipeline(pipeline); device->DestroyCommandPool(commandPool); device->DestroyPipelineLayout(pipelineLayout); device->DestroyDescriptorSetLayout(descriptorSetLayout); device->DestroyDescriptorPool(descriptorPool); device->DestroyBuffer(bufferIn); device->DestroyBuffer(bufferOut); device->DestroyShaderModule(shaderModule); device.reset(nullptr); driver.vkDestroyInstance(instance, nullptr); } INSTANTIATE_TEST_SUITE_P(ComputeParams, SwiftShaderVulkanBufferToBufferComputeTest, testing::Values(ComputeParams{ 512, 1, 1, 1 }, ComputeParams{ 512, 2, 1, 1 }, ComputeParams{ 512, 4, 1, 1 }, ComputeParams{ 512, 8, 1, 1 }, ComputeParams{ 512, 16, 1, 1 }, ComputeParams{ 512, 32, 1, 1 }, // Non-multiple of SIMD-lane. ComputeParams{ 3, 1, 1, 1 }, ComputeParams{ 2, 1, 1, 1 })); TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, Memcpy) { std::stringstream src; // #version 450 // layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; // layout(binding = 0, std430) buffer InBuffer // { // int Data[]; // } In; // layout(binding = 1, std430) buffer OutBuffer // { // int Data[]; // } Out; // void main() // { // Out.Data[gl_GlobalInvocationID.x] = In.Data[gl_GlobalInvocationID.x]; // } // clang-format off src << "OpCapability Shader\n" "OpMemoryModel Logical GLSL450\n" "OpEntryPoint GLCompute %1 \"main\" %2\n" "OpExecutionMode %1 LocalSize " << GetParam().localSizeX << " " << GetParam().localSizeY << " " << GetParam().localSizeZ << "\n" << "OpDecorate %3 ArrayStride 4\n" "OpMemberDecorate %4 0 Offset 0\n" "OpDecorate %4 BufferBlock\n" "OpDecorate %5 DescriptorSet 0\n" "OpDecorate %5 Binding 1\n" "OpDecorate %2 BuiltIn GlobalInvocationId\n" "OpDecorate %6 DescriptorSet 0\n" "OpDecorate %6 Binding 0\n" "%7 = OpTypeVoid\n" "%8 = OpTypeFunction %7\n" // void() "%9 = OpTypeInt 32 1\n" // int32 "%10 = OpTypeInt 32 0\n" // uint32 "%3 = OpTypeRuntimeArray %9\n" // int32[] "%4 = OpTypeStruct %3\n" // struct{ int32[] } "%11 = OpTypePointer Uniform %4\n" // struct{ int32[] }* "%5 = OpVariable %11 Uniform\n" // struct{ int32[] }* in "%12 = OpConstant %9 0\n" // int32(0) "%13 = OpConstant %10 0\n" // uint32(0) "%14 = OpTypeVector %10 3\n" // vec3 "%15 = OpTypePointer Input %14\n" // vec3* "%2 = OpVariable %15 Input\n" // gl_GlobalInvocationId "%16 = OpTypePointer Input %10\n" // uint32* "%6 = OpVariable %11 Uniform\n" // struct{ int32[] }* out "%17 = OpTypePointer Uniform %9\n" // int32* "%1 = OpFunction %7 None %8\n" // -- Function begin -- "%18 = OpLabel\n" "%19 = OpAccessChain %16 %2 %13\n" // &gl_GlobalInvocationId.x "%20 = OpLoad %10 %19\n" // gl_GlobalInvocationId.x "%21 = OpAccessChain %17 %6 %12 %20\n" // &in.arr[gl_GlobalInvocationId.x] "%22 = OpLoad %9 %21\n" // in.arr[gl_GlobalInvocationId.x] "%23 = OpAccessChain %17 %5 %12 %20\n" // &out.arr[gl_GlobalInvocationId.x] "OpStore %23 %22\n" // out.arr[gl_GlobalInvocationId.x] = in[gl_GlobalInvocationId.x] "OpReturn\n" "OpFunctionEnd\n"; // clang-format on test( src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return i; }); } TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, GlobalInvocationId) { std::stringstream src; // clang-format off src << "OpCapability Shader\n" "OpMemoryModel Logical GLSL450\n" "OpEntryPoint GLCompute %1 \"main\" %2\n" "OpExecutionMode %1 LocalSize " << GetParam().localSizeX << " " << GetParam().localSizeY << " " << GetParam().localSizeZ << "\n" << "OpDecorate %3 ArrayStride 4\n" "OpMemberDecorate %4 0 Offset 0\n" "OpDecorate %4 BufferBlock\n" "OpDecorate %5 DescriptorSet 0\n" "OpDecorate %5 Binding 1\n" "OpDecorate %2 BuiltIn GlobalInvocationId\n" "OpDecorate %6 DescriptorSet 0\n" "OpDecorate %6 Binding 0\n" "%7 = OpTypeVoid\n" "%8 = OpTypeFunction %7\n" // void() "%9 = OpTypeInt 32 1\n" // int32 "%10 = OpTypeInt 32 0\n" // uint32 "%3 = OpTypeRuntimeArray %9\n" // int32[] "%4 = OpTypeStruct %3\n" // struct{ int32[] } "%11 = OpTypePointer Uniform %4\n" // struct{ int32[] }* "%5 = OpVariable %11 Uniform\n" // struct{ int32[] }* in "%12 = OpConstant %9 0\n" // int32(0) "%13 = OpConstant %9 1\n" // int32(1) "%14 = OpConstant %10 0\n" // uint32(0) "%15 = OpConstant %10 1\n" // uint32(1) "%16 = OpConstant %10 2\n" // uint32(2) "%17 = OpTypeVector %10 3\n" // vec3 "%18 = OpTypePointer Input %17\n" // vec3* "%2 = OpVariable %18 Input\n" // gl_GlobalInvocationId "%19 = OpTypePointer Input %10\n" // uint32* "%6 = OpVariable %11 Uniform\n" // struct{ int32[] }* out "%20 = OpTypePointer Uniform %9\n" // int32* "%1 = OpFunction %7 None %8\n" // -- Function begin -- "%21 = OpLabel\n" "%22 = OpAccessChain %19 %2 %14\n" // &gl_GlobalInvocationId.x "%23 = OpAccessChain %19 %2 %15\n" // &gl_GlobalInvocationId.y "%24 = OpAccessChain %19 %2 %16\n" // &gl_GlobalInvocationId.z "%25 = OpLoad %10 %22\n" // gl_GlobalInvocationId.x "%26 = OpLoad %10 %23\n" // gl_GlobalInvocationId.y "%27 = OpLoad %10 %24\n" // gl_GlobalInvocationId.z "%28 = OpAccessChain %20 %6 %12 %25\n" // &in.arr[gl_GlobalInvocationId.x] "%29 = OpLoad %9 %28\n" // out.arr[gl_GlobalInvocationId.x] "%30 = OpIAdd %9 %29 %26\n" // in[gl_GlobalInvocationId.x] + gl_GlobalInvocationId.y "%31 = OpIAdd %9 %30 %27\n" // in[gl_GlobalInvocationId.x] + gl_GlobalInvocationId.y + gl_GlobalInvocationId.z "%32 = OpAccessChain %20 %5 %12 %25\n" // &out.arr[gl_GlobalInvocationId.x] "OpStore %32 %31\n" // out.arr[gl_GlobalInvocationId.x] = in[gl_GlobalInvocationId.x] + gl_GlobalInvocationId.y + gl_GlobalInvocationId.z "OpReturn\n" "OpFunctionEnd\n"; // clang-format on // gl_GlobalInvocationId.y and gl_GlobalInvocationId.z should both be zero. test( src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return i; }); } TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, BranchSimple) { std::stringstream src; // clang-format off src << "OpCapability Shader\n" "OpMemoryModel Logical GLSL450\n" "OpEntryPoint GLCompute %1 \"main\" %2\n" "OpExecutionMode %1 LocalSize " << GetParam().localSizeX << " " << GetParam().localSizeY << " " << GetParam().localSizeZ << "\n" << "OpDecorate %3 ArrayStride 4\n" "OpMemberDecorate %4 0 Offset 0\n" "OpDecorate %4 BufferBlock\n" "OpDecorate %5 DescriptorSet 0\n" "OpDecorate %5 Binding 1\n" "OpDecorate %2 BuiltIn GlobalInvocationId\n" "OpDecorate %6 DescriptorSet 0\n" "OpDecorate %6 Binding 0\n" "%7 = OpTypeVoid\n" "%8 = OpTypeFunction %7\n" // void() "%9 = OpTypeInt 32 1\n" // int32 "%10 = OpTypeInt 32 0\n" // uint32 "%3 = OpTypeRuntimeArray %9\n" // int32[] "%4 = OpTypeStruct %3\n" // struct{ int32[] } "%11 = OpTypePointer Uniform %4\n" // struct{ int32[] }* "%5 = OpVariable %11 Uniform\n" // struct{ int32[] }* in "%12 = OpConstant %9 0\n" // int32(0) "%13 = OpConstant %10 0\n" // uint32(0) "%14 = OpTypeVector %10 3\n" // vec3 "%15 = OpTypePointer Input %14\n" // vec3* "%2 = OpVariable %15 Input\n" // gl_GlobalInvocationId "%16 = OpTypePointer Input %10\n" // uint32* "%6 = OpVariable %11 Uniform\n" // struct{ int32[] }* out "%17 = OpTypePointer Uniform %9\n" // int32* "%1 = OpFunction %7 None %8\n" // -- Function begin -- "%18 = OpLabel\n" "%19 = OpAccessChain %16 %2 %13\n" // &gl_GlobalInvocationId.x "%20 = OpLoad %10 %19\n" // gl_GlobalInvocationId.x "%21 = OpAccessChain %17 %6 %12 %20\n" // &in.arr[gl_GlobalInvocationId.x] "%22 = OpLoad %9 %21\n" // in.arr[gl_GlobalInvocationId.x] "%23 = OpAccessChain %17 %5 %12 %20\n" // &out.arr[gl_GlobalInvocationId.x] // Start of branch logic // %22 = in value "OpBranch %24\n" "%24 = OpLabel\n" "OpBranch %25\n" "%25 = OpLabel\n" "OpBranch %26\n" "%26 = OpLabel\n" // %22 = out value // End of branch logic "OpStore %23 %22\n" "OpReturn\n" "OpFunctionEnd\n"; // clang-format on test( src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return i; }); } TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, BranchDeclareSSA) { std::stringstream src; // clang-format off src << "OpCapability Shader\n" "OpMemoryModel Logical GLSL450\n" "OpEntryPoint GLCompute %1 \"main\" %2\n" "OpExecutionMode %1 LocalSize " << GetParam().localSizeX << " " << GetParam().localSizeY << " " << GetParam().localSizeZ << "\n" << "OpDecorate %3 ArrayStride 4\n" "OpMemberDecorate %4 0 Offset 0\n" "OpDecorate %4 BufferBlock\n" "OpDecorate %5 DescriptorSet 0\n" "OpDecorate %5 Binding 1\n" "OpDecorate %2 BuiltIn GlobalInvocationId\n" "OpDecorate %6 DescriptorSet 0\n" "OpDecorate %6 Binding 0\n" "%7 = OpTypeVoid\n" "%8 = OpTypeFunction %7\n" // void() "%9 = OpTypeInt 32 1\n" // int32 "%10 = OpTypeInt 32 0\n" // uint32 "%3 = OpTypeRuntimeArray %9\n" // int32[] "%4 = OpTypeStruct %3\n" // struct{ int32[] } "%11 = OpTypePointer Uniform %4\n" // struct{ int32[] }* "%5 = OpVariable %11 Uniform\n" // struct{ int32[] }* in "%12 = OpConstant %9 0\n" // int32(0) "%13 = OpConstant %10 0\n" // uint32(0) "%14 = OpTypeVector %10 3\n" // vec3 "%15 = OpTypePointer Input %14\n" // vec3* "%2 = OpVariable %15 Input\n" // gl_GlobalInvocationId "%16 = OpTypePointer Input %10\n" // uint32* "%6 = OpVariable %11 Uniform\n" // struct{ int32[] }* out "%17 = OpTypePointer Uniform %9\n" // int32* "%1 = OpFunction %7 None %8\n" // -- Function begin -- "%18 = OpLabel\n" "%19 = OpAccessChain %16 %2 %13\n" // &gl_GlobalInvocationId.x "%20 = OpLoad %10 %19\n" // gl_GlobalInvocationId.x "%21 = OpAccessChain %17 %6 %12 %20\n" // &in.arr[gl_GlobalInvocationId.x] "%22 = OpLoad %9 %21\n" // in.arr[gl_GlobalInvocationId.x] "%23 = OpAccessChain %17 %5 %12 %20\n" // &out.arr[gl_GlobalInvocationId.x] // Start of branch logic // %22 = in value "OpBranch %24\n" "%24 = OpLabel\n" "%25 = OpIAdd %9 %22 %22\n" // %25 = in*2 "OpBranch %26\n" "%26 = OpLabel\n" "OpBranch %27\n" "%27 = OpLabel\n" // %25 = out value // End of branch logic "OpStore %23 %25\n" // use SSA value from previous block "OpReturn\n" "OpFunctionEnd\n"; // clang-format on test( src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return i * 2; }); } TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, BranchConditionalSimple) { std::stringstream src; // clang-format off src << "OpCapability Shader\n" "OpMemoryModel Logical GLSL450\n" "OpEntryPoint GLCompute %1 \"main\" %2\n" "OpExecutionMode %1 LocalSize " << GetParam().localSizeX << " " << GetParam().localSizeY << " " << GetParam().localSizeZ << "\n" << "OpDecorate %3 ArrayStride 4\n" "OpMemberDecorate %4 0 Offset 0\n" "OpDecorate %4 BufferBlock\n" "OpDecorate %5 DescriptorSet 0\n" "OpDecorate %5 Binding 1\n" "OpDecorate %2 BuiltIn GlobalInvocationId\n" "OpDecorate %6 DescriptorSet 0\n" "OpDecorate %6 Binding 0\n" "%7 = OpTypeVoid\n" "%8 = OpTypeFunction %7\n" // void() "%9 = OpTypeInt 32 1\n" // int32 "%10 = OpTypeInt 32 0\n" // uint32 "%11 = OpTypeBool\n" "%3 = OpTypeRuntimeArray %9\n" // int32[] "%4 = OpTypeStruct %3\n" // struct{ int32[] } "%12 = OpTypePointer Uniform %4\n" // struct{ int32[] }* "%5 = OpVariable %12 Uniform\n" // struct{ int32[] }* in "%13 = OpConstant %9 0\n" // int32(0) "%14 = OpConstant %9 2\n" // int32(2) "%15 = OpConstant %10 0\n" // uint32(0) "%16 = OpTypeVector %10 3\n" // vec4 "%17 = OpTypePointer Input %16\n" // vec4* "%2 = OpVariable %17 Input\n" // gl_GlobalInvocationId "%18 = OpTypePointer Input %10\n" // uint32* "%6 = OpVariable %12 Uniform\n" // struct{ int32[] }* out "%19 = OpTypePointer Uniform %9\n" // int32* "%1 = OpFunction %7 None %8\n" // -- Function begin -- "%20 = OpLabel\n" "%21 = OpAccessChain %18 %2 %15\n" // &gl_GlobalInvocationId.x "%22 = OpLoad %10 %21\n" // gl_GlobalInvocationId.x "%23 = OpAccessChain %19 %6 %13 %22\n" // &in.arr[gl_GlobalInvocationId.x] "%24 = OpLoad %9 %23\n" // in.arr[gl_GlobalInvocationId.x] "%25 = OpAccessChain %19 %5 %13 %22\n" // &out.arr[gl_GlobalInvocationId.x] // Start of branch logic // %24 = in value "%26 = OpSMod %9 %24 %14\n" // in % 2 "%27 = OpIEqual %11 %26 %13\n" // (in % 2) == 0 "OpSelectionMerge %28 None\n" "OpBranchConditional %27 %28 %28\n" // Both go to %28 "%28 = OpLabel\n" // %26 = out value // End of branch logic "OpStore %25 %26\n" // use SSA value from previous block "OpReturn\n" "OpFunctionEnd\n"; // clang-format on test( src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return i % 2; }); } TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, BranchConditionalTwoEmptyBlocks) { std::stringstream src; // clang-format off src << "OpCapability Shader\n" "OpMemoryModel Logical GLSL450\n" "OpEntryPoint GLCompute %1 \"main\" %2\n" "OpExecutionMode %1 LocalSize " << GetParam().localSizeX << " " << GetParam().localSizeY << " " << GetParam().localSizeZ << "\n" << "OpDecorate %3 ArrayStride 4\n" "OpMemberDecorate %4 0 Offset 0\n" "OpDecorate %4 BufferBlock\n" "OpDecorate %5 DescriptorSet 0\n" "OpDecorate %5 Binding 1\n" "OpDecorate %2 BuiltIn GlobalInvocationId\n" "OpDecorate %6 DescriptorSet 0\n" "OpDecorate %6 Binding 0\n" "%7 = OpTypeVoid\n" "%8 = OpTypeFunction %7\n" // void() "%9 = OpTypeInt 32 1\n" // int32 "%10 = OpTypeInt 32 0\n" // uint32 "%11 = OpTypeBool\n" "%3 = OpTypeRuntimeArray %9\n" // int32[] "%4 = OpTypeStruct %3\n" // struct{ int32[] } "%12 = OpTypePointer Uniform %4\n" // struct{ int32[] }* "%5 = OpVariable %12 Uniform\n" // struct{ int32[] }* in "%13 = OpConstant %9 0\n" // int32(0) "%14 = OpConstant %9 2\n" // int32(2) "%15 = OpConstant %10 0\n" // uint32(0) "%16 = OpTypeVector %10 3\n" // vec4 "%17 = OpTypePointer Input %16\n" // vec4* "%2 = OpVariable %17 Input\n" // gl_GlobalInvocationId "%18 = OpTypePointer Input %10\n" // uint32* "%6 = OpVariable %12 Uniform\n" // struct{ int32[] }* out "%19 = OpTypePointer Uniform %9\n" // int32* "%1 = OpFunction %7 None %8\n" // -- Function begin -- "%20 = OpLabel\n" "%21 = OpAccessChain %18 %2 %15\n" // &gl_GlobalInvocationId.x "%22 = OpLoad %10 %21\n" // gl_GlobalInvocationId.x "%23 = OpAccessChain %19 %6 %13 %22\n" // &in.arr[gl_GlobalInvocationId.x] "%24 = OpLoad %9 %23\n" // in.arr[gl_GlobalInvocationId.x] "%25 = OpAccessChain %19 %5 %13 %22\n" // &out.arr[gl_GlobalInvocationId.x] // Start of branch logic // %24 = in value "%26 = OpSMod %9 %24 %14\n" // in % 2 "%27 = OpIEqual %11 %26 %13\n" // (in % 2) == 0 "OpSelectionMerge %28 None\n" "OpBranchConditional %27 %29 %30\n" "%29 = OpLabel\n" // (in % 2) == 0 "OpBranch %28\n" "%30 = OpLabel\n" // (in % 2) != 0 "OpBranch %28\n" "%28 = OpLabel\n" // %26 = out value // End of branch logic "OpStore %25 %26\n" // use SSA value from previous block "OpReturn\n" "OpFunctionEnd\n"; // clang-format on test( src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return i % 2; }); } // TODO: Test for parallel assignment TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, BranchConditionalStore) { std::stringstream src; // clang-format off src << "OpCapability Shader\n" "OpMemoryModel Logical GLSL450\n" "OpEntryPoint GLCompute %1 \"main\" %2\n" "OpExecutionMode %1 LocalSize " << GetParam().localSizeX << " " << GetParam().localSizeY << " " << GetParam().localSizeZ << "\n" << "OpDecorate %3 ArrayStride 4\n" "OpMemberDecorate %4 0 Offset 0\n" "OpDecorate %4 BufferBlock\n" "OpDecorate %5 DescriptorSet 0\n" "OpDecorate %5 Binding 1\n" "OpDecorate %2 BuiltIn GlobalInvocationId\n" "OpDecorate %6 DescriptorSet 0\n" "OpDecorate %6 Binding 0\n" "%7 = OpTypeVoid\n" "%8 = OpTypeFunction %7\n" // void() "%9 = OpTypeInt 32 1\n" // int32 "%10 = OpTypeInt 32 0\n" // uint32 "%11 = OpTypeBool\n" "%3 = OpTypeRuntimeArray %9\n" // int32[] "%4 = OpTypeStruct %3\n" // struct{ int32[] } "%12 = OpTypePointer Uniform %4\n" // struct{ int32[] }* "%5 = OpVariable %12 Uniform\n" // struct{ int32[] }* in "%13 = OpConstant %9 0\n" // int32(0) "%14 = OpConstant %9 1\n" // int32(1) "%15 = OpConstant %9 2\n" // int32(2) "%16 = OpConstant %10 0\n" // uint32(0) "%17 = OpTypeVector %10 3\n" // vec4 "%18 = OpTypePointer Input %17\n" // vec4* "%2 = OpVariable %18 Input\n" // gl_GlobalInvocationId "%19 = OpTypePointer Input %10\n" // uint32* "%6 = OpVariable %12 Uniform\n" // struct{ int32[] }* out "%20 = OpTypePointer Uniform %9\n" // int32* "%1 = OpFunction %7 None %8\n" // -- Function begin -- "%21 = OpLabel\n" "%22 = OpAccessChain %19 %2 %16\n" // &gl_GlobalInvocationId.x "%23 = OpLoad %10 %22\n" // gl_GlobalInvocationId.x "%24 = OpAccessChain %20 %6 %13 %23\n" // &in.arr[gl_GlobalInvocationId.x] "%25 = OpLoad %9 %24\n" // in.arr[gl_GlobalInvocationId.x] "%26 = OpAccessChain %20 %5 %13 %23\n" // &out.arr[gl_GlobalInvocationId.x] // Start of branch logic // %25 = in value "%27 = OpSMod %9 %25 %15\n" // in % 2 "%28 = OpIEqual %11 %27 %13\n" // (in % 2) == 0 "OpSelectionMerge %29 None\n" "OpBranchConditional %28 %30 %31\n" "%30 = OpLabel\n" // (in % 2) == 0 "OpStore %26 %14\n" // write 1 "OpBranch %29\n" "%31 = OpLabel\n" // (in % 2) != 0 "OpStore %26 %15\n" // write 2 "OpBranch %29\n" "%29 = OpLabel\n" // End of branch logic "OpReturn\n" "OpFunctionEnd\n"; // clang-format on test( src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return (i % 2) == 0 ? 1 : 2; }); } TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, BranchConditionalReturnTrue) { std::stringstream src; // clang-format off src << "OpCapability Shader\n" "OpMemoryModel Logical GLSL450\n" "OpEntryPoint GLCompute %1 \"main\" %2\n" "OpExecutionMode %1 LocalSize " << GetParam().localSizeX << " " << GetParam().localSizeY << " " << GetParam().localSizeZ << "\n" << "OpDecorate %3 ArrayStride 4\n" "OpMemberDecorate %4 0 Offset 0\n" "OpDecorate %4 BufferBlock\n" "OpDecorate %5 DescriptorSet 0\n" "OpDecorate %5 Binding 1\n" "OpDecorate %2 BuiltIn GlobalInvocationId\n" "OpDecorate %6 DescriptorSet 0\n" "OpDecorate %6 Binding 0\n" "%7 = OpTypeVoid\n" "%8 = OpTypeFunction %7\n" // void() "%9 = OpTypeInt 32 1\n" // int32 "%10 = OpTypeInt 32 0\n" // uint32 "%11 = OpTypeBool\n" "%3 = OpTypeRuntimeArray %9\n" // int32[] "%4 = OpTypeStruct %3\n" // struct{ int32[] } "%12 = OpTypePointer Uniform %4\n" // struct{ int32[] }* "%5 = OpVariable %12 Uniform\n" // struct{ int32[] }* in "%13 = OpConstant %9 0\n" // int32(0) "%14 = OpConstant %9 1\n" // int32(1) "%15 = OpConstant %9 2\n" // int32(2) "%16 = OpConstant %10 0\n" // uint32(0) "%17 = OpTypeVector %10 3\n" // vec4 "%18 = OpTypePointer Input %17\n" // vec4* "%2 = OpVariable %18 Input\n" // gl_GlobalInvocationId "%19 = OpTypePointer Input %10\n" // uint32* "%6 = OpVariable %12 Uniform\n" // struct{ int32[] }* out "%20 = OpTypePointer Uniform %9\n" // int32* "%1 = OpFunction %7 None %8\n" // -- Function begin -- "%21 = OpLabel\n" "%22 = OpAccessChain %19 %2 %16\n" // &gl_GlobalInvocationId.x "%23 = OpLoad %10 %22\n" // gl_GlobalInvocationId.x "%24 = OpAccessChain %20 %6 %13 %23\n" // &in.arr[gl_GlobalInvocationId.x] "%25 = OpLoad %9 %24\n" // in.arr[gl_GlobalInvocationId.x] "%26 = OpAccessChain %20 %5 %13 %23\n" // &out.arr[gl_GlobalInvocationId.x] // Start of branch logic // %25 = in value "%27 = OpSMod %9 %25 %15\n" // in % 2 "%28 = OpIEqual %11 %27 %13\n" // (in % 2) == 0 "OpSelectionMerge %29 None\n" "OpBranchConditional %28 %30 %29\n" "%30 = OpLabel\n" // (in % 2) == 0 "OpReturn\n" "%29 = OpLabel\n" // merge "OpStore %26 %15\n" // write 2 // End of branch logic "OpReturn\n" "OpFunctionEnd\n"; // clang-format on test( src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return (i % 2) == 0 ? 0 : 2; }); } // TODO: Test for parallel assignment TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, BranchConditionalPhi) { std::stringstream src; // clang-format off src << "OpCapability Shader\n" "OpMemoryModel Logical GLSL450\n" "OpEntryPoint GLCompute %1 \"main\" %2\n" "OpExecutionMode %1 LocalSize " << GetParam().localSizeX << " " << GetParam().localSizeY << " " << GetParam().localSizeZ << "\n" << "OpDecorate %3 ArrayStride 4\n" "OpMemberDecorate %4 0 Offset 0\n" "OpDecorate %4 BufferBlock\n" "OpDecorate %5 DescriptorSet 0\n" "OpDecorate %5 Binding 1\n" "OpDecorate %2 BuiltIn GlobalInvocationId\n" "OpDecorate %6 DescriptorSet 0\n" "OpDecorate %6 Binding 0\n" "%7 = OpTypeVoid\n" "%8 = OpTypeFunction %7\n" // void() "%9 = OpTypeInt 32 1\n" // int32 "%10 = OpTypeInt 32 0\n" // uint32 "%11 = OpTypeBool\n" "%3 = OpTypeRuntimeArray %9\n" // int32[] "%4 = OpTypeStruct %3\n" // struct{ int32[] } "%12 = OpTypePointer Uniform %4\n" // struct{ int32[] }* "%5 = OpVariable %12 Uniform\n" // struct{ int32[] }* in "%13 = OpConstant %9 0\n" // int32(0) "%14 = OpConstant %9 1\n" // int32(1) "%15 = OpConstant %9 2\n" // int32(2) "%16 = OpConstant %10 0\n" // uint32(0) "%17 = OpTypeVector %10 3\n" // vec4 "%18 = OpTypePointer Input %17\n" // vec4* "%2 = OpVariable %18 Input\n" // gl_GlobalInvocationId "%19 = OpTypePointer Input %10\n" // uint32* "%6 = OpVariable %12 Uniform\n" // struct{ int32[] }* out "%20 = OpTypePointer Uniform %9\n" // int32* "%1 = OpFunction %7 None %8\n" // -- Function begin -- "%21 = OpLabel\n" "%22 = OpAccessChain %19 %2 %16\n" // &gl_GlobalInvocationId.x "%23 = OpLoad %10 %22\n" // gl_GlobalInvocationId.x "%24 = OpAccessChain %20 %6 %13 %23\n" // &in.arr[gl_GlobalInvocationId.x] "%25 = OpLoad %9 %24\n" // in.arr[gl_GlobalInvocationId.x] "%26 = OpAccessChain %20 %5 %13 %23\n" // &out.arr[gl_GlobalInvocationId.x] // Start of branch logic // %25 = in value "%27 = OpSMod %9 %25 %15\n" // in % 2 "%28 = OpIEqual %11 %27 %13\n" // (in % 2) == 0 "OpSelectionMerge %29 None\n" "OpBranchConditional %28 %30 %31\n" "%30 = OpLabel\n" // (in % 2) == 0 "OpBranch %29\n" "%31 = OpLabel\n" // (in % 2) != 0 "OpBranch %29\n" "%29 = OpLabel\n" "%32 = OpPhi %9 %14 %30 %15 %31\n" // (in % 2) == 0 ? 1 : 2 // End of branch logic "OpStore %26 %32\n" "OpReturn\n" "OpFunctionEnd\n"; // clang-format on test( src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return (i % 2) == 0 ? 1 : 2; }); } TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, SwitchEmptyCases) { std::stringstream src; // clang-format off src << "OpCapability Shader\n" "OpMemoryModel Logical GLSL450\n" "OpEntryPoint GLCompute %1 \"main\" %2\n" "OpExecutionMode %1 LocalSize " << GetParam().localSizeX << " " << GetParam().localSizeY << " " << GetParam().localSizeZ << "\n" << "OpDecorate %3 ArrayStride 4\n" "OpMemberDecorate %4 0 Offset 0\n" "OpDecorate %4 BufferBlock\n" "OpDecorate %5 DescriptorSet 0\n" "OpDecorate %5 Binding 1\n" "OpDecorate %2 BuiltIn GlobalInvocationId\n" "OpDecorate %6 DescriptorSet 0\n" "OpDecorate %6 Binding 0\n" "%7 = OpTypeVoid\n" "%8 = OpTypeFunction %7\n" // void() "%9 = OpTypeInt 32 1\n" // int32 "%10 = OpTypeInt 32 0\n" // uint32 "%11 = OpTypeBool\n" "%3 = OpTypeRuntimeArray %9\n" // int32[] "%4 = OpTypeStruct %3\n" // struct{ int32[] } "%12 = OpTypePointer Uniform %4\n" // struct{ int32[] }* "%5 = OpVariable %12 Uniform\n" // struct{ int32[] }* in "%13 = OpConstant %9 0\n" // int32(0) "%14 = OpConstant %9 2\n" // int32(2) "%15 = OpConstant %10 0\n" // uint32(0) "%16 = OpTypeVector %10 3\n" // vec4 "%17 = OpTypePointer Input %16\n" // vec4* "%2 = OpVariable %17 Input\n" // gl_GlobalInvocationId "%18 = OpTypePointer Input %10\n" // uint32* "%6 = OpVariable %12 Uniform\n" // struct{ int32[] }* out "%19 = OpTypePointer Uniform %9\n" // int32* "%1 = OpFunction %7 None %8\n" // -- Function begin -- "%20 = OpLabel\n" "%21 = OpAccessChain %18 %2 %15\n" // &gl_GlobalInvocationId.x "%22 = OpLoad %10 %21\n" // gl_GlobalInvocationId.x "%23 = OpAccessChain %19 %6 %13 %22\n" // &in.arr[gl_GlobalInvocationId.x] "%24 = OpLoad %9 %23\n" // in.arr[gl_GlobalInvocationId.x] "%25 = OpAccessChain %19 %5 %13 %22\n" // &out.arr[gl_GlobalInvocationId.x] // Start of branch logic // %24 = in value "%26 = OpSMod %9 %24 %14\n" // in % 2 "OpSelectionMerge %27 None\n" "OpSwitch %26 %27 0 %28 1 %29\n" "%28 = OpLabel\n" // (in % 2) == 0 "OpBranch %27\n" "%29 = OpLabel\n" // (in % 2) == 1 "OpBranch %27\n" "%27 = OpLabel\n" // %26 = out value // End of branch logic "OpStore %25 %26\n" // use SSA value from previous block "OpReturn\n" "OpFunctionEnd\n"; // clang-format on test( src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return i % 2; }); } TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, SwitchStore) { std::stringstream src; // clang-format off src << "OpCapability Shader\n" "OpMemoryModel Logical GLSL450\n" "OpEntryPoint GLCompute %1 \"main\" %2\n" "OpExecutionMode %1 LocalSize " << GetParam().localSizeX << " " << GetParam().localSizeY << " " << GetParam().localSizeZ << "\n" << "OpDecorate %3 ArrayStride 4\n" "OpMemberDecorate %4 0 Offset 0\n" "OpDecorate %4 BufferBlock\n" "OpDecorate %5 DescriptorSet 0\n" "OpDecorate %5 Binding 1\n" "OpDecorate %2 BuiltIn GlobalInvocationId\n" "OpDecorate %6 DescriptorSet 0\n" "OpDecorate %6 Binding 0\n" "%7 = OpTypeVoid\n" "%8 = OpTypeFunction %7\n" // void() "%9 = OpTypeInt 32 1\n" // int32 "%10 = OpTypeInt 32 0\n" // uint32 "%11 = OpTypeBool\n" "%3 = OpTypeRuntimeArray %9\n" // int32[] "%4 = OpTypeStruct %3\n" // struct{ int32[] } "%12 = OpTypePointer Uniform %4\n" // struct{ int32[] }* "%5 = OpVariable %12 Uniform\n" // struct{ int32[] }* in "%13 = OpConstant %9 0\n" // int32(0) "%14 = OpConstant %9 1\n" // int32(1) "%15 = OpConstant %9 2\n" // int32(2) "%16 = OpConstant %10 0\n" // uint32(0) "%17 = OpTypeVector %10 3\n" // vec4 "%18 = OpTypePointer Input %17\n" // vec4* "%2 = OpVariable %18 Input\n" // gl_GlobalInvocationId "%19 = OpTypePointer Input %10\n" // uint32* "%6 = OpVariable %12 Uniform\n" // struct{ int32[] }* out "%20 = OpTypePointer Uniform %9\n" // int32* "%1 = OpFunction %7 None %8\n" // -- Function begin -- "%21 = OpLabel\n" "%22 = OpAccessChain %19 %2 %16\n" // &gl_GlobalInvocationId.x "%23 = OpLoad %10 %22\n" // gl_GlobalInvocationId.x "%24 = OpAccessChain %20 %6 %13 %23\n" // &in.arr[gl_GlobalInvocationId.x] "%25 = OpLoad %9 %24\n" // in.arr[gl_GlobalInvocationId.x] "%26 = OpAccessChain %20 %5 %13 %23\n" // &out.arr[gl_GlobalInvocationId.x] // Start of branch logic // %25 = in value "%27 = OpSMod %9 %25 %15\n" // in % 2 "OpSelectionMerge %28 None\n" "OpSwitch %27 %28 0 %29 1 %30\n" "%29 = OpLabel\n" // (in % 2) == 0 "OpStore %26 %15\n" // write 2 "OpBranch %28\n" "%30 = OpLabel\n" // (in % 2) == 1 "OpStore %26 %14\n" // write 1 "OpBranch %28\n" "%28 = OpLabel\n" // End of branch logic "OpReturn\n" "OpFunctionEnd\n"; // clang-format on test( src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return (i % 2) == 0 ? 2 : 1; }); } TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, SwitchCaseReturn) { std::stringstream src; // clang-format off src << "OpCapability Shader\n" "OpMemoryModel Logical GLSL450\n" "OpEntryPoint GLCompute %1 \"main\" %2\n" "OpExecutionMode %1 LocalSize " << GetParam().localSizeX << " " << GetParam().localSizeY << " " << GetParam().localSizeZ << "\n" << "OpDecorate %3 ArrayStride 4\n" "OpMemberDecorate %4 0 Offset 0\n" "OpDecorate %4 BufferBlock\n" "OpDecorate %5 DescriptorSet 0\n" "OpDecorate %5 Binding 1\n" "OpDecorate %2 BuiltIn GlobalInvocationId\n" "OpDecorate %6 DescriptorSet 0\n" "OpDecorate %6 Binding 0\n" "%7 = OpTypeVoid\n" "%8 = OpTypeFunction %7\n" // void() "%9 = OpTypeInt 32 1\n" // int32 "%10 = OpTypeInt 32 0\n" // uint32 "%11 = OpTypeBool\n" "%3 = OpTypeRuntimeArray %9\n" // int32[] "%4 = OpTypeStruct %3\n" // struct{ int32[] } "%12 = OpTypePointer Uniform %4\n" // struct{ int32[] }* "%5 = OpVariable %12 Uniform\n" // struct{ int32[] }* in "%13 = OpConstant %9 0\n" // int32(0) "%14 = OpConstant %9 1\n" // int32(1) "%15 = OpConstant %9 2\n" // int32(2) "%16 = OpConstant %10 0\n" // uint32(0) "%17 = OpTypeVector %10 3\n" // vec4 "%18 = OpTypePointer Input %17\n" // vec4* "%2 = OpVariable %18 Input\n" // gl_GlobalInvocationId "%19 = OpTypePointer Input %10\n" // uint32* "%6 = OpVariable %12 Uniform\n" // struct{ int32[] }* out "%20 = OpTypePointer Uniform %9\n" // int32* "%1 = OpFunction %7 None %8\n" // -- Function begin -- "%21 = OpLabel\n" "%22 = OpAccessChain %19 %2 %16\n" // &gl_GlobalInvocationId.x "%23 = OpLoad %10 %22\n" // gl_GlobalInvocationId.x "%24 = OpAccessChain %20 %6 %13 %23\n" // &in.arr[gl_GlobalInvocationId.x] "%25 = OpLoad %9 %24\n" // in.arr[gl_GlobalInvocationId.x] "%26 = OpAccessChain %20 %5 %13 %23\n" // &out.arr[gl_GlobalInvocationId.x] // Start of branch logic // %25 = in value "%27 = OpSMod %9 %25 %15\n" // in % 2 "OpSelectionMerge %28 None\n" "OpSwitch %27 %28 0 %29 1 %30\n" "%29 = OpLabel\n" // (in % 2) == 0 "OpBranch %28\n" "%30 = OpLabel\n" // (in % 2) == 1 "OpReturn\n" "%28 = OpLabel\n" "OpStore %26 %14\n" // write 1 // End of branch logic "OpReturn\n" "OpFunctionEnd\n"; // clang-format on test( src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return (i % 2) == 1 ? 0 : 1; }); } TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, SwitchDefaultReturn) { std::stringstream src; // clang-format off src << "OpCapability Shader\n" "OpMemoryModel Logical GLSL450\n" "OpEntryPoint GLCompute %1 \"main\" %2\n" "OpExecutionMode %1 LocalSize " << GetParam().localSizeX << " " << GetParam().localSizeY << " " << GetParam().localSizeZ << "\n" << "OpDecorate %3 ArrayStride 4\n" "OpMemberDecorate %4 0 Offset 0\n" "OpDecorate %4 BufferBlock\n" "OpDecorate %5 DescriptorSet 0\n" "OpDecorate %5 Binding 1\n" "OpDecorate %2 BuiltIn GlobalInvocationId\n" "OpDecorate %6 DescriptorSet 0\n" "OpDecorate %6 Binding 0\n" "%7 = OpTypeVoid\n" "%8 = OpTypeFunction %7\n" // void() "%9 = OpTypeInt 32 1\n" // int32 "%10 = OpTypeInt 32 0\n" // uint32 "%11 = OpTypeBool\n" "%3 = OpTypeRuntimeArray %9\n" // int32[] "%4 = OpTypeStruct %3\n" // struct{ int32[] } "%12 = OpTypePointer Uniform %4\n" // struct{ int32[] }* "%5 = OpVariable %12 Uniform\n" // struct{ int32[] }* in "%13 = OpConstant %9 0\n" // int32(0) "%14 = OpConstant %9 1\n" // int32(1) "%15 = OpConstant %9 2\n" // int32(2) "%16 = OpConstant %10 0\n" // uint32(0) "%17 = OpTypeVector %10 3\n" // vec4 "%18 = OpTypePointer Input %17\n" // vec4* "%2 = OpVariable %18 Input\n" // gl_GlobalInvocationId "%19 = OpTypePointer Input %10\n" // uint32* "%6 = OpVariable %12 Uniform\n" // struct{ int32[] }* out "%20 = OpTypePointer Uniform %9\n" // int32* "%1 = OpFunction %7 None %8\n" // -- Function begin -- "%21 = OpLabel\n" "%22 = OpAccessChain %19 %2 %16\n" // &gl_GlobalInvocationId.x "%23 = OpLoad %10 %22\n" // gl_GlobalInvocationId.x "%24 = OpAccessChain %20 %6 %13 %23\n" // &in.arr[gl_GlobalInvocationId.x] "%25 = OpLoad %9 %24\n" // in.arr[gl_GlobalInvocationId.x] "%26 = OpAccessChain %20 %5 %13 %23\n" // &out.arr[gl_GlobalInvocationId.x] // Start of branch logic // %25 = in value "%27 = OpSMod %9 %25 %15\n" // in % 2 "OpSelectionMerge %28 None\n" "OpSwitch %27 %29 1 %30\n" "%30 = OpLabel\n" // (in % 2) == 1 "OpBranch %28\n" "%29 = OpLabel\n" // (in % 2) != 1 "OpReturn\n" "%28 = OpLabel\n" // merge "OpStore %26 %14\n" // write 1 // End of branch logic "OpReturn\n" "OpFunctionEnd\n"; // clang-format on test( src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return (i % 2) == 1 ? 1 : 0; }); } TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, SwitchCaseFallthrough) { std::stringstream src; // clang-format off src << "OpCapability Shader\n" "OpMemoryModel Logical GLSL450\n" "OpEntryPoint GLCompute %1 \"main\" %2\n" "OpExecutionMode %1 LocalSize " << GetParam().localSizeX << " " << GetParam().localSizeY << " " << GetParam().localSizeZ << "\n" << "OpDecorate %3 ArrayStride 4\n" "OpMemberDecorate %4 0 Offset 0\n" "OpDecorate %4 BufferBlock\n" "OpDecorate %5 DescriptorSet 0\n" "OpDecorate %5 Binding 1\n" "OpDecorate %2 BuiltIn GlobalInvocationId\n" "OpDecorate %6 DescriptorSet 0\n" "OpDecorate %6 Binding 0\n" "%7 = OpTypeVoid\n" "%8 = OpTypeFunction %7\n" // void() "%9 = OpTypeInt 32 1\n" // int32 "%10 = OpTypeInt 32 0\n" // uint32 "%11 = OpTypeBool\n" "%3 = OpTypeRuntimeArray %9\n" // int32[] "%4 = OpTypeStruct %3\n" // struct{ int32[] } "%12 = OpTypePointer Uniform %4\n" // struct{ int32[] }* "%5 = OpVariable %12 Uniform\n" // struct{ int32[] }* in "%13 = OpConstant %9 0\n" // int32(0) "%14 = OpConstant %9 1\n" // int32(1) "%15 = OpConstant %9 2\n" // int32(2) "%16 = OpConstant %10 0\n" // uint32(0) "%17 = OpTypeVector %10 3\n" // vec4 "%18 = OpTypePointer Input %17\n" // vec4* "%2 = OpVariable %18 Input\n" // gl_GlobalInvocationId "%19 = OpTypePointer Input %10\n" // uint32* "%6 = OpVariable %12 Uniform\n" // struct{ int32[] }* out "%20 = OpTypePointer Uniform %9\n" // int32* "%1 = OpFunction %7 None %8\n" // -- Function begin -- "%21 = OpLabel\n" "%22 = OpAccessChain %19 %2 %16\n" // &gl_GlobalInvocationId.x "%23 = OpLoad %10 %22\n" // gl_GlobalInvocationId.x "%24 = OpAccessChain %20 %6 %13 %23\n" // &in.arr[gl_GlobalInvocationId.x] "%25 = OpLoad %9 %24\n" // in.arr[gl_GlobalInvocationId.x] "%26 = OpAccessChain %20 %5 %13 %23\n" // &out.arr[gl_GlobalInvocationId.x] // Start of branch logic // %25 = in value "%27 = OpSMod %9 %25 %15\n" // in % 2 "OpSelectionMerge %28 None\n" "OpSwitch %27 %29 0 %30 1 %31\n" "%30 = OpLabel\n" // (in % 2) == 0 "%32 = OpIAdd %9 %27 %14\n" // generate an intermediate "OpStore %26 %32\n" // write a value (overwritten later) "OpBranch %31\n" // fallthrough "%31 = OpLabel\n" // (in % 2) == 1 "OpStore %26 %15\n" // write 2 "OpBranch %28\n" "%29 = OpLabel\n" // unreachable "OpUnreachable\n" "%28 = OpLabel\n" // merge // End of branch logic "OpReturn\n" "OpFunctionEnd\n"; // clang-format on test( src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return 2; }); } TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, SwitchDefaultFallthrough) { std::stringstream src; // clang-format off src << "OpCapability Shader\n" "OpMemoryModel Logical GLSL450\n" "OpEntryPoint GLCompute %1 \"main\" %2\n" "OpExecutionMode %1 LocalSize " << GetParam().localSizeX << " " << GetParam().localSizeY << " " << GetParam().localSizeZ << "\n" << "OpDecorate %3 ArrayStride 4\n" "OpMemberDecorate %4 0 Offset 0\n" "OpDecorate %4 BufferBlock\n" "OpDecorate %5 DescriptorSet 0\n" "OpDecorate %5 Binding 1\n" "OpDecorate %2 BuiltIn GlobalInvocationId\n" "OpDecorate %6 DescriptorSet 0\n" "OpDecorate %6 Binding 0\n" "%7 = OpTypeVoid\n" "%8 = OpTypeFunction %7\n" // void() "%9 = OpTypeInt 32 1\n" // int32 "%10 = OpTypeInt 32 0\n" // uint32 "%11 = OpTypeBool\n" "%3 = OpTypeRuntimeArray %9\n" // int32[] "%4 = OpTypeStruct %3\n" // struct{ int32[] } "%12 = OpTypePointer Uniform %4\n" // struct{ int32[] }* "%5 = OpVariable %12 Uniform\n" // struct{ int32[] }* in "%13 = OpConstant %9 0\n" // int32(0) "%14 = OpConstant %9 1\n" // int32(1) "%15 = OpConstant %9 2\n" // int32(2) "%16 = OpConstant %10 0\n" // uint32(0) "%17 = OpTypeVector %10 3\n" // vec4 "%18 = OpTypePointer Input %17\n" // vec4* "%2 = OpVariable %18 Input\n" // gl_GlobalInvocationId "%19 = OpTypePointer Input %10\n" // uint32* "%6 = OpVariable %12 Uniform\n" // struct{ int32[] }* out "%20 = OpTypePointer Uniform %9\n" // int32* "%1 = OpFunction %7 None %8\n" // -- Function begin -- "%21 = OpLabel\n" "%22 = OpAccessChain %19 %2 %16\n" // &gl_GlobalInvocationId.x "%23 = OpLoad %10 %22\n" // gl_GlobalInvocationId.x "%24 = OpAccessChain %20 %6 %13 %23\n" // &in.arr[gl_GlobalInvocationId.x] "%25 = OpLoad %9 %24\n" // in.arr[gl_GlobalInvocationId.x] "%26 = OpAccessChain %20 %5 %13 %23\n" // &out.arr[gl_GlobalInvocationId.x] // Start of branch logic // %25 = in value "%27 = OpSMod %9 %25 %15\n" // in % 2 "OpSelectionMerge %28 None\n" "OpSwitch %27 %29 0 %30 1 %31\n" "%30 = OpLabel\n" // (in % 2) == 0 "%32 = OpIAdd %9 %27 %14\n" // generate an intermediate "OpStore %26 %32\n" // write a value (overwritten later) "OpBranch %29\n" // fallthrough "%29 = OpLabel\n" // default "%33 = OpIAdd %9 %27 %14\n" // generate an intermediate "OpStore %26 %33\n" // write a value (overwritten later) "OpBranch %31\n" // fallthrough "%31 = OpLabel\n" // (in % 2) == 1 "OpStore %26 %15\n" // write 2 "OpBranch %28\n" "%28 = OpLabel\n" // merge // End of branch logic "OpReturn\n" "OpFunctionEnd\n"; // clang-format on test( src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return 2; }); } TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, SwitchPhi) { std::stringstream src; // clang-format off src << "OpCapability Shader\n" "OpMemoryModel Logical GLSL450\n" "OpEntryPoint GLCompute %1 \"main\" %2\n" "OpExecutionMode %1 LocalSize " << GetParam().localSizeX << " " << GetParam().localSizeY << " " << GetParam().localSizeZ << "\n" << "OpDecorate %3 ArrayStride 4\n" "OpMemberDecorate %4 0 Offset 0\n" "OpDecorate %4 BufferBlock\n" "OpDecorate %5 DescriptorSet 0\n" "OpDecorate %5 Binding 1\n" "OpDecorate %2 BuiltIn GlobalInvocationId\n" "OpDecorate %6 DescriptorSet 0\n" "OpDecorate %6 Binding 0\n" "%7 = OpTypeVoid\n" "%8 = OpTypeFunction %7\n" // void() "%9 = OpTypeInt 32 1\n" // int32 "%10 = OpTypeInt 32 0\n" // uint32 "%11 = OpTypeBool\n" "%3 = OpTypeRuntimeArray %9\n" // int32[] "%4 = OpTypeStruct %3\n" // struct{ int32[] } "%12 = OpTypePointer Uniform %4\n" // struct{ int32[] }* "%5 = OpVariable %12 Uniform\n" // struct{ int32[] }* in "%13 = OpConstant %9 0\n" // int32(0) "%14 = OpConstant %9 1\n" // int32(1) "%15 = OpConstant %9 2\n" // int32(2) "%16 = OpConstant %10 0\n" // uint32(0) "%17 = OpTypeVector %10 3\n" // vec4 "%18 = OpTypePointer Input %17\n" // vec4* "%2 = OpVariable %18 Input\n" // gl_GlobalInvocationId "%19 = OpTypePointer Input %10\n" // uint32* "%6 = OpVariable %12 Uniform\n" // struct{ int32[] }* out "%20 = OpTypePointer Uniform %9\n" // int32* "%1 = OpFunction %7 None %8\n" // -- Function begin -- "%21 = OpLabel\n" "%22 = OpAccessChain %19 %2 %16\n" // &gl_GlobalInvocationId.x "%23 = OpLoad %10 %22\n" // gl_GlobalInvocationId.x "%24 = OpAccessChain %20 %6 %13 %23\n" // &in.arr[gl_GlobalInvocationId.x] "%25 = OpLoad %9 %24\n" // in.arr[gl_GlobalInvocationId.x] "%26 = OpAccessChain %20 %5 %13 %23\n" // &out.arr[gl_GlobalInvocationId.x] // Start of branch logic // %25 = in value "%27 = OpSMod %9 %25 %15\n" // in % 2 "OpSelectionMerge %28 None\n" "OpSwitch %27 %29 1 %30\n" "%30 = OpLabel\n" // (in % 2) == 1 "OpBranch %28\n" "%29 = OpLabel\n" // (in % 2) != 1 "OpBranch %28\n" "%28 = OpLabel\n" // merge "%31 = OpPhi %9 %14 %30 %15 %29\n" // (in % 2) == 1 ? 1 : 2 "OpStore %26 %31\n" // End of branch logic "OpReturn\n" "OpFunctionEnd\n"; // clang-format on test( src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return (i % 2) == 1 ? 1 : 2; }); } TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, LoopDivergentMergePhi) { // #version 450 // layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; // layout(binding = 0, std430) buffer InBuffer // { // int Data[]; // } In; // layout(binding = 1, std430) buffer OutBuffer // { // int Data[]; // } Out; // void main() // { // int phi = 0; // uint lane = gl_GlobalInvocationID.x % 4; // for (uint i = 0; i < 4; i++) // { // if (lane == i) // { // phi = In.Data[gl_GlobalInvocationID.x]; // break; // } // } // Out.Data[gl_GlobalInvocationID.x] = phi; // } std::stringstream src; // clang-format off src << "OpCapability Shader\n" "%1 = OpExtInstImport \"GLSL.std.450\"\n" "OpMemoryModel Logical GLSL450\n" "OpEntryPoint GLCompute %2 \"main\" %3\n" "OpExecutionMode %2 LocalSize " << GetParam().localSizeX << " " << GetParam().localSizeY << " " << GetParam().localSizeZ << "\n" << "OpDecorate %3 BuiltIn GlobalInvocationId\n" "OpDecorate %4 ArrayStride 4\n" "OpMemberDecorate %5 0 Offset 0\n" "OpDecorate %5 BufferBlock\n" "OpDecorate %6 DescriptorSet 0\n" "OpDecorate %6 Binding 0\n" "OpDecorate %7 ArrayStride 4\n" "OpMemberDecorate %8 0 Offset 0\n" "OpDecorate %8 BufferBlock\n" "OpDecorate %9 DescriptorSet 0\n" "OpDecorate %9 Binding 1\n" "%10 = OpTypeVoid\n" "%11 = OpTypeFunction %10\n" "%12 = OpTypeInt 32 1\n" "%13 = OpConstant %12 0\n" "%14 = OpTypeInt 32 0\n" "%15 = OpTypeVector %14 3\n" "%16 = OpTypePointer Input %15\n" "%3 = OpVariable %16 Input\n" "%17 = OpConstant %14 0\n" "%18 = OpTypePointer Input %14\n" "%19 = OpConstant %14 4\n" "%20 = OpTypeBool\n" "%4 = OpTypeRuntimeArray %12\n" "%5 = OpTypeStruct %4\n" "%21 = OpTypePointer Uniform %5\n" "%6 = OpVariable %21 Uniform\n" "%22 = OpTypePointer Uniform %12\n" "%23 = OpConstant %12 1\n" "%7 = OpTypeRuntimeArray %12\n" "%8 = OpTypeStruct %7\n" "%24 = OpTypePointer Uniform %8\n" "%9 = OpVariable %24 Uniform\n" "%2 = OpFunction %10 None %11\n" "%25 = OpLabel\n" "%26 = OpAccessChain %18 %3 %17\n" "%27 = OpLoad %14 %26\n" "%28 = OpUMod %14 %27 %19\n" "OpBranch %29\n" "%29 = OpLabel\n" "%30 = OpPhi %14 %17 %25 %31 %32\n" "%33 = OpULessThan %20 %30 %19\n" "OpLoopMerge %34 %32 None\n" "OpBranchConditional %33 %35 %34\n" "%35 = OpLabel\n" "%36 = OpIEqual %20 %28 %30\n" "OpSelectionMerge %37 None\n" "OpBranchConditional %36 %38 %37\n" "%38 = OpLabel\n" "%39 = OpAccessChain %22 %6 %13 %27\n" "%40 = OpLoad %12 %39\n" "OpBranch %34\n" "%37 = OpLabel\n" "OpBranch %32\n" "%32 = OpLabel\n" "%31 = OpIAdd %14 %30 %23\n" "OpBranch %29\n" "%34 = OpLabel\n" "%41 = OpPhi %12 %13 %29 %40 %38\n" // %40: phi "%42 = OpAccessChain %22 %9 %13 %27\n" "OpStore %42 %41\n" "OpReturn\n" "OpFunctionEnd\n"; // clang-format on test( src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return i; }); }