mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 02:47:02 +02:00
shader: canonicalize CFG loop headers
This commit is contained in:
@@ -78,85 +78,6 @@ void LogDispatcherFallback(const CompileOptions& options, const CFG::Graph& cfg,
|
||||
static_cast<uint64_t>(cfg.back_edges.size()), reason.c_str());
|
||||
}
|
||||
|
||||
bool InstructionMaySplitSpirvBlock(const IR::Instruction& inst) {
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::SLoadDword:
|
||||
case IR::Opcode::SBufferLoadDword:
|
||||
case IR::Opcode::BufferLoadUbyte:
|
||||
case IR::Opcode::BufferLoadSbyte:
|
||||
case IR::Opcode::BufferLoadUshort:
|
||||
case IR::Opcode::BufferLoadSshort:
|
||||
case IR::Opcode::BufferLoadDword:
|
||||
case IR::Opcode::BufferStoreByte:
|
||||
case IR::Opcode::BufferStoreShort:
|
||||
case IR::Opcode::BufferStoreDword:
|
||||
case IR::Opcode::AtomicSwapU32:
|
||||
case IR::Opcode::AtomicAddU32:
|
||||
case IR::Opcode::AtomicSubU32:
|
||||
case IR::Opcode::AtomicSMinI32:
|
||||
case IR::Opcode::AtomicUMinU32:
|
||||
case IR::Opcode::AtomicSMaxI32:
|
||||
case IR::Opcode::AtomicUMaxU32:
|
||||
case IR::Opcode::AtomicAndU32:
|
||||
case IR::Opcode::AtomicOrU32:
|
||||
case IR::Opcode::AtomicXorU32:
|
||||
case IR::Opcode::AtomicFMinF32:
|
||||
case IR::Opcode::AtomicFMaxF32:
|
||||
case IR::Opcode::FlatLoadUbyte:
|
||||
case IR::Opcode::FlatLoadSbyte:
|
||||
case IR::Opcode::FlatLoadUshort:
|
||||
case IR::Opcode::FlatLoadSshort:
|
||||
case IR::Opcode::FlatLoadDword:
|
||||
case IR::Opcode::FlatStoreByte:
|
||||
case IR::Opcode::FlatStoreShort:
|
||||
case IR::Opcode::FlatStoreDword:
|
||||
case IR::Opcode::DsReadUbyte:
|
||||
case IR::Opcode::DsReadSbyte:
|
||||
case IR::Opcode::DsReadUshort:
|
||||
case IR::Opcode::DsReadSshort:
|
||||
case IR::Opcode::DsReadB32:
|
||||
case IR::Opcode::DsMinF32:
|
||||
case IR::Opcode::DsMaxF32:
|
||||
case IR::Opcode::DsWriteByte:
|
||||
case IR::Opcode::DsWriteShort:
|
||||
case IR::Opcode::DsWriteB32:
|
||||
case IR::Opcode::DsWriteAddtidB32:
|
||||
case IR::Opcode::DsReadAddtidB32:
|
||||
case IR::Opcode::DsAppend:
|
||||
case IR::Opcode::DsConsume:
|
||||
case IR::Opcode::ImageLoad:
|
||||
case IR::Opcode::ImageStore:
|
||||
case IR::Opcode::ImageSample:
|
||||
case IR::Opcode::ImageGather4:
|
||||
case IR::Opcode::Export: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool NeedsDispatcherForStructuredLoopHeader(const IR::Program& ir, uint32_t* failure_block,
|
||||
std::string* reason) {
|
||||
for (const auto& block: ir.blocks) {
|
||||
if (!block.terminator.loop_header) {
|
||||
continue;
|
||||
}
|
||||
for (const auto& inst: block.instructions) {
|
||||
if (!InstructionMaySplitSpirvBlock(inst)) {
|
||||
continue;
|
||||
}
|
||||
if (reason != nullptr) {
|
||||
*reason = fmt::format("loop header block {} contains an instruction whose SPIR-V "
|
||||
"lowering emits internal control flow: {}",
|
||||
block.id, IR::InstructionToString(inst).c_str());
|
||||
}
|
||||
if (failure_block != nullptr) {
|
||||
*failure_block = block.id;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
enum class EmbeddedFetchValueType {
|
||||
Unknown,
|
||||
Constant,
|
||||
@@ -771,69 +692,6 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!dispatcher_fallback) {
|
||||
const auto select_dispatcher = [&](uint32_t failure_block, const std::string& reason,
|
||||
const CFG::Graph& diagnostic_cfg) {
|
||||
dispatcher_fallback = true;
|
||||
dispatcher_reason = reason;
|
||||
cfg.failure_kind = CFG::FailureKind::StructuredControlFlow;
|
||||
cfg.failure_block = failure_block;
|
||||
cfg.unsupported_reason = reason;
|
||||
ir.cfg_failure_kind = CFG::FailureKind::StructuredControlFlow;
|
||||
ir.fallback_reason = reason;
|
||||
LogDispatcherFallback(options, diagnostic_cfg, "emit", reason);
|
||||
};
|
||||
const auto retry_budget = cfg.natural_loops.size();
|
||||
for (size_t retry = 0;; retry++) {
|
||||
std::string emitter_reason;
|
||||
uint32_t failure_block = UINT32_MAX;
|
||||
if (!NeedsDispatcherForStructuredLoopHeader(ir, &failure_block, &emitter_reason)) {
|
||||
break;
|
||||
}
|
||||
if (retry >= retry_budget) {
|
||||
select_dispatcher(failure_block,
|
||||
emitter_reason + "; no-clone loop-header isolation exhausted",
|
||||
cfg);
|
||||
break;
|
||||
}
|
||||
|
||||
const auto* source_block = cfg.FindBlock(failure_block);
|
||||
const auto source_pc = source_block != nullptr ? source_block->start_pc : UINT32_MAX;
|
||||
const auto previous_cfg = cfg;
|
||||
const auto blocks_before = cfg.blocks.size();
|
||||
std::string isolation_error;
|
||||
if (!CFG::IsolateLoopHeader(cfg, failure_block, &isolation_error)) {
|
||||
const auto failed_cfg = cfg;
|
||||
cfg = previous_cfg;
|
||||
select_dispatcher(failure_block,
|
||||
fmt::format("{}; no-clone loop-header isolation failed: {}",
|
||||
emitter_reason, isolation_error),
|
||||
failed_cfg);
|
||||
break;
|
||||
}
|
||||
|
||||
IR::Program isolated_ir;
|
||||
if (!lower_program(isolated_ir)) {
|
||||
const auto lower_error = error != nullptr ? *error : "IR lowering failed";
|
||||
if (error != nullptr) {
|
||||
error->clear();
|
||||
}
|
||||
cfg = previous_cfg;
|
||||
select_dispatcher(failure_block,
|
||||
fmt::format("{}; isolated loop-header lowering failed: {}",
|
||||
emitter_reason, lower_error),
|
||||
cfg);
|
||||
break;
|
||||
}
|
||||
ir = std::move(isolated_ir);
|
||||
LOGF("%s CFG isolated semantic loop header: stage=%s hash=0x%016" PRIx64
|
||||
" source_block=%" PRIu32 " pc=0x%08" PRIx32 " blocks=%" PRIu64 "->%" PRIu64
|
||||
" reason=%s\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
failure_block, source_pc, static_cast<uint64_t>(blocks_before),
|
||||
static_cast<uint64_t>(cfg.blocks.size()), emitter_reason.c_str());
|
||||
}
|
||||
}
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " IR LowerProgram blocks=%" PRIu64
|
||||
" elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
|
||||
@@ -1264,6 +1264,11 @@ bool IsInnermostLoopControlConditional(const Graph& graph, const BasicBlock& blo
|
||||
};
|
||||
return is_repeat_target(true_target) && is_repeat_target(false_target);
|
||||
}
|
||||
const bool true_in_body = Contains(loop->body_blocks, true_target);
|
||||
const bool false_in_body = Contains(loop->body_blocks, false_target);
|
||||
if (true_in_body != false_in_body) {
|
||||
return true;
|
||||
}
|
||||
const auto is_control_target = [&](uint32_t target) {
|
||||
return target == loop->merge || target == loop->continue_block;
|
||||
};
|
||||
@@ -1342,6 +1347,37 @@ bool CanonicalizeNaturalLoops(Graph& graph, std::string* error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsolateSemanticLoopHeaders(Graph& graph, std::string* error) {
|
||||
const auto isolation_budget = graph.natural_loops.size() + 1u;
|
||||
for (size_t isolation = 0; isolation < isolation_budget; isolation++) {
|
||||
const auto loop = std::find_if(graph.natural_loops.begin(), graph.natural_loops.end(),
|
||||
[&](const auto& value) {
|
||||
const auto* header = graph.FindBlock(value.header);
|
||||
return header != nullptr &&
|
||||
header->inst_begin != header->inst_end;
|
||||
});
|
||||
if (loop == graph.natural_loops.end()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// SPIR-V requires OpLoopMerge and its branch to remain in the loop header's
|
||||
// physical block. Keep that header as a dedicated control node, exactly like
|
||||
// the separate Loop node in shadPS4's structured AST. Guest instructions live
|
||||
// in the body so later lowering may introduce bounds/EXEC control flow without
|
||||
// displacing OpLoopMerge into a helper-created block.
|
||||
if (!IsolateSemanticLoopHeader(graph, loop->header)) {
|
||||
SetFailure(graph, FailureKind::StructuredControlFlow, loop->header,
|
||||
fmt::format("failed to isolate semantic loop header {}", loop->header),
|
||||
error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
SetFailure(graph, FailureKind::StructuredControlFlow, graph.entry_block,
|
||||
"CFG semantic loop-header isolation exceeded rewrite budget", error);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SplitSharedMergeBlock(Graph& graph, uint32_t merge,
|
||||
const std::vector<uint32_t>& construct_blocks,
|
||||
bool force_split = false) {
|
||||
@@ -2113,6 +2149,9 @@ bool StructurizeImpl(Graph& graph, std::string* error) {
|
||||
if (!SplitSharedMergeBlocks(graph, error)) {
|
||||
return false;
|
||||
}
|
||||
if (!IsolateSemanticLoopHeaders(graph, error)) {
|
||||
return false;
|
||||
}
|
||||
ClearStructuredTerminators(graph);
|
||||
|
||||
std::map<uint32_t, uint32_t> merge_headers;
|
||||
@@ -2139,6 +2178,14 @@ bool StructurizeImpl(Graph& graph, std::string* error) {
|
||||
error);
|
||||
return false;
|
||||
}
|
||||
if (header->inst_begin != header->inst_end ||
|
||||
header->terminator.kind != TerminatorKind::Branch) {
|
||||
SetFailure(graph, FailureKind::StructuredControlFlow, loop.header,
|
||||
fmt::format("loop header {} is not a dedicated empty control block",
|
||||
loop.header),
|
||||
error);
|
||||
return false;
|
||||
}
|
||||
if (!reserve_merge_block(loop.header, loop.merge)) {
|
||||
return false;
|
||||
}
|
||||
@@ -2207,31 +2254,6 @@ bool Structurize(Graph& graph, std::string* error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsolateLoopHeader(Graph& graph, uint32_t header_id, std::string* error) {
|
||||
const auto loop =
|
||||
std::find_if(graph.natural_loops.begin(), graph.natural_loops.end(),
|
||||
[header_id](const auto& value) { return value.header == header_id; });
|
||||
if (loop == graph.natural_loops.end()) {
|
||||
SetFailure(graph, FailureKind::StructuredControlFlow, header_id,
|
||||
fmt::format("block {} is not a natural loop header", header_id), error);
|
||||
return false;
|
||||
}
|
||||
|
||||
ClearStructuredTerminators(graph);
|
||||
if (!IsolateSemanticLoopHeader(graph, header_id)) {
|
||||
SetFailure(graph, FailureKind::StructuredControlFlow, header_id,
|
||||
fmt::format("loop header block {} has no semantic instructions", header_id),
|
||||
error);
|
||||
return false;
|
||||
}
|
||||
|
||||
graph.unsupported = false;
|
||||
graph.failure_kind = FailureKind::None;
|
||||
graph.failure_block = UINT32_MAX;
|
||||
graph.unsupported_reason.clear();
|
||||
return Structurize(graph, error);
|
||||
}
|
||||
|
||||
std::string BranchConditionToString(BranchCondition condition) {
|
||||
switch (condition) {
|
||||
case BranchCondition::Always: return "always";
|
||||
|
||||
@@ -110,7 +110,6 @@ struct Graph {
|
||||
|
||||
bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* error);
|
||||
bool Structurize(Graph& graph, std::string* error);
|
||||
bool IsolateLoopHeader(Graph& graph, uint32_t header_id, std::string* error);
|
||||
std::string BranchConditionToString(BranchCondition condition);
|
||||
std::string FailureKindToString(FailureKind kind);
|
||||
std::string GraphToString(const Graph& graph);
|
||||
|
||||
Reference in New Issue
Block a user