Merge pull request #1034 from KhronosGroup/fix-1033

Deal with OpSwitch case fallthrough
This commit is contained in:
Hans-Kristian Arntzen 2019-06-21 15:55:33 +02:00 committed by GitHub
commit 39f23cd058
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 655 additions and 55 deletions

View File

@ -16,7 +16,6 @@ void main()
_19 = 1.0;
break;
}
case 1:
default:
{
_19 = 3.0;

View File

@ -0,0 +1,79 @@
static int vIndex;
static float4 FragColor;
struct SPIRV_Cross_Input
{
nointerpolation int vIndex : TEXCOORD0;
};
struct SPIRV_Cross_Output
{
float4 FragColor : SV_Target0;
};
void frag_main()
{
int i = 0;
int j;
int _30;
int _31;
if (vIndex != 0 && vIndex != 1 && vIndex != 11 && vIndex != 2 && vIndex != 3 && vIndex != 4 && vIndex != 5)
{
_30 = 2;
}
if (vIndex == 1 || vIndex == 11)
{
_31 = 1;
}
switch (vIndex)
{
case 0:
{
_30 = 3;
}
default:
{
j = _30;
_31 = 0;
}
case 1:
case 11:
{
j = _31;
}
case 2:
{
break;
}
case 3:
{
if (vIndex > 3)
{
i = 0;
break;
}
else
{
break;
}
}
case 4:
{
}
case 5:
{
i = 0;
break;
}
}
FragColor = float(i).xxxx;
}
SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input)
{
vIndex = stage_input.vIndex;
frag_main();
SPIRV_Cross_Output stage_output;
stage_output.FragColor = FragColor;
return stage_output;
}

View File

@ -0,0 +1,75 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct main0_out
{
float4 FragColor [[color(0)]];
};
struct main0_in
{
int vIndex [[user(locn0)]];
};
fragment main0_out main0(main0_in in [[stage_in]])
{
int i = 0;
main0_out out = {};
int j;
int _30;
int _31;
if (in.vIndex != 0 && in.vIndex != 1 && in.vIndex != 11 && in.vIndex != 2 && in.vIndex != 3 && in.vIndex != 4 && in.vIndex != 5)
{
_30 = 2;
}
if (in.vIndex == 1 || in.vIndex == 11)
{
_31 = 1;
}
switch (in.vIndex)
{
case 0:
{
_30 = 3;
}
default:
{
j = _30;
_31 = 0;
}
case 1:
case 11:
{
j = _31;
}
case 2:
{
break;
}
case 3:
{
if (in.vIndex > 3)
{
i = 0;
break;
}
else
{
break;
}
}
case 4:
{
}
case 5:
{
i = 0;
break;
}
}
out.FragColor = float4(float(i));
return out;
}

View File

@ -0,0 +1,63 @@
#version 450
layout(location = 0) flat in int vIndex;
layout(location = 0) out vec4 FragColor;
void main()
{
int i;
int j;
int _30;
int _31;
if (vIndex != 0 && vIndex != 1 && vIndex != 11 && vIndex != 2 && vIndex != 3 && vIndex != 4 && vIndex != 5)
{
_30 = 2;
}
if (vIndex == 1 || vIndex == 11)
{
_31 = 1;
}
switch (vIndex)
{
case 0:
{
_30 = 3;
}
default:
{
j = _30;
_31 = 0;
}
case 1:
case 11:
{
j = _31;
}
case 2:
{
break;
}
case 3:
{
if (vIndex > 3)
{
i = 0;
break;
}
else
{
break;
}
}
case 4:
{
}
case 5:
{
i = 0;
break;
}
}
FragColor = vec4(float(i));
}

View File

@ -16,7 +16,6 @@ void main()
_19 = 1.0;
break;
}
case 1:
default:
{
_19 = 3.0;

View File

@ -0,0 +1,80 @@
; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 7
; Bound: 29
; Schema: 0
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %vIndex %FragColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 450
OpName %main "main"
OpName %vIndex "vIndex"
OpName %FragColor "FragColor"
OpName %i "i"
OpName %j "j"
OpDecorate %vIndex Flat
OpDecorate %vIndex Location 0
OpDecorate %FragColor Location 0
%void = OpTypeVoid
%3 = OpTypeFunction %void
%int = OpTypeInt 32 1
%bool = OpTypeBool
%int_0 = OpConstant %int 0
%int_1 = OpConstant %int 1
%int_2 = OpConstant %int 2
%int_3 = OpConstant %int 3
%_ptr_Input_int = OpTypePointer Input %int
%vIndex = OpVariable %_ptr_Input_int Input
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Output_v4float = OpTypePointer Output %v4float
%FragColor = OpVariable %_ptr_Output_v4float Output
%_ptr_Function_int = OpTypePointer Function %int
%main = OpFunction %void None %3
%header = OpLabel
%i = OpVariable %_ptr_Function_int Function %int_0
%j = OpVariable %_ptr_Function_int Function
%9 = OpLoad %int %vIndex
OpSelectionMerge %switch_merge None
OpSwitch %9 %default_case 100 %default_case 0 %case_0 1 %case_1 11 %case_1 2 %case_2 3 %case_3 4 %case_4 5 %case_5
%case_0 = OpLabel
OpBranch %default_case
%default_case = OpLabel
%default_case_phi = OpPhi %int %int_2 %header %int_3 %case_0
; Test what happens when a case block dominates access to a variable.
OpStore %j %default_case_phi
OpBranch %case_1
%case_1 = OpLabel
; Test phi nodes between case labels.
%case_1_phi = OpPhi %int %int_0 %default_case %int_1 %header
OpStore %j %case_1_phi
OpBranch %case_2
%case_2 = OpLabel
OpBranch %switch_merge
%case_3 = OpLabel
; Conditionally branch to another case block. This is really dumb, but it is apparently legal.
%case_3_cond = OpSGreaterThan %bool %9 %int_3
OpBranchConditional %case_3_cond %case_4 %switch_merge
%case_4 = OpLabel
; When emitted from case 3, we should *not* see fallthrough behavior.
OpBranch %case_5
%case_5 = OpLabel
OpStore %i %int_0
OpBranch %switch_merge
%switch_merge = OpLabel
%26 = OpLoad %int %i
%27 = OpConvertSToF %float %26
%28 = OpCompositeConstruct %v4float %27 %27 %27 %27
OpStore %FragColor %28
OpReturn
OpFunctionEnd

View File

@ -0,0 +1,80 @@
; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 7
; Bound: 29
; Schema: 0
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %vIndex %FragColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 450
OpName %main "main"
OpName %vIndex "vIndex"
OpName %FragColor "FragColor"
OpName %i "i"
OpName %j "j"
OpDecorate %vIndex Flat
OpDecorate %vIndex Location 0
OpDecorate %FragColor Location 0
%void = OpTypeVoid
%3 = OpTypeFunction %void
%int = OpTypeInt 32 1
%bool = OpTypeBool
%int_0 = OpConstant %int 0
%int_1 = OpConstant %int 1
%int_2 = OpConstant %int 2
%int_3 = OpConstant %int 3
%_ptr_Input_int = OpTypePointer Input %int
%vIndex = OpVariable %_ptr_Input_int Input
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Output_v4float = OpTypePointer Output %v4float
%FragColor = OpVariable %_ptr_Output_v4float Output
%_ptr_Function_int = OpTypePointer Function %int
%main = OpFunction %void None %3
%header = OpLabel
%i = OpVariable %_ptr_Function_int Function %int_0
%j = OpVariable %_ptr_Function_int Function
%9 = OpLoad %int %vIndex
OpSelectionMerge %switch_merge None
OpSwitch %9 %default_case 100 %default_case 0 %case_0 1 %case_1 11 %case_1 2 %case_2 3 %case_3 4 %case_4 5 %case_5
%case_0 = OpLabel
OpBranch %default_case
%default_case = OpLabel
%default_case_phi = OpPhi %int %int_2 %header %int_3 %case_0
; Test what happens when a case block dominates access to a variable.
OpStore %j %default_case_phi
OpBranch %case_1
%case_1 = OpLabel
; Test phi nodes between case labels.
%case_1_phi = OpPhi %int %int_0 %default_case %int_1 %header
OpStore %j %case_1_phi
OpBranch %case_2
%case_2 = OpLabel
OpBranch %switch_merge
%case_3 = OpLabel
; Conditionally branch to another case block. This is really dumb, but it is apparently legal.
%case_3_cond = OpSGreaterThan %bool %9 %int_3
OpBranchConditional %case_3_cond %case_4 %switch_merge
%case_4 = OpLabel
; When emitted from case 3, we should *not* see fallthrough behavior.
OpBranch %case_5
%case_5 = OpLabel
OpStore %i %int_0
OpBranch %switch_merge
%switch_merge = OpLabel
%26 = OpLoad %int %i
%27 = OpConvertSToF %float %26
%28 = OpCompositeConstruct %v4float %27 %27 %27 %27
OpStore %FragColor %28
OpReturn
OpFunctionEnd

View File

@ -0,0 +1,80 @@
; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 7
; Bound: 29
; Schema: 0
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %vIndex %FragColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 450
OpName %main "main"
OpName %vIndex "vIndex"
OpName %FragColor "FragColor"
OpName %i "i"
OpName %j "j"
OpDecorate %vIndex Flat
OpDecorate %vIndex Location 0
OpDecorate %FragColor Location 0
%void = OpTypeVoid
%3 = OpTypeFunction %void
%int = OpTypeInt 32 1
%bool = OpTypeBool
%int_0 = OpConstant %int 0
%int_1 = OpConstant %int 1
%int_2 = OpConstant %int 2
%int_3 = OpConstant %int 3
%_ptr_Input_int = OpTypePointer Input %int
%vIndex = OpVariable %_ptr_Input_int Input
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Output_v4float = OpTypePointer Output %v4float
%FragColor = OpVariable %_ptr_Output_v4float Output
%_ptr_Function_int = OpTypePointer Function %int
%main = OpFunction %void None %3
%header = OpLabel
%i = OpVariable %_ptr_Function_int Function
%j = OpVariable %_ptr_Function_int Function
%9 = OpLoad %int %vIndex
OpSelectionMerge %switch_merge None
OpSwitch %9 %default_case 100 %default_case 0 %case_0 1 %case_1 11 %case_1 2 %case_2 3 %case_3 4 %case_4 5 %case_5
%case_0 = OpLabel
OpBranch %default_case
%default_case = OpLabel
%default_case_phi = OpPhi %int %int_2 %header %int_3 %case_0
; Test what happens when a case block dominates access to a variable.
OpStore %j %default_case_phi
OpBranch %case_1
%case_1 = OpLabel
; Test phi nodes between case labels.
%case_1_phi = OpPhi %int %int_0 %default_case %int_1 %header
OpStore %j %case_1_phi
OpBranch %case_2
%case_2 = OpLabel
OpBranch %switch_merge
%case_3 = OpLabel
; Conditionally branch to another case block. This is really dumb, but it is apparently legal.
%case_3_cond = OpSGreaterThan %bool %9 %int_3
OpBranchConditional %case_3_cond %case_4 %switch_merge
%case_4 = OpLabel
; When emitted from case 3, we should *not* see fallthrough behavior.
OpBranch %case_5
%case_5 = OpLabel
OpStore %i %int_0
OpBranch %switch_merge
%switch_merge = OpLabel
%26 = OpLoad %int %i
%27 = OpConvertSToF %float %26
%28 = OpCompositeConstruct %v4float %27 %27 %27 %27
OpStore %FragColor %28
OpReturn
OpFunctionEnd

View File

@ -183,14 +183,14 @@ std::string join(Ts &&... ts)
return stream.str();
}
inline std::string merge(const SmallVector<std::string> &list)
inline std::string merge(const SmallVector<std::string> &list, const char *between = ", ")
{
StringStream<> stream;
for (auto &elem : list)
{
stream << elem;
if (&elem != &list.back())
stream << ", ";
stream << between;
}
return stream.str();
}
@ -704,6 +704,10 @@ struct SPIRBlock : IVariant
// Do we need a ladder variable to defer breaking out of a loop construct after a switch block?
bool need_ladder_break = false;
// If marked, we have explicitly handled Phi from this block, so skip any flushes related to that on a branch.
// Used to handle an edge case with switch and case-label fallthrough where fall-through writes to Phi.
uint32_t ignore_phi_from_block = 0;
// The dominating block which this block might be within.
// Used in continue; blocks to determine if we really need to write continue.
uint32_t loop_dominator = 0;

View File

@ -1600,6 +1600,11 @@ bool Compiler::execution_is_branchless(const SPIRBlock &from, const SPIRBlock &t
}
}
bool Compiler::execution_is_direct_branch(const SPIRBlock &from, const SPIRBlock &to) const
{
return from.terminator == SPIRBlock::Direct && from.merge == SPIRBlock::MergeNone && from.next_block == to.self;
}
SPIRBlock::ContinueBlockType Compiler::continue_block_type(const SPIRBlock &block) const
{
// The block was deemed too complex during code emit, pick conservative fallback paths.
@ -4372,3 +4377,4 @@ bool Compiler::type_is_array_of_pointers(const SPIRType &type) const
// If parent type has same pointer depth, we must have an array of pointers.
return type.pointer_depth == get<SPIRType>(type.parent_type).pointer_depth;
}

View File

@ -669,6 +669,7 @@ protected:
bool block_is_outside_flow_control_from_block(const SPIRBlock &from, const SPIRBlock &to);
bool execution_is_branchless(const SPIRBlock &from, const SPIRBlock &to) const;
bool execution_is_direct_branch(const SPIRBlock &from, const SPIRBlock &to) const;
bool execution_is_noop(const SPIRBlock &from, const SPIRBlock &to) const;
SPIRBlock::ContinueBlockType continue_block_type(const SPIRBlock &continue_block) const;

View File

@ -447,6 +447,11 @@ public:
}
}
void insert(T *itr, const T &value)
{
insert(itr, &value, &value + 1);
}
T *erase(T *itr)
{
std::move(itr + 1, this->end(), itr);

View File

@ -288,8 +288,11 @@ static uint32_t pls_format_to_components(PlsFormat format)
static const char *vector_swizzle(int vecsize, int index)
{
static const char *swizzle[4][4] = {
{ ".x", ".y", ".z", ".w" }, { ".xy", ".yz", ".zw" }, { ".xyz", ".yzw" }, { "" }
static const char * const swizzle[4][4] = {
{ ".x", ".y", ".z", ".w" },
{ ".xy", ".yz", ".zw", nullptr },
{ ".xyz", ".yzw", nullptr, nullptr },
{ "", nullptr, nullptr, nullptr },
};
assert(vecsize >= 1 && vecsize <= 4);
@ -10723,6 +10726,8 @@ bool CompilerGLSL::flush_phi_required(uint32_t from, uint32_t to)
void CompilerGLSL::flush_phi(uint32_t from, uint32_t to)
{
auto &child = get<SPIRBlock>(to);
if (child.ignore_phi_from_block == from)
return;
unordered_set<uint32_t> temporary_phi_variables;
@ -11483,7 +11488,8 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block)
case SPIRBlock::MultiSelect:
{
auto &type = expression_type(block.condition);
bool unsigned_case = type.basetype == SPIRType::UInt || type.basetype == SPIRType::UShort;
bool unsigned_case = type.basetype == SPIRType::UInt || type.basetype == SPIRType::UShort ||
type.basetype == SPIRType::UByte;
if (block.merge == SPIRBlock::MergeNone)
SPIRV_CROSS_THROW("Switch statement is not structured");
@ -11508,61 +11514,179 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block)
if (block.need_ladder_break)
statement("bool _", block.self, "_ladder_break = false;");
// Find all unique case constructs.
unordered_map<uint32_t, SmallVector<uint32_t>> case_constructs;
SmallVector<uint32_t> block_declaration_order;
SmallVector<uint32_t> literals_to_merge;
// If a switch case branches to the default block for some reason, we can just remove that literal from consideration
// and let the default: block handle it.
// 2.11 in SPIR-V spec states that for fall-through cases, there is a very strict declaration order which we can take advantage of here.
// We only need to consider possible fallthrough if order[i] branches to order[i + 1].
for (auto &c : block.cases)
{
if (c.block != block.next_block && c.block != block.default_block)
{
if (!case_constructs.count(c.block))
block_declaration_order.push_back(c.block);
case_constructs[c.block].push_back(c.value);
}
else if (c.block == block.next_block && block.default_block != block.next_block)
{
// We might have to flush phi inside specific case labels.
// If we can piggyback on default:, do so instead.
literals_to_merge.push_back(c.value);
}
}
// Empty literal array -> default.
if (block.default_block != block.next_block)
{
auto &default_block = get<SPIRBlock>(block.default_block);
// We need to slide in the default block somewhere in this chain
// if there are fall-through scenarios since the default is declared separately in OpSwitch.
// Only consider trivial fall-through cases here.
size_t num_blocks = block_declaration_order.size();
bool injected_block = false;
for (size_t i = 0; i < num_blocks; i++)
{
auto &case_block = get<SPIRBlock>(block_declaration_order[i]);
if (execution_is_direct_branch(case_block, default_block))
{
// Fallthrough to default block, we must inject the default block here.
block_declaration_order.insert(begin(block_declaration_order) + i + 1, block.default_block);
injected_block = true;
break;
}
else if (execution_is_direct_branch(default_block, case_block))
{
// Default case is falling through to another case label, we must inject the default block here.
block_declaration_order.insert(begin(block_declaration_order) + i, block.default_block);
injected_block = true;
break;
}
}
// Order does not matter.
if (!injected_block)
block_declaration_order.push_back(block.default_block);
case_constructs[block.default_block] = {};
}
size_t num_blocks = block_declaration_order.size();
const auto to_case_label = [](uint32_t literal, bool is_unsigned_case) -> string {
return is_unsigned_case ? convert_to_string(literal) : convert_to_string(int32_t(literal));
};
// We need to deal with a complex scenario for OpPhi. If we have case-fallthrough and Phi in the picture,
// we need to flush phi nodes outside the switch block in a branch,
// and skip any Phi handling inside the case label to make fall-through work as expected.
// This kind of code-gen is super awkward and it's a last resort. Normally we would want to handle this
// inside the case label if at all possible.
for (size_t i = 1; i < num_blocks; i++)
{
if (flush_phi_required(block.self, block_declaration_order[i]) &&
flush_phi_required(block_declaration_order[i - 1], block_declaration_order[i]))
{
uint32_t target_block = block_declaration_order[i];
// Make sure we flush Phi, it might have been marked to be ignored earlier.
get<SPIRBlock>(target_block).ignore_phi_from_block = 0;
auto &literals = case_constructs[target_block];
if (literals.empty())
{
// Oh boy, gotta make a complete negative test instead! o.o
// Find all possible literals that would *not* make us enter the default block.
// If none of those literals match, we flush Phi ...
SmallVector<string> conditions;
for (size_t j = 0; j < num_blocks; j++)
{
auto &negative_literals = case_constructs[block_declaration_order[j]];
for (auto &case_label : negative_literals)
conditions.push_back(join(to_enclosed_expression(block.condition), " != ", to_case_label(case_label, unsigned_case)));
}
statement("if (", merge(conditions, " && "), ")");
begin_scope();
flush_phi(block.self, target_block);
end_scope();
}
else
{
SmallVector<string> conditions;
conditions.reserve(literals.size());
for (auto &case_label : literals)
conditions.push_back(join(to_enclosed_expression(block.condition), " == ", to_case_label(case_label, unsigned_case)));
statement("if (", merge(conditions, " || "), ")");
begin_scope();
flush_phi(block.self, target_block);
end_scope();
}
// Mark the block so that we don't flush Phi from header to case label.
get<SPIRBlock>(target_block).ignore_phi_from_block = block.self;
}
}
emit_block_hints(block);
statement("switch (", to_expression(block.condition), ")");
begin_scope();
// Multiple case labels can branch to same block, so find all unique blocks.
bool emitted_default = false;
unordered_set<uint32_t> emitted_blocks;
for (auto &c : block.cases)
for (size_t i = 0; i < num_blocks; i++)
{
if (emitted_blocks.count(c.block) != 0)
continue;
uint32_t target_block = block_declaration_order[i];
auto &literals = case_constructs[target_block];
// Emit all case labels which branch to our target.
// FIXME: O(n^2), revisit if we hit shaders with 100++ case labels ...
for (auto &other_case : block.cases)
if (literals.empty())
{
if (other_case.block == c.block)
// Default case.
statement("default:");
}
else
{
for (auto &case_literal : literals)
{
// The case label value must be sign-extended properly in SPIR-V, so we can assume 32-bit values here.
auto case_value = unsigned_case ? convert_to_string(uint32_t(other_case.value)) :
convert_to_string(int32_t(other_case.value));
statement("case ", case_value, label_suffix, ":");
statement("case ", to_case_label(case_literal, unsigned_case), label_suffix, ":");
}
}
// Maybe we share with default block?
if (block.default_block == c.block)
auto &case_block = get<SPIRBlock>(target_block);
if (i + 1 < num_blocks && execution_is_direct_branch(case_block, get<SPIRBlock>(block_declaration_order[i + 1])))
{
statement("default:");
emitted_default = true;
// We will fall through here, so just terminate the block chain early.
// We still need to deal with Phi potentially.
// No need for a stack-like thing here since we only do fall-through when there is a
// single trivial branch to fall-through target..
current_emitting_switch_fallthrough = true;
}
// Complete the target.
emitted_blocks.insert(c.block);
else
current_emitting_switch_fallthrough = false;
begin_scope();
branch(block.self, c.block);
branch(block.self, target_block);
end_scope();
current_emitting_switch_fallthrough = false;
}
if (!emitted_default)
// Might still have to flush phi variables if we branch from loop header directly to merge target.
if (flush_phi_required(block.self, block.next_block))
{
if (block.default_block != block.next_block)
if (block.default_block == block.next_block || !literals_to_merge.empty())
{
statement("default:");
begin_scope();
if (is_break(block.default_block))
SPIRV_CROSS_THROW("Cannot break; out of a switch statement and out of a loop at the same time ...");
branch(block.self, block.default_block);
end_scope();
}
else if (flush_phi_required(block.self, block.next_block))
{
statement("default:");
for (auto &case_literal : literals_to_merge)
statement("case ", to_case_label(case_literal, unsigned_case), label_suffix, ":");
if (block.default_block == block.next_block)
statement("default:");
begin_scope();
flush_phi(block.self, block.next_block);
statement("break;");
@ -11644,22 +11768,26 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block)
if (block.merge != SPIRBlock::MergeSelection)
flush_phi(block.self, block.next_block);
// For merge selects we might have ignored the fact that a merge target
// could have been a break; or continue;
// We will need to deal with it here.
if (is_loop_break(block.next_block))
// For switch fallthrough cases, we terminate the chain here, but we still need to handle Phi.
if (!current_emitting_switch_fallthrough)
{
// Cannot check for just break, because switch statements will also use break.
assert(block.merge == SPIRBlock::MergeSelection);
statement("break;");
// For merge selects we might have ignored the fact that a merge target
// could have been a break; or continue;
// We will need to deal with it here.
if (is_loop_break(block.next_block))
{
// Cannot check for just break, because switch statements will also use break.
assert(block.merge == SPIRBlock::MergeSelection);
statement("break;");
}
else if (is_continue(block.next_block))
{
assert(block.merge == SPIRBlock::MergeSelection);
branch_to_continue(block.self, block.next_block);
}
else if (block.self != block.next_block)
emit_block_chain(get<SPIRBlock>(block.next_block));
}
else if (is_continue(block.next_block))
{
assert(block.merge == SPIRBlock::MergeSelection);
branch_to_continue(block.self, block.next_block);
}
else if (block.self != block.next_block)
emit_block_chain(get<SPIRBlock>(block.next_block));
}
if (block.merge == SPIRBlock::MergeLoop)

View File

@ -223,6 +223,7 @@ protected:
SPIRBlock *current_emitting_block = nullptr;
SPIRBlock *current_emitting_switch = nullptr;
bool current_emitting_switch_fallthrough = false;
virtual void emit_instruction(const Instruction &instr);
void emit_block_instructions(SPIRBlock &block);