mirror of
https://github.com/KhronosGroup/SPIRV-Cross.git
synced 2024-11-15 08:20:07 +00:00
Merge pull request #1034 from KhronosGroup/fix-1033
Deal with OpSwitch case fallthrough
This commit is contained in:
commit
39f23cd058
@ -16,7 +16,6 @@ void main()
|
||||
_19 = 1.0;
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
default:
|
||||
{
|
||||
_19 = 3.0;
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ void main()
|
||||
_19 = 1.0;
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
default:
|
||||
{
|
||||
_19 = 3.0;
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
230
spirv_glsl.cpp
230
spirv_glsl.cpp
@ -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)
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user