Fix control flow bug where we missed continue;

Case which caused failure:

if (cond)
{
    continue;
}
break;

Only allow tracing from inner selections if the outer header never
merges execution.
This commit is contained in:
Hans-Kristian Arntzen 2022-06-07 14:58:48 +02:00
parent 50b4d5389b
commit 46e4b5a3c8
3 changed files with 70 additions and 6 deletions

View File

@ -0,0 +1,22 @@
#version 450
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(binding = 0, std430) buffer STO
{
uint data[];
} ssbo;
void main()
{
while (true)
{
ssbo.data[0]++;
if (ssbo.data[2] != 0u)
{
ssbo.data[5]++;
continue;
}
break;
}
}

View File

@ -0,0 +1,21 @@
#version 450
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout (binding = 0) buffer STO
{
uint data[];
} ssbo;
void main()
{
while(true)
{
ssbo.data[0] += 1;
if (bool(ssbo.data[2]))
{
ssbo.data[5] += 1;
continue;
}
break;
}
}

View File

@ -306,15 +306,36 @@ bool CFG::node_terminates_control_flow_in_sub_graph(BlockID from, BlockID to) co
bool true_path_ignore = false; bool true_path_ignore = false;
bool false_path_ignore = false; bool false_path_ignore = false;
if (ignore_block_id && dom.terminator == SPIRBlock::Select)
bool merges_to_nothing = dom.merge == SPIRBlock::MergeNone ||
(dom.merge == SPIRBlock::MergeSelection && dom.next_block &&
compiler.get<SPIRBlock>(dom.next_block).terminator == SPIRBlock::Unreachable) ||
(dom.merge == SPIRBlock::MergeLoop && dom.merge_block &&
compiler.get<SPIRBlock>(dom.merge_block).terminator == SPIRBlock::Unreachable);
if (dom.self == from || merges_to_nothing)
{ {
auto &true_block = compiler.get<SPIRBlock>(dom.true_block); // We can only ignore inner branchy paths if there is no merge,
auto &false_block = compiler.get<SPIRBlock>(dom.false_block); // i.e. no code is generated afterwards. E.g. this allows us to elide continue:
auto &ignore_block = compiler.get<SPIRBlock>(ignore_block_id); // for (;;) { if (cond) { continue; } else { break; } }.
true_path_ignore = compiler.execution_is_branchless(true_block, ignore_block); // Codegen here in SPIR-V will be something like either no merge if one path directly breaks, or
false_path_ignore = compiler.execution_is_branchless(false_block, ignore_block); // we merge to Unreachable.
if (ignore_block_id && dom.terminator == SPIRBlock::Select)
{
auto &true_block = compiler.get<SPIRBlock>(dom.true_block);
auto &false_block = compiler.get<SPIRBlock>(dom.false_block);
auto &ignore_block = compiler.get<SPIRBlock>(ignore_block_id);
true_path_ignore = compiler.execution_is_branchless(true_block, ignore_block);
false_path_ignore = compiler.execution_is_branchless(false_block, ignore_block);
}
} }
// Cases where we allow traversal. This serves as a proxy for post-dominance in a loop body.
// TODO: Might want to do full post-dominance analysis, but it's a lot of churn for something like this ...
// - We're the merge block of a selection construct. Jump to header.
// - We're the merge block of a loop. Jump to header.
// - Direct branch. Trivial.
// - Allow cases inside a branch if the header cannot merge execution before loop exit.
if ((dom.merge == SPIRBlock::MergeSelection && dom.next_block == to) || if ((dom.merge == SPIRBlock::MergeSelection && dom.next_block == to) ||
(dom.merge == SPIRBlock::MergeLoop && dom.merge_block == to) || (dom.merge == SPIRBlock::MergeLoop && dom.merge_block == to) ||
(dom.terminator == SPIRBlock::Direct && dom.next_block == to) || (dom.terminator == SPIRBlock::Direct && dom.next_block == to) ||