OSDN Git Service

SpirvShader: Implement OpBranchConditional, OpPhi, ...
authorBen Clayton <bclayton@google.com>
Thu, 21 Mar 2019 18:47:15 +0000 (18:47 +0000)
committerBen Clayton <bclayton@google.com>
Tue, 26 Mar 2019 12:46:26 +0000 (12:46 +0000)
... OpUnreachable and OpReturn.

Tests: dEQP-VK.spirv_assembly.instruction.compute.*
Tests: dEQP-VK.spirv_assembly.instruction.graphics.*
Bug: b/128527271
Change-Id: Iec9af723c72c873df8cbdea7c0027e2f7fa25e70
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/27774
Presubmit-Ready: Ben Clayton <bclayton@google.com>
Tested-by: Ben Clayton <bclayton@google.com>
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
src/Pipeline/SpirvShader.cpp
src/Pipeline/SpirvShader.hpp
tests/VulkanUnitTests/unittests.cpp

index 56129bc..c226f13 100644 (file)
@@ -146,6 +146,9 @@ namespace sw
                                break;
                        }
 
+                       case spv::OpSelectionMerge:
+                               break; // Nothing to do in analysis pass.
+
                        case spv::OpTypeVoid:
                        case spv::OpTypeBool:
                        case spv::OpTypeInt:
@@ -417,6 +420,7 @@ namespace sw
                        case spv::OpDPdyFine:
                        case spv::OpFwidthFine:
                        case spv::OpAtomicLoad:
+                       case spv::OpPhi:
                                // Instructions that yield an intermediate value
                        {
                                Type::ID typeId = insn.word(1);
@@ -1173,6 +1177,8 @@ namespace sw
                switch (block.kind)
                {
                        case Block::Simple:
+                       case Block::StructuredBranchConditional:
+                       case Block::UnstructuredBranchConditional:
                                if (id != mainBlockId)
                                {
                                        // Emit all preceeding blocks and set the activeLaneMask.
@@ -1261,11 +1267,6 @@ namespace sw
                        return EmitResult::Continue;
 
                case spv::OpLabel:
-               case spv::OpReturn:
-                       // TODO: when we do control flow, will need to do some work here.
-                       // Until then, there is nothing to do -- we expect there to be an initial OpLabel
-                       // in the entrypoint function, for which we do nothing; and a final OpReturn at the
-                       // end of the entrypoint function, for which we do nothing.
                        return EmitResult::Continue;
 
                case spv::OpVariable:
@@ -1394,6 +1395,21 @@ namespace sw
                case spv::OpBranch:
                        return EmitBranch(insn, state);
 
+               case spv::OpPhi:
+                       return EmitPhi(insn, state);
+
+               case spv::OpSelectionMerge:
+                       return EmitResult::Continue;
+
+               case spv::OpBranchConditional:
+                       return EmitBranchConditional(insn, state);
+
+               case spv::OpUnreachable:
+                       return EmitUnreachable(insn, state);
+
+               case spv::OpReturn:
+                       return EmitReturn(insn, state);
+
                default:
                        UNIMPLEMENTED("opcode: %s", OpcodeName(insn.opcode()).c_str());
                        break;
@@ -2602,6 +2618,69 @@ namespace sw
                return EmitResult::Terminator;
        }
 
+       SpirvShader::EmitResult SpirvShader::EmitBranchConditional(InsnIterator insn, EmitState *state) const
+       {
+               auto block = getBlock(state->currentBlock);
+               ASSERT(block.branchInstruction == insn);
+
+               auto condId = Object::ID(block.branchInstruction.word(1));
+               auto trueBlockId = Block::ID(block.branchInstruction.word(2));
+               auto falseBlockId = Block::ID(block.branchInstruction.word(3));
+
+               auto cond = GenericValue(this, state->routine, condId);
+               ASSERT_MSG(getType(getObject(condId).type).sizeInComponents == 1, "Condition must be a Boolean type scalar");
+
+               // TODO: Optimize for case where all lanes take same path.
+
+               state->addOutputActiveLaneMaskEdge(trueBlockId, cond.Int(0));
+               state->addOutputActiveLaneMaskEdge(falseBlockId, ~cond.Int(0));
+
+               return EmitResult::Terminator;
+       }
+
+
+       SpirvShader::EmitResult SpirvShader::EmitUnreachable(InsnIterator insn, EmitState *state) const
+       {
+               // TODO: Log something in this case?
+               state->setActiveLaneMask(SIMD::Int(0));
+               return EmitResult::Terminator;
+       }
+
+       SpirvShader::EmitResult SpirvShader::EmitReturn(InsnIterator insn, EmitState *state) const
+       {
+               state->setActiveLaneMask(SIMD::Int(0));
+               return EmitResult::Terminator;
+       }
+
+       SpirvShader::EmitResult SpirvShader::EmitPhi(InsnIterator insn, EmitState *state) const
+       {
+               auto routine = state->routine;
+               auto typeId = Type::ID(insn.word(1));
+               auto type = getType(typeId);
+               auto objectId = Object::ID(insn.word(2));
+
+               auto &dst = routine->createIntermediate(objectId, type.sizeInComponents);
+
+               bool first = true;
+               for (uint32_t w = 3; w < insn.wordCount(); w += 2)
+               {
+                       auto varId = Object::ID(insn.word(w + 0));
+                       auto blockId = Block::ID(insn.word(w + 1));
+
+                       auto in = GenericValue(this, routine, varId);
+                       auto mask = state->getActiveLaneMaskEdge(blockId, state->currentBlock);
+
+                       for (uint32_t i = 0; i < type.sizeInComponents; i++)
+                       {
+                               auto inMasked = in.Int(i) & mask;
+                               dst.replace(i, first ? inMasked : (dst.Int(i) | inMasked));
+                       }
+                       first = false;
+               }
+
+               return EmitResult::Continue;
+       }
+
        void SpirvShader::emitEpilog(SpirvRoutine *routine) const
        {
                for (auto insn : *this)
index 754d6b8..8268c71 100644 (file)
@@ -173,6 +173,11 @@ namespace sw
                                return &iter[n];
                        }
 
+                       bool operator==(InsnIterator const &other) const
+                       {
+                               return iter == other.iter;
+                       }
+
                        bool operator!=(InsnIterator const &other) const
                        {
                                return iter != other.iter;
@@ -596,6 +601,10 @@ namespace sw
                EmitResult EmitAny(InsnIterator insn, EmitState *state) const;
                EmitResult EmitAll(InsnIterator insn, EmitState *state) const;
                EmitResult EmitBranch(InsnIterator insn, EmitState *state) const;
+               EmitResult EmitBranchConditional(InsnIterator insn, EmitState *state) const;
+               EmitResult EmitUnreachable(InsnIterator insn, EmitState *state) const;
+               EmitResult EmitReturn(InsnIterator insn, EmitState *state) const;
+               EmitResult EmitPhi(InsnIterator insn, EmitState *state) const;
 
                // OpcodeName() returns the name of the opcode op.
                // If NDEBUG is defined, then OpcodeName() will only return the numerical code.
index faa396a..2dca716 100644 (file)
@@ -597,4 +597,323 @@ TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, BranchDeclareSSA)
               "OpFunctionEnd\n";\r
 \r
     test(src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return i * 2; });\r
-}
\ No newline at end of file
+}\r
+\r
+TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, BranchConditionalSimple)\r
+{\r
+    std::stringstream src;\r
+    src <<\r
+              "OpCapability Shader\n"\r
+              "OpMemoryModel Logical GLSL450\n"\r
+              "OpEntryPoint GLCompute %1 \"main\" %2\n"\r
+              "OpExecutionMode %1 LocalSize " <<\r
+                GetParam().localSizeX << " " <<\r
+                GetParam().localSizeY << " " <<\r
+                GetParam().localSizeZ << "\n" <<\r
+              "OpDecorate %3 ArrayStride 4\n"\r
+              "OpMemberDecorate %4 0 Offset 0\n"\r
+              "OpDecorate %4 BufferBlock\n"\r
+              "OpDecorate %5 DescriptorSet 0\n"\r
+              "OpDecorate %5 Binding 1\n"\r
+              "OpDecorate %2 BuiltIn GlobalInvocationId\n"\r
+              "OpDecorate %6 DescriptorSet 0\n"\r
+              "OpDecorate %6 Binding 0\n"\r
+         "%7 = OpTypeVoid\n"\r
+         "%8 = OpTypeFunction %7\n"             // void()\r
+         "%9 = OpTypeInt 32 1\n"                // int32\r
+        "%10 = OpTypeInt 32 0\n"                // uint32\r
+        "%11 = OpTypeBool\n"\r
+         "%3 = OpTypeRuntimeArray %9\n"         // int32[]\r
+         "%4 = OpTypeStruct %3\n"               // struct{ int32[] }\r
+        "%12 = OpTypePointer Uniform %4\n"      // struct{ int32[] }*\r
+         "%5 = OpVariable %12 Uniform\n"        // struct{ int32[] }* in\r
+        "%13 = OpConstant %9 0\n"               // int32(0)\r
+        "%14 = OpConstant %9 2\n"               // int32(2)\r
+        "%15 = OpConstant %10 0\n"              // uint32(0)\r
+        "%16 = OpTypeVector %10 3\n"            // vec4<int32>\r
+        "%17 = OpTypePointer Input %16\n"       // vec4<int32>*\r
+         "%2 = OpVariable %17 Input\n"          // gl_GlobalInvocationId\r
+        "%18 = OpTypePointer Input %10\n"       // uint32*\r
+         "%6 = OpVariable %12 Uniform\n"        // struct{ int32[] }* out\r
+        "%19 = OpTypePointer Uniform %9\n"      // int32*\r
+         "%1 = OpFunction %7 None %8\n"         // -- Function begin --\r
+        "%20 = OpLabel\n"\r
+        "%21 = OpAccessChain %18 %2 %15\n"      // &gl_GlobalInvocationId.x\r
+        "%22 = OpLoad %10 %21\n"                // gl_GlobalInvocationId.x\r
+        "%23 = OpAccessChain %19 %6 %13 %22\n"  // &in.arr[gl_GlobalInvocationId.x]\r
+        "%24 = OpLoad %9 %23\n"                 // in.arr[gl_GlobalInvocationId.x]\r
+        "%25 = OpAccessChain %19 %5 %13 %22\n"  // &out.arr[gl_GlobalInvocationId.x]\r
+    // Start of branch logic\r
+    // %24 = in value\r
+        "%26 = OpSMod %9 %24 %14\n"             // in % 2\r
+        "%27 = OpIEqual %11 %26 %13\n"          // (in % 2) == 0\r
+              "OpSelectionMerge %28 None\n"\r
+              "OpBranchConditional %27 %28 %28\n" // Both go to %28\r
+        "%28 = OpLabel\n"\r
+    // %26 = out value\r
+    // End of branch logic\r
+              "OpStore %25 %26\n"               // use SSA value from previous block\r
+              "OpReturn\n"\r
+              "OpFunctionEnd\n";\r
+\r
+    test(src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return i%2; });\r
+}\r
+\r
+TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, BranchConditionalTwoEmptyBlocks)\r
+{\r
+    std::stringstream src;\r
+    src <<\r
+              "OpCapability Shader\n"\r
+              "OpMemoryModel Logical GLSL450\n"\r
+              "OpEntryPoint GLCompute %1 \"main\" %2\n"\r
+              "OpExecutionMode %1 LocalSize " <<\r
+                GetParam().localSizeX << " " <<\r
+                GetParam().localSizeY << " " <<\r
+                GetParam().localSizeZ << "\n" <<\r
+              "OpDecorate %3 ArrayStride 4\n"\r
+              "OpMemberDecorate %4 0 Offset 0\n"\r
+              "OpDecorate %4 BufferBlock\n"\r
+              "OpDecorate %5 DescriptorSet 0\n"\r
+              "OpDecorate %5 Binding 1\n"\r
+              "OpDecorate %2 BuiltIn GlobalInvocationId\n"\r
+              "OpDecorate %6 DescriptorSet 0\n"\r
+              "OpDecorate %6 Binding 0\n"\r
+         "%7 = OpTypeVoid\n"\r
+         "%8 = OpTypeFunction %7\n"             // void()\r
+         "%9 = OpTypeInt 32 1\n"                // int32\r
+        "%10 = OpTypeInt 32 0\n"                // uint32\r
+        "%11 = OpTypeBool\n"\r
+         "%3 = OpTypeRuntimeArray %9\n"         // int32[]\r
+         "%4 = OpTypeStruct %3\n"               // struct{ int32[] }\r
+        "%12 = OpTypePointer Uniform %4\n"      // struct{ int32[] }*\r
+         "%5 = OpVariable %12 Uniform\n"        // struct{ int32[] }* in\r
+        "%13 = OpConstant %9 0\n"               // int32(0)\r
+        "%14 = OpConstant %9 2\n"               // int32(2)\r
+        "%15 = OpConstant %10 0\n"              // uint32(0)\r
+        "%16 = OpTypeVector %10 3\n"            // vec4<int32>\r
+        "%17 = OpTypePointer Input %16\n"       // vec4<int32>*\r
+         "%2 = OpVariable %17 Input\n"          // gl_GlobalInvocationId\r
+        "%18 = OpTypePointer Input %10\n"       // uint32*\r
+         "%6 = OpVariable %12 Uniform\n"        // struct{ int32[] }* out\r
+        "%19 = OpTypePointer Uniform %9\n"      // int32*\r
+         "%1 = OpFunction %7 None %8\n"         // -- Function begin --\r
+        "%20 = OpLabel\n"\r
+        "%21 = OpAccessChain %18 %2 %15\n"      // &gl_GlobalInvocationId.x\r
+        "%22 = OpLoad %10 %21\n"                // gl_GlobalInvocationId.x\r
+        "%23 = OpAccessChain %19 %6 %13 %22\n"  // &in.arr[gl_GlobalInvocationId.x]\r
+        "%24 = OpLoad %9 %23\n"                 // in.arr[gl_GlobalInvocationId.x]\r
+        "%25 = OpAccessChain %19 %5 %13 %22\n"  // &out.arr[gl_GlobalInvocationId.x]\r
+    // Start of branch logic\r
+    // %24 = in value\r
+        "%26 = OpSMod %9 %24 %14\n"             // in % 2\r
+        "%27 = OpIEqual %11 %26 %13\n"          // (in % 2) == 0\r
+              "OpSelectionMerge %28 None\n"\r
+              "OpBranchConditional %27 %29 %30\n"\r
+        "%29 = OpLabel\n"                       // (in % 2) == 0\r
+              "OpBranch %28\n"\r
+        "%30 = OpLabel\n"                       // (in % 2) != 0\r
+              "OpBranch %28\n"\r
+        "%28 = OpLabel\n"\r
+    // %26 = out value\r
+    // End of branch logic\r
+              "OpStore %25 %26\n"               // use SSA value from previous block\r
+              "OpReturn\n"\r
+              "OpFunctionEnd\n";\r
+\r
+    test(src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return i%2; });\r
+}\r
+\r
+// TODO: Test for parallel assignment\r
+TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, BranchConditionalStore)\r
+{\r
+    std::stringstream src;\r
+    src <<\r
+              "OpCapability Shader\n"\r
+              "OpMemoryModel Logical GLSL450\n"\r
+              "OpEntryPoint GLCompute %1 \"main\" %2\n"\r
+              "OpExecutionMode %1 LocalSize " <<\r
+                GetParam().localSizeX << " " <<\r
+                GetParam().localSizeY << " " <<\r
+                GetParam().localSizeZ << "\n" <<\r
+              "OpDecorate %3 ArrayStride 4\n"\r
+              "OpMemberDecorate %4 0 Offset 0\n"\r
+              "OpDecorate %4 BufferBlock\n"\r
+              "OpDecorate %5 DescriptorSet 0\n"\r
+              "OpDecorate %5 Binding 1\n"\r
+              "OpDecorate %2 BuiltIn GlobalInvocationId\n"\r
+              "OpDecorate %6 DescriptorSet 0\n"\r
+              "OpDecorate %6 Binding 0\n"\r
+         "%7 = OpTypeVoid\n"\r
+         "%8 = OpTypeFunction %7\n"             // void()\r
+         "%9 = OpTypeInt 32 1\n"                // int32\r
+        "%10 = OpTypeInt 32 0\n"                // uint32\r
+        "%11 = OpTypeBool\n"\r
+         "%3 = OpTypeRuntimeArray %9\n"         // int32[]\r
+         "%4 = OpTypeStruct %3\n"               // struct{ int32[] }\r
+        "%12 = OpTypePointer Uniform %4\n"      // struct{ int32[] }*\r
+         "%5 = OpVariable %12 Uniform\n"        // struct{ int32[] }* in\r
+        "%13 = OpConstant %9 0\n"               // int32(0)\r
+        "%14 = OpConstant %9 1\n"               // int32(1)\r
+        "%15 = OpConstant %9 2\n"               // int32(2)\r
+        "%16 = OpConstant %10 0\n"              // uint32(0)\r
+        "%17 = OpTypeVector %10 3\n"            // vec4<int32>\r
+        "%18 = OpTypePointer Input %17\n"       // vec4<int32>*\r
+         "%2 = OpVariable %18 Input\n"          // gl_GlobalInvocationId\r
+        "%19 = OpTypePointer Input %10\n"       // uint32*\r
+         "%6 = OpVariable %12 Uniform\n"        // struct{ int32[] }* out\r
+        "%20 = OpTypePointer Uniform %9\n"      // int32*\r
+         "%1 = OpFunction %7 None %8\n"         // -- Function begin --\r
+        "%21 = OpLabel\n"\r
+        "%22 = OpAccessChain %19 %2 %16\n"      // &gl_GlobalInvocationId.x\r
+        "%23 = OpLoad %10 %22\n"                // gl_GlobalInvocationId.x\r
+        "%24 = OpAccessChain %20 %6 %13 %23\n"  // &in.arr[gl_GlobalInvocationId.x]\r
+        "%25 = OpLoad %9 %24\n"                 // in.arr[gl_GlobalInvocationId.x]\r
+        "%26 = OpAccessChain %20 %5 %13 %23\n"  // &out.arr[gl_GlobalInvocationId.x]\r
+    // Start of branch logic\r
+    // %25 = in value\r
+        "%27 = OpSMod %9 %25 %15\n"             // in % 2\r
+        "%28 = OpIEqual %11 %27 %13\n"          // (in % 2) == 0\r
+              "OpSelectionMerge %29 None\n"\r
+              "OpBranchConditional %28 %30 %31\n"\r
+        "%30 = OpLabel\n"                       // (in % 2) == 0\r
+              "OpStore %26 %14\n"               // write 1\r
+              "OpBranch %29\n"\r
+        "%31 = OpLabel\n"                       // (in % 2) != 0\r
+              "OpStore %26 %15\n"               // write 2\r
+              "OpBranch %29\n"\r
+        "%29 = OpLabel\n"\r
+    // End of branch logic\r
+              "OpReturn\n"\r
+              "OpFunctionEnd\n";\r
+\r
+    test(src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return (i % 2) == 0 ? 1 : 2; });\r
+}\r
+\r
+TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, BranchConditionalReturnTrue)\r
+{\r
+    std::stringstream src;\r
+    src <<\r
+              "OpCapability Shader\n"\r
+              "OpMemoryModel Logical GLSL450\n"\r
+              "OpEntryPoint GLCompute %1 \"main\" %2\n"\r
+              "OpExecutionMode %1 LocalSize " <<\r
+                GetParam().localSizeX << " " <<\r
+                GetParam().localSizeY << " " <<\r
+                GetParam().localSizeZ << "\n" <<\r
+              "OpDecorate %3 ArrayStride 4\n"\r
+              "OpMemberDecorate %4 0 Offset 0\n"\r
+              "OpDecorate %4 BufferBlock\n"\r
+              "OpDecorate %5 DescriptorSet 0\n"\r
+              "OpDecorate %5 Binding 1\n"\r
+              "OpDecorate %2 BuiltIn GlobalInvocationId\n"\r
+              "OpDecorate %6 DescriptorSet 0\n"\r
+              "OpDecorate %6 Binding 0\n"\r
+         "%7 = OpTypeVoid\n"\r
+         "%8 = OpTypeFunction %7\n"             // void()\r
+         "%9 = OpTypeInt 32 1\n"                // int32\r
+        "%10 = OpTypeInt 32 0\n"                // uint32\r
+        "%11 = OpTypeBool\n"\r
+         "%3 = OpTypeRuntimeArray %9\n"         // int32[]\r
+         "%4 = OpTypeStruct %3\n"               // struct{ int32[] }\r
+        "%12 = OpTypePointer Uniform %4\n"      // struct{ int32[] }*\r
+         "%5 = OpVariable %12 Uniform\n"        // struct{ int32[] }* in\r
+        "%13 = OpConstant %9 0\n"               // int32(0)\r
+        "%14 = OpConstant %9 1\n"               // int32(1)\r
+        "%15 = OpConstant %9 2\n"               // int32(2)\r
+        "%16 = OpConstant %10 0\n"              // uint32(0)\r
+        "%17 = OpTypeVector %10 3\n"            // vec4<int32>\r
+        "%18 = OpTypePointer Input %17\n"       // vec4<int32>*\r
+         "%2 = OpVariable %18 Input\n"          // gl_GlobalInvocationId\r
+        "%19 = OpTypePointer Input %10\n"       // uint32*\r
+         "%6 = OpVariable %12 Uniform\n"        // struct{ int32[] }* out\r
+        "%20 = OpTypePointer Uniform %9\n"      // int32*\r
+         "%1 = OpFunction %7 None %8\n"         // -- Function begin --\r
+        "%21 = OpLabel\n"\r
+        "%22 = OpAccessChain %19 %2 %16\n"      // &gl_GlobalInvocationId.x\r
+        "%23 = OpLoad %10 %22\n"                // gl_GlobalInvocationId.x\r
+        "%24 = OpAccessChain %20 %6 %13 %23\n"  // &in.arr[gl_GlobalInvocationId.x]\r
+        "%25 = OpLoad %9 %24\n"                 // in.arr[gl_GlobalInvocationId.x]\r
+        "%26 = OpAccessChain %20 %5 %13 %23\n"  // &out.arr[gl_GlobalInvocationId.x]\r
+    // Start of branch logic\r
+    // %25 = in value\r
+        "%27 = OpSMod %9 %25 %15\n"             // in % 2\r
+        "%28 = OpIEqual %11 %27 %13\n"          // (in % 2) == 0\r
+              "OpSelectionMerge %29 None\n"\r
+              "OpBranchConditional %28 %30 %29\n"\r
+        "%30 = OpLabel\n"                       // (in % 2) == 0\r
+              "OpReturn\n"\r
+        "%29 = OpLabel\n"                       // merge\r
+              "OpStore %26 %15\n"               // write 2\r
+    // End of branch logic\r
+              "OpReturn\n"\r
+              "OpFunctionEnd\n";\r
+\r
+    test(src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return (i % 2) == 0 ? 0 : 2; });\r
+}\r
+\r
+// TODO: Test for parallel assignment\r
+TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, BranchConditionalPhi)\r
+{\r
+    std::stringstream src;\r
+    src <<\r
+              "OpCapability Shader\n"\r
+              "OpMemoryModel Logical GLSL450\n"\r
+              "OpEntryPoint GLCompute %1 \"main\" %2\n"\r
+              "OpExecutionMode %1 LocalSize " <<\r
+                GetParam().localSizeX << " " <<\r
+                GetParam().localSizeY << " " <<\r
+                GetParam().localSizeZ << "\n" <<\r
+              "OpDecorate %3 ArrayStride 4\n"\r
+              "OpMemberDecorate %4 0 Offset 0\n"\r
+              "OpDecorate %4 BufferBlock\n"\r
+              "OpDecorate %5 DescriptorSet 0\n"\r
+              "OpDecorate %5 Binding 1\n"\r
+              "OpDecorate %2 BuiltIn GlobalInvocationId\n"\r
+              "OpDecorate %6 DescriptorSet 0\n"\r
+              "OpDecorate %6 Binding 0\n"\r
+         "%7 = OpTypeVoid\n"\r
+         "%8 = OpTypeFunction %7\n"             // void()\r
+         "%9 = OpTypeInt 32 1\n"                // int32\r
+        "%10 = OpTypeInt 32 0\n"                // uint32\r
+        "%11 = OpTypeBool\n"\r
+         "%3 = OpTypeRuntimeArray %9\n"         // int32[]\r
+         "%4 = OpTypeStruct %3\n"               // struct{ int32[] }\r
+        "%12 = OpTypePointer Uniform %4\n"      // struct{ int32[] }*\r
+         "%5 = OpVariable %12 Uniform\n"        // struct{ int32[] }* in\r
+        "%13 = OpConstant %9 0\n"               // int32(0)\r
+        "%14 = OpConstant %9 1\n"               // int32(1)\r
+        "%15 = OpConstant %9 2\n"               // int32(2)\r
+        "%16 = OpConstant %10 0\n"              // uint32(0)\r
+        "%17 = OpTypeVector %10 3\n"            // vec4<int32>\r
+        "%18 = OpTypePointer Input %17\n"       // vec4<int32>*\r
+         "%2 = OpVariable %18 Input\n"          // gl_GlobalInvocationId\r
+        "%19 = OpTypePointer Input %10\n"       // uint32*\r
+         "%6 = OpVariable %12 Uniform\n"        // struct{ int32[] }* out\r
+        "%20 = OpTypePointer Uniform %9\n"      // int32*\r
+         "%1 = OpFunction %7 None %8\n"         // -- Function begin --\r
+        "%21 = OpLabel\n"\r
+        "%22 = OpAccessChain %19 %2 %16\n"      // &gl_GlobalInvocationId.x\r
+        "%23 = OpLoad %10 %22\n"                // gl_GlobalInvocationId.x\r
+        "%24 = OpAccessChain %20 %6 %13 %23\n"  // &in.arr[gl_GlobalInvocationId.x]\r
+        "%25 = OpLoad %9 %24\n"                 // in.arr[gl_GlobalInvocationId.x]\r
+        "%26 = OpAccessChain %20 %5 %13 %23\n"  // &out.arr[gl_GlobalInvocationId.x]\r
+    // Start of branch logic\r
+    // %25 = in value\r
+        "%27 = OpSMod %9 %25 %15\n"             // in % 2\r
+        "%28 = OpIEqual %11 %27 %13\n"          // (in % 2) == 0\r
+              "OpSelectionMerge %29 None\n"\r
+              "OpBranchConditional %28 %30 %31\n"\r
+        "%30 = OpLabel\n"                       // (in % 2) == 0\r
+              "OpBranch %29\n"\r
+        "%31 = OpLabel\n"                       // (in % 2) != 0\r
+              "OpBranch %29\n"\r
+        "%29 = OpLabel\n"\r
+        "%32 = OpPhi %9 %14 %30 %15 %31\n"      // (in % 2) == 0 ? 1 : 2\r
+    // End of branch logic\r
+              "OpStore %26 %32\n"\r
+              "OpReturn\n"\r
+              "OpFunctionEnd\n";\r
+\r
+    test(src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return (i % 2) == 0 ? 1 : 2; });\r
+}\r
+\r