shader: build CFG labels and targets from ordered storage

This commit is contained in:
nmzik
2026-10-09 01:55:50 +02:00
parent 46997dfc7a
commit 7c6121cba5
@@ -8,7 +8,6 @@
#include <deque> #include <deque>
#include <list> #include <list>
#include <map> #include <map>
#include <set>
#include <span> #include <span>
namespace Libs::Graphics::ShaderRecompiler::CFG { namespace Libs::Graphics::ShaderRecompiler::CFG {
@@ -568,9 +567,8 @@ bool ResolveSetpcTargets(const Decoder::Program& program, uint32_t setpc_index,
return false; return false;
} }
bool IsValidTarget(uint32_t target, const std::set<uint32_t>& instruction_pcs, uint32_t first_pc, bool IsValidTarget(uint32_t target, std::span<const Instruction> instructions, uint32_t end_pc) {
uint32_t end_pc) { return target == end_pc || std::ranges::binary_search(instructions, target, {}, &Instruction::pc);
return target == end_pc || (target >= first_pc && instruction_pcs.contains(target));
} }
std::vector<uint32_t> AllBlockIds(uint32_t count) { std::vector<uint32_t> AllBlockIds(uint32_t count) {
@@ -1455,9 +1453,7 @@ Graph BuildGraph(const Decoder::Program& program) {
const auto first_pc = program.instructions.front().pc; const auto first_pc = program.instructions.front().pc;
const auto end_pc = ProgramEndPc(program); const auto end_pc = ProgramEndPc(program);
std::set<uint32_t> instruction_pcs;
for (const auto& inst: program.instructions) { for (const auto& inst: program.instructions) {
instruction_pcs.insert(inst.pc);
if (inst.opcode == Opcode::UNSUPPORTED) { if (inst.opcode == Opcode::UNSUPPORTED) {
ExitBuildFailure( ExitBuildFailure(
graph, FailureKind::UnsupportedInstruction, UINT32_MAX, graph, FailureKind::UnsupportedInstruction, UINT32_MAX,
@@ -1466,23 +1462,21 @@ Graph BuildGraph(const Decoder::Program& program) {
} }
} }
std::set<uint32_t> labels; std::vector<uint32_t> labels {first_pc, end_pc};
labels.insert(first_pc);
labels.insert(end_pc);
std::map<uint32_t, SetpcTargetInfo> setpc_targets; std::map<uint32_t, SetpcTargetInfo> setpc_targets;
for (uint32_t i = 0; i < program.instructions.size(); i++) { for (uint32_t i = 0; i < program.instructions.size(); i++) {
const auto& inst = program.instructions[i]; const auto& inst = program.instructions[i];
const auto next_pc = InstructionEndPc(inst); const auto next_pc = InstructionEndPc(inst);
if (Decoder::IsDirectBranch(inst.opcode)) { if (Decoder::IsDirectBranch(inst.opcode)) {
if (!IsValidTarget(inst.branch_target, instruction_pcs, first_pc, end_pc)) { if (!IsValidTarget(inst.branch_target, program.instructions, end_pc)) {
ExitBuildFailure(graph, FailureKind::InvalidBranchTarget, UINT32_MAX, ExitBuildFailure(graph, FailureKind::InvalidBranchTarget, UINT32_MAX,
fmt::format("branch at pc 0x{:08x} targets invalid pc 0x{:08x}", fmt::format("branch at pc 0x{:08x} targets invalid pc 0x{:08x}",
inst.pc, inst.branch_target)); inst.pc, inst.branch_target));
} }
labels.insert(inst.branch_target); labels.push_back(inst.branch_target);
if (next_pc <= end_pc) { if (next_pc <= end_pc) {
labels.insert(next_pc); labels.push_back(next_pc);
} }
} else if (inst.opcode == Opcode::S_SETPC_B64) { } else if (inst.opcode == Opcode::S_SETPC_B64) {
SetpcTargetInfo target_info; SetpcTargetInfo target_info;
@@ -1495,30 +1489,32 @@ Graph BuildGraph(const Decoder::Program& program) {
? std::span<const uint32_t>(target_info.target_pcs) ? std::span<const uint32_t>(target_info.target_pcs)
: std::span<const uint32_t>(&target_info.target, 1); : std::span<const uint32_t>(&target_info.target, 1);
for (const auto target: target_pcs) { for (const auto target: target_pcs) {
if (!IsValidTarget(target, instruction_pcs, first_pc, end_pc)) { if (!IsValidTarget(target, program.instructions, end_pc)) {
ExitBuildFailure( ExitBuildFailure(
graph, FailureKind::InvalidBranchTarget, UINT32_MAX, graph, FailureKind::InvalidBranchTarget, UINT32_MAX,
fmt::format("S_SETPC_B64 at pc 0x{:08x} targets invalid pc 0x{:08x}", fmt::format("S_SETPC_B64 at pc 0x{:08x} targets invalid pc 0x{:08x}",
inst.pc, target)); inst.pc, target));
} }
labels.insert(target); labels.push_back(target);
} }
setpc_targets.emplace(inst.pc, std::move(target_info)); setpc_targets.emplace(inst.pc, std::move(target_info));
if (next_pc <= end_pc) { if (next_pc <= end_pc) {
labels.insert(next_pc); labels.push_back(next_pc);
} }
} else if (inst.opcode == Opcode::S_ENDPGM) { } else if (inst.opcode == Opcode::S_ENDPGM) {
labels.insert(next_pc); labels.push_back(next_pc);
} }
} }
std::vector<uint32_t> sorted_labels(labels.begin(), labels.end()); SortUnique(labels);
for (uint32_t i = 0; i < sorted_labels.size(); i++) { graph.blocks.reserve(labels.size());
const auto start = sorted_labels[i]; for (uint32_t i = 0; i < labels.size(); i++) {
const auto start = labels[i];
if (start > end_pc) { if (start > end_pc) {
continue; continue;
} }
if (start != end_pc && !instruction_pcs.contains(start)) { const auto begin = std::ranges::lower_bound(program.instructions, start, {}, &Instruction::pc);
if (start != end_pc && (begin == program.instructions.end() || begin->pc != start)) {
ExitBuildFailure( ExitBuildFailure(
graph, FailureKind::InvalidLabel, UINT32_MAX, graph, FailureKind::InvalidLabel, UINT32_MAX,
fmt::format("CFG label does not start on an instruction: 0x{:08x}", start)); fmt::format("CFG label does not start on an instruction: 0x{:08x}", start));
@@ -1527,11 +1523,8 @@ Graph BuildGraph(const Decoder::Program& program) {
BasicBlock block; BasicBlock block;
block.id = static_cast<uint32_t>(graph.blocks.size()); block.id = static_cast<uint32_t>(graph.blocks.size());
block.start_pc = start; block.start_pc = start;
block.end_pc = i + 1u < sorted_labels.size() ? sorted_labels[i + 1u] : end_pc; block.end_pc = i + 1u < labels.size() ? labels[i + 1u] : end_pc;
block.inst_begin = static_cast<uint32_t>( block.inst_begin = static_cast<uint32_t>(begin - program.instructions.begin());
std::lower_bound(program.instructions.begin(), program.instructions.end(), start,
[](const Instruction& inst, uint32_t pc) { return inst.pc < pc; }) -
program.instructions.begin());
block.inst_end = static_cast<uint32_t>( block.inst_end = static_cast<uint32_t>(
std::lower_bound(program.instructions.begin(), program.instructions.end(), block.end_pc, std::lower_bound(program.instructions.begin(), program.instructions.end(), block.end_pc,
[](const Instruction& inst, uint32_t pc) { return inst.pc < pc; }) - [](const Instruction& inst, uint32_t pc) { return inst.pc < pc; }) -
@@ -1541,10 +1534,10 @@ Graph BuildGraph(const Decoder::Program& program) {
graph.entry_block = 0; graph.entry_block = 0;
std::map<uint32_t, uint32_t> pc_to_block; const auto block_at_pc = [&](uint32_t pc) {
for (const auto& block: graph.blocks) { const auto found = std::ranges::lower_bound(graph.blocks, pc, {}, &BasicBlock::start_pc);
pc_to_block.emplace(block.start_pc, block.id); return found != graph.blocks.end() && found->start_pc == pc ? found->id : UINT32_MAX;
} };
for (auto& block: graph.blocks) { for (auto& block: graph.blocks) {
block.terminator = {}; block.terminator = {};
@@ -1566,7 +1559,7 @@ Graph BuildGraph(const Decoder::Program& program) {
block.terminator.indirect_selector_code = target_info.selector_code; block.terminator.indirect_selector_code = target_info.selector_code;
for (const auto target_pc: target_info.target_pcs) { for (const auto target_pc: target_info.target_pcs) {
block.terminator.indirect_target_pcs.push_back(target_pc); block.terminator.indirect_target_pcs.push_back(target_pc);
block.terminator.indirect_targets.push_back(pc_to_block.at(target_pc)); block.terminator.indirect_targets.push_back(block_at_pc(target_pc));
} }
const auto selector_count = std::min(target_info.selector_values.size(), const auto selector_count = std::min(target_info.selector_values.size(),
target_info.selector_target_pcs.size()); target_info.selector_target_pcs.size());
@@ -1574,35 +1567,35 @@ Graph BuildGraph(const Decoder::Program& program) {
block.terminator.indirect_selector_values.push_back( block.terminator.indirect_selector_values.push_back(
target_info.selector_values[i]); target_info.selector_values[i]);
block.terminator.indirect_selector_targets.push_back( block.terminator.indirect_selector_targets.push_back(
pc_to_block.at(target_info.selector_target_pcs[i])); block_at_pc(target_info.selector_target_pcs[i]));
} }
} else { } else {
block.terminator.kind = TerminatorKind::Branch; block.terminator.kind = TerminatorKind::Branch;
block.terminator.condition = BranchCondition::Always; block.terminator.condition = BranchCondition::Always;
block.terminator.true_block = pc_to_block.at(target_info.target); block.terminator.true_block = block_at_pc(target_info.target);
} }
} else if (last.opcode == Opcode::S_BRANCH) { } else if (last.opcode == Opcode::S_BRANCH) {
block.terminator.kind = TerminatorKind::Branch; block.terminator.kind = TerminatorKind::Branch;
block.terminator.condition = BranchCondition::Always; block.terminator.condition = BranchCondition::Always;
block.terminator.true_block = pc_to_block.at(last.branch_target); block.terminator.true_block = block_at_pc(last.branch_target);
} else if (Decoder::IsConditionalBranch(last.opcode)) { } else if (Decoder::IsConditionalBranch(last.opcode)) {
block.terminator.kind = TerminatorKind::ConditionalBranch; block.terminator.kind = TerminatorKind::ConditionalBranch;
block.terminator.condition = ConditionForOpcode(last.opcode); block.terminator.condition = ConditionForOpcode(last.opcode);
block.terminator.true_block = pc_to_block.at(last.branch_target); block.terminator.true_block = block_at_pc(last.branch_target);
const auto fallthrough = pc_to_block.find(next_pc); const auto fallthrough = block_at_pc(next_pc);
if (fallthrough == pc_to_block.end()) { if (fallthrough == UINT32_MAX) {
ExitBuildFailure( ExitBuildFailure(
graph, FailureKind::MissingFallthrough, block.id, graph, FailureKind::MissingFallthrough, block.id,
fmt::format("conditional branch at pc 0x{:08x} has no fallthrough block", fmt::format("conditional branch at pc 0x{:08x} has no fallthrough block",
last.pc)); last.pc));
} }
block.terminator.false_block = fallthrough->second; block.terminator.false_block = fallthrough;
} else { } else {
auto next = pc_to_block.find(block.end_pc); const auto next = block_at_pc(block.end_pc);
if (next != pc_to_block.end() && block.end_pc != block.start_pc) { if (next != UINT32_MAX && block.end_pc != block.start_pc) {
block.terminator.kind = TerminatorKind::Branch; block.terminator.kind = TerminatorKind::Branch;
block.terminator.condition = BranchCondition::Always; block.terminator.condition = BranchCondition::Always;
block.terminator.true_block = next->second; block.terminator.true_block = next;
} else { } else {
block.terminator.kind = TerminatorKind::Return; block.terminator.kind = TerminatorKind::Return;
} }