Commit 9fd02e0d by Ben Clayton

SpirvShader: Implement OpBranchConditional, OpPhi, ...

... 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: 's avatarBen Clayton <bclayton@google.com> Kokoro-Presubmit: kokoro <noreply+kokoro@google.com> Reviewed-by: 's avatarNicolas Capens <nicolascapens@google.com>
parent 54ec759a
...@@ -146,6 +146,9 @@ namespace sw ...@@ -146,6 +146,9 @@ namespace sw
break; break;
} }
case spv::OpSelectionMerge:
break; // Nothing to do in analysis pass.
case spv::OpTypeVoid: case spv::OpTypeVoid:
case spv::OpTypeBool: case spv::OpTypeBool:
case spv::OpTypeInt: case spv::OpTypeInt:
...@@ -417,6 +420,7 @@ namespace sw ...@@ -417,6 +420,7 @@ namespace sw
case spv::OpDPdyFine: case spv::OpDPdyFine:
case spv::OpFwidthFine: case spv::OpFwidthFine:
case spv::OpAtomicLoad: case spv::OpAtomicLoad:
case spv::OpPhi:
// Instructions that yield an intermediate value // Instructions that yield an intermediate value
{ {
Type::ID typeId = insn.word(1); Type::ID typeId = insn.word(1);
...@@ -1173,6 +1177,8 @@ namespace sw ...@@ -1173,6 +1177,8 @@ namespace sw
switch (block.kind) switch (block.kind)
{ {
case Block::Simple: case Block::Simple:
case Block::StructuredBranchConditional:
case Block::UnstructuredBranchConditional:
if (id != mainBlockId) if (id != mainBlockId)
{ {
// Emit all preceeding blocks and set the activeLaneMask. // Emit all preceeding blocks and set the activeLaneMask.
...@@ -1261,11 +1267,6 @@ namespace sw ...@@ -1261,11 +1267,6 @@ namespace sw
return EmitResult::Continue; return EmitResult::Continue;
case spv::OpLabel: 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; return EmitResult::Continue;
case spv::OpVariable: case spv::OpVariable:
...@@ -1394,6 +1395,21 @@ namespace sw ...@@ -1394,6 +1395,21 @@ namespace sw
case spv::OpBranch: case spv::OpBranch:
return EmitBranch(insn, state); 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: default:
UNIMPLEMENTED("opcode: %s", OpcodeName(insn.opcode()).c_str()); UNIMPLEMENTED("opcode: %s", OpcodeName(insn.opcode()).c_str());
break; break;
...@@ -2602,6 +2618,69 @@ namespace sw ...@@ -2602,6 +2618,69 @@ namespace sw
return EmitResult::Terminator; 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 void SpirvShader::emitEpilog(SpirvRoutine *routine) const
{ {
for (auto insn : *this) for (auto insn : *this)
......
...@@ -173,6 +173,11 @@ namespace sw ...@@ -173,6 +173,11 @@ namespace sw
return &iter[n]; return &iter[n];
} }
bool operator==(InsnIterator const &other) const
{
return iter == other.iter;
}
bool operator!=(InsnIterator const &other) const bool operator!=(InsnIterator const &other) const
{ {
return iter != other.iter; return iter != other.iter;
...@@ -596,6 +601,10 @@ namespace sw ...@@ -596,6 +601,10 @@ namespace sw
EmitResult EmitAny(InsnIterator insn, EmitState *state) const; EmitResult EmitAny(InsnIterator insn, EmitState *state) const;
EmitResult EmitAll(InsnIterator insn, EmitState *state) const; EmitResult EmitAll(InsnIterator insn, EmitState *state) const;
EmitResult EmitBranch(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. // OpcodeName() returns the name of the opcode op.
// If NDEBUG is defined, then OpcodeName() will only return the numerical code. // If NDEBUG is defined, then OpcodeName() will only return the numerical code.
......
...@@ -597,4 +597,323 @@ TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, BranchDeclareSSA) ...@@ -597,4 +597,323 @@ TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, BranchDeclareSSA)
"OpFunctionEnd\n"; "OpFunctionEnd\n";
test(src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return i * 2; }); test(src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return i * 2; });
} }
\ No newline at end of file
TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, BranchConditionalSimple)
{
std::stringstream src;
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<int32>
"%17 = OpTypePointer Input %16\n" // vec4<int32>*
"%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";
test(src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return i%2; });
}
TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, BranchConditionalTwoEmptyBlocks)
{
std::stringstream src;
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<int32>
"%17 = OpTypePointer Input %16\n" // vec4<int32>*
"%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";
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;
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<int32>
"%18 = OpTypePointer Input %17\n" // vec4<int32>*
"%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";
test(src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return (i % 2) == 0 ? 1 : 2; });
}
TEST_P(SwiftShaderVulkanBufferToBufferComputeTest, BranchConditionalReturnTrue)
{
std::stringstream src;
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<int32>
"%18 = OpTypePointer Input %17\n" // vec4<int32>*
"%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";
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;
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<int32>
"%18 = OpTypePointer Input %17\n" // vec4<int32>*
"%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";
test(src.str(), [](uint32_t i) { return i; }, [](uint32_t i) { return (i % 2) == 0 ? 1 : 2; });
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment