#include "ControlFlow/GraphBuilder.hpp" #include "ControlFlow/RequestSerializer.hpp" #include "ControlFlow/Structurizer.hpp" #include "RdnaDecoder/RdnaInstructionDecoder.hpp" #include "Recompiler.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { using namespace ShaderRecompiler; enum class Split { Clone, Route }; struct Program { std::string_view name; std::string_view source; std::vector code; Split split; std::size_t clonedLimit = 0; std::vector reference = {}; }; using RouteState = std::map; void require(bool condition, const std::string& message) { if (!condition) throw std::runtime_error(message); } std::vector constructBlocks(const ControlFlowGraph& graph, std::uint32_t header, std::uint32_t exclude) { std::vector blocks; for (const auto& block : graph.blocks) { if (graph.Dominates(header, block.id) && (exclude == InvalidControlFlowId || !graph.Dominates(exclude, block.id))) { blocks.push_back(block.id); } } return blocks; } void verifyStructured(const std::string& prefix, const ControlFlowGraph& graph) { std::map mergeOwners; std::vector> loopExits; for (const auto& block : graph.blocks) { const auto& terminator = block.terminator; if (terminator.mergeBlock == InvalidControlFlowId) continue; require(mergeOwners.emplace(terminator.mergeBlock, block.id).second, prefix + "block " + std::to_string(terminator.mergeBlock) + " merges two constructs"); if (terminator.loopHeader) loopExits.emplace_back(terminator.mergeBlock, terminator.continueBlock); } for (const auto& block : graph.blocks) { const auto& terminator = block.terminator; if (terminator.loopHeader || terminator.mergeBlock == InvalidControlFlowId) continue; for (const auto member : constructBlocks(graph, block.id, terminator.mergeBlock)) { for (const auto successor : graph.FindBlock(member).successors) { if (successor == terminator.mergeBlock || (graph.Dominates(block.id, successor) && !graph.Dominates(terminator.mergeBlock, successor))) continue; const bool loopExit = std::any_of(loopExits.begin(), loopExits.end(), [&](const auto& exits) { return successor == exits.first || successor == exits.second; }); require(loopExit, prefix + "block " + std::to_string(member) + " of the selection at block " + std::to_string(block.id) + " branches to block " + std::to_string(successor) + " outside the construct"); } } } } std::uint32_t followEmptyBlocks(const std::string& prefix, const ControlFlowGraph& graph, std::uint32_t blockId, RouteState& routes) { for (std::size_t steps = 0; steps <= graph.blocks.size(); ++steps) { const auto& block = graph.FindBlock(blockId); const auto& terminator = block.terminator; const bool empty = block.instructionBegin == block.instructionEnd; require(terminator.gotoValue < 0 || empty, prefix + "block " + std::to_string(block.id) + " sets a route variable after guest instructions"); if (terminator.gotoValue >= 0) routes[terminator.gotoVariable] = terminator.gotoValue != 0; if (!empty) return blockId; if (terminator.kind == TerminatorKind::Branch) { blockId = terminator.trueBlock; } else if (terminator.kind == TerminatorKind::ConditionalBranch && terminator.condition == BranchCondition::GotoVariable) { const auto route = routes.find(terminator.gotoVariable); require(route != routes.end(), prefix + "block " + std::to_string(block.id) + " reads route variable " + std::to_string(terminator.gotoVariable) + " before any path sets it"); blockId = route->second ? terminator.trueBlock : terminator.falseBlock; } else { return blockId; } } throw std::runtime_error(prefix + "a cycle of empty blocks"); } void verifySameExecutions(const std::string& prefix, const ControlFlowGraph& original, const ControlFlowGraph& structured) { std::vector> pending{{structured.blocks.front().id, original.entryBlock, {}}}; std::vector> visited; RouteState none; while (!pending.empty()) { auto [structuredId, originalId, routes] = pending.back(); pending.pop_back(); const auto copyId = followEmptyBlocks(prefix, structured, structuredId, routes); const auto sourceId = followEmptyBlocks(prefix, original, originalId, none); auto state = std::make_tuple(copyId, sourceId, routes); if (std::find(visited.begin(), visited.end(), state) != visited.end()) continue; visited.push_back(state); const auto& copy = structured.FindBlock(copyId); const auto& source = original.FindBlock(sourceId); require(copy.instructionBegin == source.instructionBegin && copy.instructionEnd == source.instructionEnd, prefix + "block " + std::to_string(copy.id) + " runs other instructions than block " + std::to_string(source.id)); require(copy.terminator.kind == source.terminator.kind && copy.terminator.condition == source.terminator.condition, prefix + "block " + std::to_string(copy.id) + " branches differently from block " + std::to_string(source.id)); if (source.terminator.kind == TerminatorKind::Branch || source.terminator.kind == TerminatorKind::ConditionalBranch) pending.emplace_back(copy.terminator.trueBlock, source.terminator.trueBlock, routes); if (source.terminator.kind == TerminatorKind::ConditionalBranch) pending.emplace_back(copy.terminator.falseBlock, source.terminator.falseBlock, routes); } } std::size_t clonedInstructions(const ControlFlowGraph& graph) { std::map, std::size_t> copies; std::size_t cloned = 0; for (const auto& block : graph.blocks) { if (block.instructionBegin != block.instructionEnd && copies[{block.instructionBegin, block.instructionEnd}]++ != 0) cloned += block.instructionEnd - block.instructionBegin; } return cloned; } std::size_t routeVariables(const ControlFlowGraph& graph) { std::vector variables; for (const auto& block : graph.blocks) { if (block.terminator.condition == BranchCondition::GotoVariable && std::find(variables.begin(), variables.end(), block.terminator.gotoVariable) == variables.end()) variables.push_back(block.terminator.gotoVariable); } return variables.size(); } struct Structured { std::size_t addedBlocks; std::size_t clonedInstructions; std::size_t routeVariables; }; Structured verifyGraph(const std::string& name, std::span code) { const std::string prefix = name + ": "; const auto decoded = RdnaInstructionDecoder{}.Decode(code); const auto original = GraphBuilder{}.Build(decoded); auto graph = original; Structurizer{}.Structurize(graph); verifyStructured(prefix, graph); verifySameExecutions(prefix, original, graph); return {graph.blocks.size() - original.blocks.size(), clonedInstructions(graph), routeVariables(graph)}; } std::vector Store(std::vector code) { code.insert(code.end(), {0xe0700000u, 0x80000100u, 0xbf810000u}); return code; } void verifyRequest(const char* path) { std::ifstream file(path, std::ios::binary); std::ostringstream text; text << file.rdbuf(); const auto request = RequestSerializer{}.Deserialize(text.str()); const auto result = verifyGraph(path, request.request.shader.code); std::printf("%s: structured with %zu added blocks, %zu cloned instructions, %zu route variables\n", path, result.addedBlocks, result.clonedInstructions, result.routeVariables); } std::size_t recompile(const std::string& name, std::span code, std::uint32_t subgroupSize) { const std::array userData{0x10000000u, 0x00100000u, 0x40u, 0x00027facu}; const std::array capabilities{1u, 61u}; const std::array extensions{"SPV_KHR_storage_buffer_storage_class"}; RecompileRequest request{}; request.shader = {ShaderStage::Compute, 0x20000u, code, 0, {}}; request.context.waveSize = 64; request.context.userDataBaseRegister = 0; request.context.userData = userData; request.context.compute = ShaderComputeStageInfo{{64u, 1u, 1u}, 0u, {false, false, false}, false, 1u}; request.target.vulkanVersion = 0x00403000u; request.target.spirvVersion = 0x00010600u; request.target.subgroupSize = subgroupSize; request.target.bdaAbiVersion = 1; request.target.supportedCapabilities = capabilities; request.target.supportedExtensions = extensions; request.target.maxWorkgroupSize = {1024u, 1024u, 64u}; request.target.maxWorkgroupInvocations = 1024; request.target.maxWorkgroupSharedMemoryBytes = 49152; request.layout = {0, 0, 0, 128}; request.useCache = false; const auto result = Recompile(request); require(!result.spirv.empty(), name + ": no SPIR-V for a " + std::to_string(subgroupSize) + "-lane subgroup"); return result.spirv.size(); } void verifyProgram(const Program& program) { const std::string name(program.name); const auto result = verifyGraph(name, program.code); if (program.split == Split::Clone) { require(result.clonedInstructions != 0 && result.routeVariables == 0, name + ": expected a clone, got " + std::to_string(result.clonedInstructions) + " cloned instructions and " + std::to_string(result.routeVariables) + " route variables"); } else { require(result.clonedInstructions <= program.clonedLimit && result.routeVariables != 0, name + ": expected routing with at most " + std::to_string(program.clonedLimit) + " cloned instructions, got " + std::to_string(result.clonedInstructions) + " cloned instructions and " + std::to_string(result.routeVariables) + " route variables"); } const auto words = recompile(name, program.code, 64u); static_cast(recompile(name, program.code, 32u)); if (!program.reference.empty()) { const auto reference = recompile(name, program.reference, 64u); require(words * 10u <= reference * 11u, name + ": " + std::to_string(words) + " SPIR-V words, more than 10% over the " + std::to_string(reference) + " of the program without the entering branch"); } } } int main(int argc, char** argv) { if (argc > 1) { int failures = 0; for (int i = 1; i < argc; ++i) { try { verifyRequest(argv[i]); } catch (const std::exception& error) { std::fprintf(stderr, "%s: %s\n", argv[i], error.what()); ++failures; } } return failures == 0 ? 0 : 1; } const std::vector programs{ {"shared tail", R"( v_cmp_gt_u32 vcc, 16, v0 s_cbranch_vccz outer_else v_cmp_gt_u32 vcc, 8, v0 s_cbranch_vccnz tail v_add_nc_u32 v1, 1, v0 s_branch done outer_else: v_add_nc_u32 v1, 2, v0 tail: v_add_nc_u32 v1, 3, v1 done: buffer_store_dword v1, off, s[0:3], 0 s_endpgm)", Store({0x7d880090u, 0xbf860004u, 0x7d880088u, 0xbf870003u, 0x4a020081u, 0xbf820002u, 0x4a020082u, 0x4a020283u}), Split::Clone}, {"shared tail with a selection", R"( v_cmp_gt_u32 vcc, 16, v0 s_cbranch_vccz outer_else v_cmp_gt_u32 vcc, 8, v0 s_cbranch_vccnz tail v_add_nc_u32 v1, 1, v0 s_branch done outer_else: v_add_nc_u32 v1, 2, v0 tail: v_cmp_gt_u32 vcc, 4, v0 s_cbranch_vccz tail_join v_add_nc_u32 v1, 5, v1 tail_join: v_add_nc_u32 v1, 3, v1 done: buffer_store_dword v1, off, s[0:3], 0 s_endpgm)", Store({0x7d880090u, 0xbf860004u, 0x7d880088u, 0xbf870003u, 0x4a020081u, 0xbf820005u, 0x4a020082u, 0x7d880084u, 0xbf860001u, 0x4a020285u, 0x4a020283u}), Split::Clone}, {"shared tail in a loop", R"( s_mov_b32 s8, 0 v_mov_b32 v1, 0 loop: v_cmp_gt_u32 vcc, s8, v0 s_cbranch_vccz outer_else v_cmp_gt_u32 vcc, 8, v0 s_cbranch_vccnz tail v_add_nc_u32 v1, 1, v1 s_branch latch outer_else: v_add_nc_u32 v1, 2, v1 tail: v_add_nc_u32 v1, 3, v1 latch: s_add_u32 s8, s8, 16 s_cmp_lt_u32 s8, 64 s_cbranch_scc1 loop buffer_store_dword v1, off, s[0:3], 0 s_endpgm)", Store({0xbe880380u, 0x7e020280u, 0x7d880008u, 0xbf860004u, 0x7d880088u, 0xbf870003u, 0x4a020281u, 0xbf820002u, 0x4a020282u, 0x4a020283u, 0x80089008u, 0xbf0ac008u, 0xbf85fff5u}), Split::Clone}, {"shared early exit", R"( v_mov_b32 v1, 0 v_cmp_gt_u32 vcc, 16, v0 s_and_saveexec_b64 s[8:9], vcc s_cbranch_execz join v_cmp_gt_u32 vcc, 4, v0 s_cbranch_vccz early_exit v_add_nc_u32 v1, 1, v0 join: s_mov_b64 exec, s[8:9] v_cmp_gt_u32 vcc, 32, v0 s_cbranch_vccz early_exit buffer_store_dword v1, off, s[0:3], 0 s_endpgm early_exit: s_mov_b64 exec, 0 s_endpgm)", {0x7e020280u, 0x7d880090u, 0xbe88246au, 0xbf880003u, 0x7d880084u, 0xbf860007u, 0x4a020081u, 0xbefe0408u, 0x7d8800a0u, 0xbf860003u, 0xe0700000u, 0x80000100u, 0xbf810000u, 0xbefe0480u, 0xbf810000u}, Split::Clone}, {"short-circuit condition", R"( v_cmp_gt_u32 vcc, 16, v0 s_cbranch_vccz body v_cmp_gt_u32 vcc, 8, v0 s_cbranch_vccnz done body: v_add_nc_u32 v1, 1, v0 v_cmp_gt_u32 vcc, 4, v0 s_cbranch_vccz done v_add_nc_u32 v1, 2, v1 done: buffer_store_dword v1, off, s[0:3], 0 s_endpgm)", Store({0x7d880090u, 0xbf860002u, 0x7d880088u, 0xbf870004u, 0x4a020081u, 0x7d880084u, 0xbf860001u, 0x4a020282u}), Split::Clone}, {"entered region with an expensive body", R"( v_mov_b32 v1, 0 v_cmp_gt_u32 vcc, 16, v0 s_and_saveexec_b64 s[8:9], vcc s_cbranch_execz body v_add_nc_u32 v1, 1, v0 v_cmp_gt_u32 vcc, 8, v1 s_cbranch_vccnz done body: s_mov_b32 s10, 0 loop: buffer_load_dword v2, off, s[0:3], 0 v_mul_f32 v3, v2, v1 v_add_f32 v3, v3, v2 v_xor_b32 v1, v3, v1 v_readfirstlane_b32 s11, v1 v_add_nc_u32 v1, s11, v1 buffer_load_dword v2, off, s[0:3], 0 v_mul_f32 v3, v2, v1 v_add_f32 v3, v3, v2 v_xor_b32 v1, v3, v1 v_readfirstlane_b32 s11, v1 v_add_nc_u32 v1, s11, v1 s_add_u32 s10, s10, 1 s_cmp_lt_u32 s10, 4 s_cbranch_scc1 loop s_mov_b64 exec, s[8:9] buffer_store_dword v1, off, s[0:3], 0 done: s_endpgm)", {0x7e020280u, 0x7d880090u, 0xbe88246au, 0xbf880003u, 0x4a020081u, 0x7d880288u, 0xbf870015u, 0xbe8a0380u, 0xe0300000u, 0x80000200u, 0x10060302u, 0x06060503u, 0x3a020303u, 0x7e160501u, 0x4a02020bu, 0xe0300000u, 0x80000200u, 0x10060302u, 0x06060503u, 0x3a020303u, 0x7e160501u, 0x4a02020bu, 0x800a810au, 0xbf0a840au, 0xbf85ffefu, 0xbefe0408u, 0xe0700000u, 0x80000100u, 0xbf810000u}, Split::Route, 0, {0x7e020280u, 0x7d880090u, 0xbe88246au, 0xbf800000u, 0x4a020081u, 0x7d880288u, 0xbf870015u, 0xbe8a0380u, 0xe0300000u, 0x80000200u, 0x10060302u, 0x06060503u, 0x3a020303u, 0x7e160501u, 0x4a02020bu, 0xe0300000u, 0x80000200u, 0x10060302u, 0x06060503u, 0x3a020303u, 0x7e160501u, 0x4a02020bu, 0x800a810au, 0xbf0a840au, 0xbf85ffefu, 0xbefe0408u, 0xe0700000u, 0x80000100u, 0xbf810000u}}, {"return tail past an entered region", R"( v_mov_b32 v1, 0 v_cmp_gt_u32 vcc, 16, v0 s_and_saveexec_b64 s[8:9], vcc s_cbranch_execz body v_cmp_gt_u32 vcc, 8, v0 s_cbranch_vccnz done v_add_nc_u32 v1, 1, v0 s_mov_b64 exec, s[8:9] body: s_mov_b32 s10, 0 loop: buffer_load_dword v2, off, s[0:3], 0 v_mul_f32 v3, v2, v1 v_add_f32 v3, v3, v2 v_xor_b32 v1, v3, v1 v_readfirstlane_b32 s11, v1 v_add_nc_u32 v1, s11, v1 buffer_load_dword v2, off, s[0:3], 0 v_mul_f32 v3, v2, v1 v_add_f32 v3, v3, v2 v_xor_b32 v1, v3, v1 v_readfirstlane_b32 s11, v1 v_add_nc_u32 v1, s11, v1 s_add_u32 s10, s10, 1 s_cmp_lt_u32 s10, 4 s_cbranch_scc1 loop buffer_store_dword v1, off, s[0:3], 0 done: s_endpgm)", {0x7e020280u, 0x7d880090u, 0xbe88246au, 0xbf880004u, 0x7d880088u, 0xbf870016u, 0x4a020081u, 0xbefe0408u, 0xbe8a0380u, 0xe0300000u, 0x80000200u, 0x10060302u, 0x06060503u, 0x3a020303u, 0x7e160501u, 0x4a02020bu, 0xe0300000u, 0x80000200u, 0x10060302u, 0x06060503u, 0x3a020303u, 0x7e160501u, 0x4a02020bu, 0x800a810au, 0xbf0a840au, 0xbf85ffefu, 0xe0700000u, 0x80000100u, 0xbf810000u}, Split::Clone}, {"entered region in a loop", R"( s_mov_b32 s12, 0 v_mov_b32 v1, 0 outer: v_cmp_gt_u32 vcc, s12, v0 s_cbranch_vccz body v_cmp_gt_u32 vcc, 8, v0 s_cbranch_vccnz join v_add_nc_u32 v1, 1, v1 body: buffer_load_dword v2, off, s[0:3], 0 v_mul_f32 v3, v2, v1 v_add_f32 v3, v3, v2 v_xor_b32 v1, v3, v1 v_readfirstlane_b32 s11, v1 v_add_nc_u32 v1, s11, v1 buffer_load_dword v2, off, s[0:3], 0 v_mul_f32 v3, v2, v1 v_add_f32 v3, v3, v2 v_xor_b32 v1, v3, v1 v_readfirstlane_b32 s11, v1 v_add_nc_u32 v1, s11, v1 join: v_add_nc_u32 v1, 2, v1 s_add_u32 s12, s12, 16 s_cmp_lt_u32 s12, 64 s_cbranch_scc0 exit_loop s_branch outer exit_loop: buffer_store_dword v1, off, s[0:3], 0 s_endpgm)", {0xbe8c0380u, 0x7e020280u, 0x7d88000cu, 0xbf860003u, 0x7d880088u, 0xbf87000fu, 0x4a020281u, 0xe0300000u, 0x80000200u, 0x10060302u, 0x06060503u, 0x3a020303u, 0x7e160501u, 0x4a02020bu, 0xe0300000u, 0x80000200u, 0x10060302u, 0x06060503u, 0x3a020303u, 0x7e160501u, 0x4a02020bu, 0x4a020282u, 0x800c900cu, 0xbf0ac00cu, 0xbf840001u, 0xbf82ffe8u, 0xe0700000u, 0x80000100u, 0xbf810000u}, Split::Route}, {"nested entered regions", R"( v_mov_b32 v1, 0 v_cmp_gt_u32 vcc, 16, v0 s_and_saveexec_b64 s[8:9], vcc s_cbranch_execz outer_body v_add_nc_u32 v1, 1, v0 v_cmp_gt_u32 vcc, 8, v1 s_cbranch_vccnz done outer_body: buffer_load_dword v2, off, s[0:3], 0 v_mul_f32 v3, v2, v1 v_add_f32 v3, v3, v2 v_xor_b32 v1, v3, v1 v_readfirstlane_b32 s11, v1 v_add_nc_u32 v1, s11, v1 s_cmp_eq_u32 s11, 64 s_cbranch_scc0 inner_body v_add_nc_u32 v1, 2, v1 v_cmp_gt_u32 vcc, 4, v1 s_cbranch_vccz done inner_body: s_mov_b32 s10, 0 inner_loop: buffer_load_dword v2, off, s[0:3], 0 v_mul_f32 v3, v2, v1 v_add_f32 v3, v3, v2 v_xor_b32 v1, v3, v1 v_readfirstlane_b32 s11, v1 v_add_nc_u32 v1, s11, v1 s_add_u32 s10, s10, 1 s_cmp_lt_u32 s10, 4 s_cbranch_scc1 inner_loop s_mov_b64 exec, s[8:9] buffer_store_dword v1, off, s[0:3], 0 done: s_endpgm)", {0x7e020280u, 0x7d880090u, 0xbe88246au, 0xbf880003u, 0x4a020081u, 0x7d880288u, 0xbf87001au, 0xe0300000u, 0x80000200u, 0x10060302u, 0x06060503u, 0x3a020303u, 0x7e160501u, 0x4a02020bu, 0xbf06c00bu, 0xbf840003u, 0x4a020282u, 0x7d880284u, 0xbf86000eu, 0xbe8a0380u, 0xe0300000u, 0x80000200u, 0x10060302u, 0x06060503u, 0x3a020303u, 0x7e160501u, 0x4a02020bu, 0x800a810au, 0xbf0a840au, 0xbf85fff6u, 0xbefe0408u, 0xe0700000u, 0x80000100u, 0xbf810000u}, Split::Route}, {"shared body after an overwritten condition", R"( v_mov_b32 v1, 0 s_cmp_eq_u32 s2, 64 s_cbranch_scc0 shared v_add_nc_u32 v1, 1, v0 s_cmp_eq_u32 s3, 0 s_cbranch_scc1 done shared: s_cbranch_scc1 store buffer_load_dword v2, off, s[0:3], 0 v_mul_f32 v3, v2, v1 v_add_f32 v3, v3, v2 v_xor_b32 v1, v3, v1 v_readfirstlane_b32 s11, v1 v_add_nc_u32 v1, s11, v1 buffer_load_dword v2, off, s[0:3], 0 v_mul_f32 v3, v2, v1 v_add_f32 v3, v3, v2 v_xor_b32 v1, v3, v1 v_readfirstlane_b32 s11, v1 v_add_nc_u32 v1, s11, v1 store: buffer_store_dword v1, off, s[0:3], 0 done: s_endpgm)", {0x7e020280u, 0xbf06c002u, 0xbf840003u, 0x4a020081u, 0xbf068003u, 0xbf850011u, 0xbf85000eu, 0xe0300000u, 0x80000200u, 0x10060302u, 0x06060503u, 0x3a020303u, 0x7e160501u, 0x4a02020bu, 0xe0300000u, 0x80000200u, 0x10060302u, 0x06060503u, 0x3a020303u, 0x7e160501u, 0x4a02020bu, 0xe0700000u, 0x80000100u, 0xbf810000u}, Split::Route}, {"entered region beside early returns", R"( v_mov_b32 v1, 0 v_cmp_gt_u32 vcc, 16, v0 s_cbranch_vccz body v_add_nc_u32 v1, 1, v0 v_cmp_gt_u32 vcc, 8, v1 s_cbranch_vccnz tail v_readfirstlane_b32 s11, v1 s_cmp_eq_u32 s11, 12 s_cbranch_scc0 done s_cmp_eq_u32 s11, 38 s_cbranch_scc1 done body: buffer_load_dword v2, off, s[0:3], 0 v_mul_f32 v3, v2, v1 v_add_f32 v3, v3, v2 v_xor_b32 v1, v3, v1 v_readfirstlane_b32 s11, v1 v_add_nc_u32 v1, s11, v1 buffer_load_dword v2, off, s[0:3], 0 v_mul_f32 v3, v2, v1 v_add_f32 v3, v3, v2 v_xor_b32 v1, v3, v1 v_readfirstlane_b32 s11, v1 v_add_nc_u32 v1, s11, v1 tail: v_add_nc_u32 v1, 1, v1 v_xor_b32 v1, v3, v1 v_add_nc_u32 v1, 2, v1 v_xor_b32 v1, v3, v1 v_add_nc_u32 v1, 3, v1 v_xor_b32 v1, v3, v1 v_add_nc_u32 v1, 4, v1 v_xor_b32 v1, v3, v1 v_add_nc_u32 v1, 5, v1 v_xor_b32 v1, v3, v1 v_add_nc_u32 v1, 6, v1 v_xor_b32 v1, v3, v1 v_add_nc_u32 v1, 7, v1 v_xor_b32 v1, v3, v1 v_add_nc_u32 v1, 8, v1 v_xor_b32 v1, v3, v1 v_add_nc_u32 v1, 9, v1 v_xor_b32 v1, v3, v1 v_add_nc_u32 v1, 10, v1 v_xor_b32 v1, v3, v1 done: buffer_store_dword v1, off, s[0:3], 0 s_endpgm)", {0x7e020280u, 0x7d880090u, 0xbf860008u, 0x4a020081u, 0x7d880288u, 0xbf870013u, 0x7e160501u, 0xbf068c0bu, 0xbf840024u, 0xbf06a60bu, 0xbf850022u, 0xe0300000u, 0x80000200u, 0x10060302u, 0x06060503u, 0x3a020303u, 0x7e160501u, 0x4a02020bu, 0xe0300000u, 0x80000200u, 0x10060302u, 0x06060503u, 0x3a020303u, 0x7e160501u, 0x4a02020bu, 0x4a020281u, 0x3a020303u, 0x4a020282u, 0x3a020303u, 0x4a020283u, 0x3a020303u, 0x4a020284u, 0x3a020303u, 0x4a020285u, 0x3a020303u, 0x4a020286u, 0x3a020303u, 0x4a020287u, 0x3a020303u, 0x4a020288u, 0x3a020303u, 0x4a020289u, 0x3a020303u, 0x4a02028au, 0x3a020303u, 0xe0700000u, 0x80000100u, 0xbf810000u}, Split::Route, 4}, {"selection arm that only returns", R"( s_cbranch_execz inner s_cbranch_scc1 done inner: v_cmp_gt_u32 vcc, 3, v1 s_cbranch_vccnz second v_mul_f32 v3, v2, v1 s_cbranch_vccz join second: s_cbranch_vccnz done join: v_add_nc_u32 v1, 2, v1 done: buffer_store_dword v1, off, s[0:3], 0 s_endpgm)", {0xbf880001u, 0xbf850006u, 0x7d880283u, 0xbf870002u, 0x10060302u, 0xbf860001u, 0xbf870001u, 0x4a020282u, 0xe0700000u, 0x80000100u, 0xbf810000u}, Split::Route, 9}, {"shared early exit beside an expensive region", R"( v_mov_b32 v1, 0 v_cmp_gt_u32 vcc, 16, v0 s_and_saveexec_b64 s[8:9], vcc s_cbranch_execz join v_cmp_gt_u32 vcc, 4, v0 s_cbranch_vccz early_exit buffer_load_dword v2, off, s[0:3], 0 v_mul_f32 v3, v2, v1 v_add_f32 v3, v3, v2 v_xor_b32 v1, v3, v1 v_readfirstlane_b32 s11, v1 v_add_nc_u32 v1, s11, v1 buffer_load_dword v2, off, s[0:3], 0 v_mul_f32 v3, v2, v1 v_add_f32 v3, v3, v2 v_xor_b32 v1, v3, v1 join: s_mov_b64 exec, s[8:9] v_cmp_gt_u32 vcc, 32, v0 s_cbranch_vccz early_exit buffer_load_dword v2, off, s[0:3], 0 v_mul_f32 v3, v2, v1 v_add_f32 v3, v3, v2 v_xor_b32 v1, v3, v1 v_readfirstlane_b32 s11, v1 v_add_nc_u32 v1, s11, v1 buffer_store_dword v1, off, s[0:3], 0 s_endpgm early_exit: s_mov_b64 exec, 0 s_endpgm)", {0x7e020280u, 0x7d880090u, 0xbe88246au, 0xbf88000eu, 0x7d880084u, 0xbf860019u, 0xe0300000u, 0x80000200u, 0x10060302u, 0x06060503u, 0x3a020303u, 0x7e160501u, 0x4a02020bu, 0xe0300000u, 0x80000200u, 0x10060302u, 0x06060503u, 0x3a020303u, 0xbefe0408u, 0x7d8800a0u, 0xbf86000au, 0xe0300000u, 0x80000200u, 0x10060302u, 0x06060503u, 0x3a020303u, 0x7e160501u, 0x4a02020bu, 0xe0700000u, 0x80000100u, 0xbf810000u, 0xbefe0480u, 0xbf810000u}, Split::Clone}, }; int failures = 0; for (const auto& program : programs) { try { verifyProgram(program); } catch (const std::exception& error) { std::fprintf(stderr, "%s\n%.*s\n", error.what(), static_cast(program.source.size()), program.source.data()); ++failures; } } if (failures != 0) return 1; std::puts("Control flow structurization tests passed"); return 0; }