mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-24 21:06:55 +02:00
shader: resolve descriptors from control flow
This commit is contained in:
@@ -548,30 +548,40 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
|
||||
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " CFG BuildGraph\n", GetDumpLabel(options),
|
||||
StageName(options.stage), options.shader_hash);
|
||||
auto cfg = CFG::BuildGraph(decoded);
|
||||
auto native_cfg = CFG::BuildGraph(decoded);
|
||||
CFG::Graph structured_cfg;
|
||||
auto* selected_cfg = &native_cfg;
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " CFG BuildGraph blocks=%" PRIu64
|
||||
" loops=%" PRIu64 " back_edges=%" PRIu64 " elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
static_cast<uint64_t>(cfg.blocks.size()), static_cast<uint64_t>(cfg.natural_loops.size()),
|
||||
static_cast<uint64_t>(cfg.back_edges.size()), phase_ms());
|
||||
if (cfg.irreducible) {
|
||||
LogDispatcherFallback(options, cfg, "build");
|
||||
static_cast<uint64_t>(native_cfg.blocks.size()),
|
||||
static_cast<uint64_t>(native_cfg.natural_loops.size()),
|
||||
static_cast<uint64_t>(native_cfg.back_edges.size()), phase_ms());
|
||||
if (native_cfg.irreducible) {
|
||||
LogDispatcherFallback(options, native_cfg, "build");
|
||||
} else {
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " CFG Structurize\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash);
|
||||
if (!CFG::Structurize(cfg)) {
|
||||
LogDispatcherFallback(options, cfg, "structurize");
|
||||
structured_cfg = CFG::Structurize(native_cfg);
|
||||
if (structured_cfg.unsupported) {
|
||||
native_cfg.unsupported = true;
|
||||
native_cfg.failure_kind = structured_cfg.failure_kind;
|
||||
native_cfg.failure_block = structured_cfg.failure_block;
|
||||
native_cfg.unsupported_reason = structured_cfg.unsupported_reason;
|
||||
LogDispatcherFallback(options, native_cfg, "structurize");
|
||||
} else {
|
||||
selected_cfg = &structured_cfg;
|
||||
LOGF("%s structured CFG success: blocks=%" PRIu64 "\n", GetDumpLabel(options),
|
||||
static_cast<uint64_t>(cfg.blocks.size()));
|
||||
static_cast<uint64_t>(selected_cfg->blocks.size()));
|
||||
}
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " CFG Structurize blocks=%" PRIu64
|
||||
" loops=%" PRIu64 " elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
static_cast<uint64_t>(cfg.blocks.size()),
|
||||
static_cast<uint64_t>(cfg.natural_loops.size()), phase_ms());
|
||||
static_cast<uint64_t>(selected_cfg->blocks.size()),
|
||||
static_cast<uint64_t>(selected_cfg->natural_loops.size()), phase_ms());
|
||||
}
|
||||
|
||||
const auto& cfg = *selected_cfg;
|
||||
Frontend::EmbeddedFetchPlan embedded_fetch;
|
||||
if ((options.stage == ShaderType::Vertex || options.stage == ShaderType::Local) &&
|
||||
options.input_info.vertex != nullptr && options.input_info.vertex->fetch_embedded) {
|
||||
@@ -616,7 +626,7 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
|
||||
LowerTessellationMemory(ir, options);
|
||||
IR::BuildSrtPlan(ir);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
IR::TrackResources(ir);
|
||||
IR::TrackResources(ir, decoded, native_cfg);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
TranslateResult result;
|
||||
result.program = std::move(ir);
|
||||
|
||||
@@ -1758,16 +1758,13 @@ Graph BuildGraph(const Decoder::Program& program) {
|
||||
return graph;
|
||||
}
|
||||
|
||||
bool Structurize(Graph& graph) {
|
||||
if (graph.unsupported || graph.irreducible) return false;
|
||||
auto structured = GotoStructurizer(graph).Run();
|
||||
if (structured.unsupported) {
|
||||
SetFailure(graph, structured.failure_kind, structured.failure_block,
|
||||
structured.unsupported_reason);
|
||||
return false;
|
||||
Graph Structurize(const Graph& graph) {
|
||||
if (graph.unsupported || graph.irreducible) {
|
||||
Graph failed;
|
||||
SetFailure(failed, graph.failure_kind, graph.failure_block, graph.unsupported_reason);
|
||||
return failed;
|
||||
}
|
||||
graph = std::move(structured);
|
||||
return true;
|
||||
return GotoStructurizer(graph).Run();
|
||||
}
|
||||
|
||||
std::string BranchConditionToString(BranchCondition condition) {
|
||||
|
||||
@@ -119,9 +119,9 @@ struct Graph {
|
||||
};
|
||||
|
||||
Graph BuildGraph(const Decoder::Program& program);
|
||||
// Commits structured control flow on success; preserves the original graph with
|
||||
// failure diagnostics on failure. failure_block is an original block ID or UINT32_MAX.
|
||||
bool Structurize(Graph& graph);
|
||||
// Returns structured control flow or failure diagnostics without changing the native graph.
|
||||
// On failure, failure_block is an original block ID or UINT32_MAX.
|
||||
Graph Structurize(const Graph& graph);
|
||||
std::string BranchConditionToString(BranchCondition condition);
|
||||
std::string FailureKindToString(FailureKind kind);
|
||||
std::string GraphToString(const Graph& graph);
|
||||
|
||||
@@ -317,6 +317,7 @@ void Translator::WriteRawU32(const Decoder::Operand& operand, IR::U32 value) {
|
||||
case Decoder::OperandKind::Sgpr: {
|
||||
const auto reg = static_cast<IR::ScalarReg>(operand.reg);
|
||||
ir.SetScalarReg(reg, value);
|
||||
program.scalar_writes.push_back({current_pc, reg});
|
||||
ir.SetScalarMaskTag(reg, IR::U1(IR::Value(false)));
|
||||
if (IR::RegIndex(reg) > 0u) {
|
||||
ir.SetScalarMaskTag(static_cast<IR::ScalarReg>(IR::RegIndex(reg) - 1u),
|
||||
@@ -699,11 +700,13 @@ std::array<IR::U32, 2> Translator::WriteMask(const Decoder::Operand& operand, IR
|
||||
IR::U1(IR::Value(false)));
|
||||
}
|
||||
ir.SetScalarReg(reg, mask[0]);
|
||||
program.scalar_writes.push_back({current_pc, reg});
|
||||
// A wave32 VALU mask destination must not overwrite the neighboring SGPR.
|
||||
if ((write_64 || program.wave_size == 64u) &&
|
||||
IR::RegIndex(reg) + 1u < IR::NumScalarRegs) {
|
||||
const auto high = static_cast<IR::ScalarReg>(IR::RegIndex(reg) + 1u);
|
||||
ir.SetScalarReg(high, mask[1]);
|
||||
program.scalar_writes.push_back({current_pc, high});
|
||||
ir.SetThreadBitScalarReg(high, IR::U1(IR::Value(false)));
|
||||
ir.SetScalarMaskTag(high, IR::U1(IR::Value(false)));
|
||||
}
|
||||
|
||||
@@ -81,7 +81,8 @@ bool EquivalentValue(const ResourcePlan& program, Value left, Value right,
|
||||
program.memory_info[li] != program.memory_info[ri]) {
|
||||
return false;
|
||||
}
|
||||
} else if (lhs->Flags<uint64_t>() != rhs->Flags<uint64_t>()) {
|
||||
} else if (lhs->GetOpcode() != ValueOpcode::ReadConst &&
|
||||
lhs->Flags<uint64_t>() != rhs->Flags<uint64_t>()) {
|
||||
return false;
|
||||
}
|
||||
for (size_t index = 0; index < lhs->NumArgs(); index++) {
|
||||
|
||||
@@ -583,6 +583,8 @@ struct Program: ResourcePlan {
|
||||
CFG::FailureKind cfg_failure_kind = CFG::FailureKind::None;
|
||||
std::string fallback_reason;
|
||||
std::vector<BlockInfo> block_info;
|
||||
struct ScalarWrite { uint32_t pc; ScalarReg reg; };
|
||||
std::vector<ScalarWrite> scalar_writes;
|
||||
// Typed memory and export instructions reference shader-local metadata by dense index.
|
||||
// Decoder-only details (such as NSA register numbers) have already become IR operands.
|
||||
std::vector<ExportInfo> export_info;
|
||||
|
||||
@@ -82,7 +82,10 @@ uint32_t ByteExtent(const MemoryInfo& memory) {
|
||||
|
||||
class Tracker {
|
||||
public:
|
||||
explicit Tracker(Program& program): m_program(program), m_info(program.info) {
|
||||
Tracker(Program& program, const Decoder::Program& decoded, const CFG::Graph& native_cfg)
|
||||
: m_program(program), m_decoded(decoded), m_native_cfg(native_cfg),
|
||||
m_scalar_writes(std::move(program.scalar_writes)), m_info(program.info) {
|
||||
std::ranges::sort(m_scalar_writes, {}, &Program::ScalarWrite::pc);
|
||||
m_info.buffers.clear();
|
||||
m_info.images.clear();
|
||||
m_info.samplers.clear();
|
||||
@@ -169,7 +172,98 @@ private:
|
||||
std::abort();
|
||||
}
|
||||
|
||||
Value LowerDescriptorPhi(Value value, const Block* use) {
|
||||
Value NativeDescriptorSource(Value value, uint32_t reg, uint32_t use_pc) const {
|
||||
value = value.Resolve();
|
||||
const auto* phi = value.TryInstruction();
|
||||
if (phi == nullptr || phi->GetOpcode() != ValueOpcode::Phi || m_native_cfg.blocks.empty())
|
||||
return value;
|
||||
std::vector<const Inst*> candidates;
|
||||
std::vector<const Inst*> visited;
|
||||
std::vector<const Inst*> pending {phi};
|
||||
while (!pending.empty()) {
|
||||
const auto* inst = pending.back();
|
||||
pending.pop_back();
|
||||
if (std::ranges::find(visited, inst) != visited.end()) continue;
|
||||
visited.push_back(inst);
|
||||
if (inst->GetOpcode() == ValueOpcode::Phi) {
|
||||
for (size_t i = 0; i < inst->NumArgs(); ++i) {
|
||||
const auto* arg = inst->Arg(i).Resolve().TryInstruction();
|
||||
if (arg != nullptr) pending.push_back(arg);
|
||||
}
|
||||
} else if (inst->GetOpcode() == ValueOpcode::ReadConst ||
|
||||
inst->GetOpcode() == ValueOpcode::GetUserData) {
|
||||
candidates.push_back(inst);
|
||||
}
|
||||
}
|
||||
if (candidates.empty()) return value;
|
||||
const auto source_at = [&](uint32_t pc) {
|
||||
Value source;
|
||||
const auto native = std::ranges::lower_bound(m_decoded.instructions, pc, {},
|
||||
&Decoder::Instruction::pc);
|
||||
for (const auto* candidate: candidates) {
|
||||
if (pc == UINT32_MAX) {
|
||||
if (candidate->GetOpcode() != ValueOpcode::GetUserData ||
|
||||
RegIndex(candidate->Arg(0).ScalarRegister()) != reg)
|
||||
continue;
|
||||
} else {
|
||||
if (candidate->GetOpcode() != ValueOpcode::ReadConst) continue;
|
||||
const auto flags = candidate->Flags<MemoryFlags>();
|
||||
if (flags.pc != pc || flags.index >= m_program.memory_info.size()) continue;
|
||||
if (native == m_decoded.instructions.end() || native->pc != pc ||
|
||||
native->dst.kind != Decoder::OperandKind::Sgpr ||
|
||||
native->dst.reg + m_program.memory_info[flags.index].component_index != reg)
|
||||
continue;
|
||||
}
|
||||
const Value current(const_cast<Inst*>(candidate));
|
||||
if (!source.IsEmpty() && !EquivalentValue(m_program, source, current)) return Value {};
|
||||
source = current;
|
||||
}
|
||||
return source;
|
||||
};
|
||||
const auto use = std::ranges::find_if(m_native_cfg.blocks, [&](const auto& block) {
|
||||
return block.start_pc <= use_pc && use_pc < block.end_pc;
|
||||
});
|
||||
if (use == m_native_cfg.blocks.end()) return value;
|
||||
struct Position { uint32_t block; uint32_t before; };
|
||||
std::vector<Position> positions {{use->id, use_pc}};
|
||||
std::vector<bool> reached(m_native_cfg.blocks.size());
|
||||
Value selected;
|
||||
const auto select = [&](uint32_t pc) {
|
||||
const auto source = source_at(pc);
|
||||
if (source.IsEmpty() || (!selected.IsEmpty() && !EquivalentValue(m_program, selected, source)))
|
||||
return false;
|
||||
selected = source;
|
||||
return true;
|
||||
};
|
||||
while (!positions.empty()) {
|
||||
const auto position = positions.back();
|
||||
positions.pop_back();
|
||||
const auto& block = m_native_cfg.blocks[position.block];
|
||||
// A backedge may revisit the use block after a write later than the original use.
|
||||
if (position.before == block.end_pc) {
|
||||
if (reached[block.id]) continue;
|
||||
reached[block.id] = true;
|
||||
}
|
||||
auto write = std::ranges::lower_bound(m_scalar_writes, position.before, {},
|
||||
&Program::ScalarWrite::pc);
|
||||
bool found = false;
|
||||
while (write != m_scalar_writes.begin()) {
|
||||
--write;
|
||||
if (write->pc < block.start_pc) break;
|
||||
if (RegIndex(write->reg) != reg) continue;
|
||||
if (!select(write->pc)) return value;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (found) continue;
|
||||
if (block.id == m_native_cfg.entry_block && !select(UINT32_MAX)) return value;
|
||||
for (const auto pred: block.predecessors)
|
||||
positions.push_back({pred, m_native_cfg.blocks[pred].end_pc});
|
||||
}
|
||||
return selected.IsEmpty() ? value : selected;
|
||||
}
|
||||
|
||||
Value LowerDescriptorPhi(Value value) {
|
||||
value = value.Resolve();
|
||||
const auto* phi = value.TryInstruction();
|
||||
if (m_shader_writes || phi == nullptr || phi->GetOpcode() != ValueOpcode::Phi ||
|
||||
@@ -183,42 +277,6 @@ private:
|
||||
branch == phi->PhiBlock(1)) {
|
||||
return value;
|
||||
}
|
||||
// Structurization can merge the descriptor and its use predicate in parallel Phis.
|
||||
// Match their incoming blocks to exclude only edges that cannot reach this use.
|
||||
if (use != nullptr && use->ImmPredecessors().size() == 1u &&
|
||||
use->ImmPredecessors()[0] == merge) {
|
||||
const auto merge_it = std::ranges::find(m_program.blocks, merge);
|
||||
const auto use_it = std::ranges::find(m_program.blocks, use);
|
||||
if (merge_it != m_program.blocks.end() && use_it != m_program.blocks.end()) {
|
||||
const auto& info = m_program.block_info[merge_it - m_program.blocks.begin()];
|
||||
const auto& term = info.terminator;
|
||||
const auto id = m_program.block_info[use_it - m_program.blocks.begin()].id;
|
||||
const auto* condition = info.condition.Resolve().TryInstruction();
|
||||
if (term.kind == CFG::TerminatorKind::ConditionalBranch &&
|
||||
term.true_block != term.false_block &&
|
||||
(id == term.true_block || id == term.false_block) && condition != nullptr &&
|
||||
condition->GetOpcode() == ValueOpcode::Phi && condition->GetType() == Type::U1 &&
|
||||
condition->Parent() == merge && condition->NumArgs() == 2u &&
|
||||
condition->NumPhiBlocks() == 2u) {
|
||||
const bool taken = id == term.true_block;
|
||||
for (uint32_t skipped = 0; skipped < 2u; skipped++) {
|
||||
const auto excluded = condition->Arg(skipped).Resolve();
|
||||
const auto included = condition->Arg(skipped ^ 1u).Resolve();
|
||||
if (!excluded.IsImmediate() || excluded.GetType() != Type::U1 ||
|
||||
excluded.U1() == taken ||
|
||||
(included.IsImmediate() && included.U1() != taken)) {
|
||||
continue;
|
||||
}
|
||||
for (uint32_t selected = 0; selected < 2u; selected++) {
|
||||
if (phi->PhiBlock(selected) == condition->PhiBlock(skipped ^ 1u) &&
|
||||
phi->PhiBlock(selected ^ 1u) == condition->PhiBlock(skipped)) {
|
||||
return phi->Arg(selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& [original, selected]: m_descriptor_selections) {
|
||||
if (original == phi) {
|
||||
return selected;
|
||||
@@ -276,14 +334,16 @@ private:
|
||||
}
|
||||
|
||||
void MakeSource(const Inst& handle, uint32_t width, bool sampler, bool sample_adjust,
|
||||
DescriptorSource& descriptor, uint32_t pc) {
|
||||
uint32_t base_reg, DescriptorSource& descriptor, uint32_t pc) {
|
||||
if (handle.NumArgs() != width) {
|
||||
Fail(pc, fmt::format("{} has {} descriptor dwords, expected {}",
|
||||
ValueOpcodeName(handle.GetOpcode()), handle.NumArgs(), width));
|
||||
}
|
||||
descriptor.dword_count = width;
|
||||
for (uint32_t i = 0; i < width; i++) {
|
||||
descriptor.dwords[i] = LowerDescriptorPhi(handle.Arg(i), handle.Parent());
|
||||
const auto value = base_reg != UINT32_MAX
|
||||
? NativeDescriptorSource(handle.Arg(i), base_reg + i, pc) : handle.Arg(i);
|
||||
descriptor.dwords[i] = LowerDescriptorPhi(value);
|
||||
}
|
||||
if (sample_adjust) {
|
||||
descriptor.dwords[3] = CanonicalizeSampleAdjustDword3(descriptor.dwords[3]);
|
||||
@@ -404,7 +464,7 @@ private:
|
||||
if (width == 0u) {
|
||||
return false;
|
||||
}
|
||||
MakeSource(handle, width, false, false, descriptor, pc);
|
||||
MakeSource(handle, width, false, false, UINT32_MAX, descriptor, pc);
|
||||
uint32_t bad_dword = 0;
|
||||
return ValidateSource(descriptor, bad_dword);
|
||||
}
|
||||
@@ -1014,14 +1074,15 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
bool GetHandle(Value value, ValueOpcode expected, uint32_t width, uint32_t pc, Inst*& handle,
|
||||
uint32_t& source, bool sampler = false, bool sample_adjust = false) {
|
||||
bool GetHandle(Value value, ValueOpcode expected, uint32_t width, uint32_t pc,
|
||||
uint32_t base_reg, Inst*& handle, uint32_t& source, bool sampler = false,
|
||||
bool sample_adjust = false) {
|
||||
handle = value.Resolve().TryInstruction();
|
||||
if (handle == nullptr || handle->GetOpcode() != expected) {
|
||||
Fail(pc, fmt::format("memory operation requires {}", ValueOpcodeName(expected)));
|
||||
}
|
||||
DescriptorSource descriptor;
|
||||
MakeSource(*handle, width, sampler, sample_adjust, descriptor, pc);
|
||||
MakeSource(*handle, width, sampler, sample_adjust, base_reg, descriptor, pc);
|
||||
uint32_t bad_dword = 0;
|
||||
if (!ValidateSource(descriptor, bad_dword)) {
|
||||
if (expected == ValueOpcode::GetBufferResource &&
|
||||
@@ -1204,8 +1265,8 @@ private:
|
||||
uint32_t resource = 0;
|
||||
|
||||
if (buffer != BufferAccess::None) {
|
||||
if (!GetHandle(inst.Arg(0), ValueOpcode::GetBufferResource, 4, flags.pc, handle,
|
||||
source)) {
|
||||
if (!GetHandle(inst.Arg(0), ValueOpcode::GetBufferResource, 4, flags.pc,
|
||||
memory.resource * 4u, handle, source)) {
|
||||
if (memory.kind != ResourceKind::Buffer || !memory.SupportsIndirectBufferLoad(op)) {
|
||||
Fail(flags.pc,
|
||||
"buffer descriptor is not a valid runtime value; GPU-selected access "
|
||||
@@ -1255,7 +1316,8 @@ private:
|
||||
if (indirect != nullptr) {
|
||||
source = indirect->source;
|
||||
} else {
|
||||
GetHandle(inst.Arg(0), ValueOpcode::GetImageResource, 8, flags.pc, handle, source);
|
||||
GetHandle(inst.Arg(0), ValueOpcode::GetImageResource, 8, flags.pc,
|
||||
memory.resource * 4u, handle, source);
|
||||
}
|
||||
resource = AddImage(source, memory, op, flags.pc);
|
||||
if (resource == UINT32_MAX) {
|
||||
@@ -1271,8 +1333,8 @@ private:
|
||||
uint32_t sampler_source = 0;
|
||||
const bool sample_adjust =
|
||||
(memory.image_sample_flags & Decoder::ImageSampleFlagAdjust) != 0;
|
||||
GetHandle(inst.Arg(1), ValueOpcode::GetSamplerResource, 4, flags.pc, sampler_handle,
|
||||
sampler_source, true, sample_adjust);
|
||||
GetHandle(inst.Arg(1), ValueOpcode::GetSamplerResource, 4, flags.pc,
|
||||
memory.sampler * 4u, sampler_handle, sampler_source, true, sample_adjust);
|
||||
sampler = AddSampler(sampler_source, flags.pc);
|
||||
if (sampler == UINT32_MAX) {
|
||||
Fail(flags.pc, "sampler resource limit exceeded");
|
||||
@@ -1313,6 +1375,9 @@ private:
|
||||
}
|
||||
|
||||
Program& m_program;
|
||||
const Decoder::Program& m_decoded;
|
||||
const CFG::Graph& m_native_cfg;
|
||||
std::vector<Program::ScalarWrite> m_scalar_writes;
|
||||
ShaderInfo m_info;
|
||||
std::vector<DescriptorSource> m_sources;
|
||||
std::vector<HandlePatch> m_handle_patches;
|
||||
@@ -1324,8 +1389,8 @@ private:
|
||||
|
||||
} // namespace
|
||||
|
||||
void TrackResources(Program& program) {
|
||||
Tracker(program).Run();
|
||||
void TrackResources(Program& program, const Decoder::Program& decoded, const CFG::Graph& native_cfg) {
|
||||
Tracker(program, decoded, native_cfg).Run();
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
// Collects immutable resource topology from typed SSA handles, interns their resolved dwords in
|
||||
// descriptor_sources, then writes dense indices to handle flags and MemoryInfo.
|
||||
void TrackResources(Program& program);
|
||||
void TrackResources(Program& program, const Decoder::Program& decoded, const CFG::Graph& native_cfg);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
|
||||
@@ -430,7 +430,8 @@ private:
|
||||
const auto resource =
|
||||
Value(&*block->PrependNewInst(where, ValueOpcode::GetSrtResource));
|
||||
const auto flat = Value(&*block->PrependNewInst(where, ValueOpcode::ReadConst,
|
||||
{resource, Value(patch.slot)}));
|
||||
{resource, Value(patch.slot)},
|
||||
patch.inst->Flags<uint64_t>()));
|
||||
const auto uses = patch.inst->Uses();
|
||||
for (const auto& use: uses) {
|
||||
use.user->SetArg(use.operand, flat);
|
||||
|
||||
@@ -127,7 +127,7 @@ struct Fixture {
|
||||
|
||||
void PlanAndTrack() {
|
||||
BuildSrtPlan(program);
|
||||
TrackResources(program);
|
||||
TrackResources(program, {}, {});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -520,7 +520,7 @@ void TestInvariantIndirectImageMaterialization() {
|
||||
|
||||
auto malformed = MakeIndirectImageFixture(true);
|
||||
BuildSrtPlan(malformed->program);
|
||||
CheckFatal([&] { TrackResources(malformed->program); }, "not a valid runtime value",
|
||||
CheckFatal([&] { TrackResources(malformed->program, {}, {}); }, "not a valid runtime value",
|
||||
"malformed indirect image pattern was accepted");
|
||||
Check(!malformed->program.resource_tracking_complete &&
|
||||
malformed->program.info.images.empty() &&
|
||||
@@ -529,7 +529,7 @@ void TestInvariantIndirectImageMaterialization() {
|
||||
|
||||
auto wrapped_immediate = MakeIndirectImageFixture(false, 4u);
|
||||
BuildSrtPlan(wrapped_immediate->program);
|
||||
CheckFatal([&] { TrackResources(wrapped_immediate->program); },
|
||||
CheckFatal([&] { TrackResources(wrapped_immediate->program, {}, {}); },
|
||||
"not a valid runtime value",
|
||||
"wrapped scalar immediate entered the invariant image proof");
|
||||
Check(!wrapped_immediate->program.resource_tracking_complete,
|
||||
@@ -1405,7 +1405,7 @@ void TestDenseBufferTracking() {
|
||||
fixture.program.memory_info[other_flags.index].resource == 1,
|
||||
"typed memory metadata was not patched to dense indices");
|
||||
|
||||
CheckFatal([&] { TrackResources(fixture.program); }, "already tracked",
|
||||
CheckFatal([&] { TrackResources(fixture.program, {}, {}); }, "already tracked",
|
||||
"resource tracking allowed a second mutation pass");
|
||||
}
|
||||
|
||||
@@ -1602,7 +1602,7 @@ void TestSampleAdjustSamplerScratch() {
|
||||
{rejected_image, rejected_sampler, rejected.ImageAddress()},
|
||||
rejected.AddMemory(rejected_memory, 0x200));
|
||||
BuildSrtPlan(rejected.program);
|
||||
CheckFatal([&] { TrackResources(rejected.program); },
|
||||
CheckFatal([&] { TrackResources(rejected.program, {}, {}); },
|
||||
"not a valid runtime value", message);
|
||||
};
|
||||
CheckRejected(0u, 12u,
|
||||
@@ -1991,7 +1991,7 @@ void TestPhiValidation() {
|
||||
fixture.AddMemory(memory, 20), merge);
|
||||
|
||||
BuildSrtPlan(fixture.program);
|
||||
CheckFatal([&] { TrackResources(fixture.program); }, "not a valid runtime value",
|
||||
CheckFatal([&] { TrackResources(fixture.program, {}, {}); }, "not a valid runtime value",
|
||||
"control-dependent descriptor phi was accepted");
|
||||
Check(!fixture.program.resource_tracking_complete &&
|
||||
fixture.program.info.buffers.empty() &&
|
||||
@@ -2155,90 +2155,6 @@ void TestConditionalSamplerPhi() {
|
||||
}
|
||||
}
|
||||
|
||||
void TestGuardedSamplerPhi() {
|
||||
namespace CFG = Libs::Graphics::ShaderRecompiler::CFG;
|
||||
const auto make_plan = [](bool take_true, bool reverse_phi, bool bypass,
|
||||
bool mismatched, bool ambiguous) {
|
||||
Fixture fixture(ShaderType::Pixel);
|
||||
auto *entry = fixture.block;
|
||||
auto *loaded = fixture.AddBlock();
|
||||
auto *merge = fixture.AddBlock();
|
||||
auto *sample = fixture.AddBlock();
|
||||
auto *exit = fixture.AddBlock();
|
||||
entry->AddBranch(loaded);
|
||||
entry->AddBranch(merge);
|
||||
loaded->AddBranch(merge);
|
||||
merge->AddBranch(sample);
|
||||
merge->AddBranch(exit);
|
||||
sample->AddBranch(exit);
|
||||
if (bypass) exit->AddBranch(sample);
|
||||
const auto lane = fixture.Emit(ValueOpcode::LaneId);
|
||||
const auto predicate = fixture.Emit(ValueOpcode::IEqual32, {lane, Value(0u)});
|
||||
fixture.program.block_info[0].condition = predicate;
|
||||
fixture.program.block_info[0].terminator = {
|
||||
.kind = CFG::TerminatorKind::ConditionalBranch,
|
||||
.true_block = 1u, .false_block = 2u};
|
||||
fixture.program.block_info[1].terminator = {
|
||||
.kind = CFG::TerminatorKind::Branch, .true_block = 2u};
|
||||
auto &guard = merge->AppendNewInst(ValueOpcode::Phi, {},
|
||||
static_cast<uint64_t>(Type::U1));
|
||||
guard.AddPhiOperand(entry, ambiguous ? predicate : Value(!take_true));
|
||||
guard.AddPhiOperand(mismatched ? sample : loaded, predicate);
|
||||
fixture.program.block_info[2].condition = Value(&guard);
|
||||
fixture.program.block_info[2].terminator = {
|
||||
.kind = CFG::TerminatorKind::ConditionalBranch,
|
||||
.true_block = take_true ? 3u : 4u,
|
||||
.false_block = take_true ? 4u : 3u};
|
||||
fixture.program.block_info[3].terminator = {
|
||||
.kind = CFG::TerminatorKind::Branch, .true_block = 4u};
|
||||
fixture.program.block_info[4].terminator = {
|
||||
.kind = bypass ? CFG::TerminatorKind::Branch : CFG::TerminatorKind::Return,
|
||||
.true_block = 3u};
|
||||
std::array<Value, 4> words;
|
||||
for (uint32_t word = 0; word < words.size(); ++word) {
|
||||
// An arbitrary excluded value proves this is control-flow reasoning, not null filtering.
|
||||
const auto first = Value(0xbad000u + word);
|
||||
const auto second = fixture.UserData(word);
|
||||
auto &phi = merge->AppendNewInst(ValueOpcode::Phi, {},
|
||||
static_cast<uint64_t>(Type::U32));
|
||||
phi.AddPhiOperand(reverse_phi ? loaded : entry, reverse_phi ? second : first);
|
||||
phi.AddPhiOperand(reverse_phi ? entry : loaded, reverse_phi ? first : second);
|
||||
words[word] = Value(&phi);
|
||||
}
|
||||
fixture.block = sample;
|
||||
const auto image = fixture.Image({Value(0u), Value(0u), Value(0u), Value(0u),
|
||||
Value(0u), Value(0u), Value(0u), Value(0u)});
|
||||
const auto sampler = fixture.Sampler(words);
|
||||
MemoryInfo memory;
|
||||
memory.kind = ResourceKind::Image;
|
||||
memory.image_dimension = Decoder::ImageDimension::Dim2D;
|
||||
fixture.Emit(ValueOpcode::ImageSampleRaw,
|
||||
{image, sampler, fixture.ImageAddress()},
|
||||
fixture.AddMemory(memory, 0x2a0));
|
||||
fixture.PlanAndTrack();
|
||||
Check(words[0].ResolveInstruction()->GetOpcode() == ValueOpcode::Phi,
|
||||
"guarded host descriptor selection rewrote the GPU Phi");
|
||||
return ExtractResourcePlan(fixture.program);
|
||||
};
|
||||
for (const bool take_true : {false, true}) {
|
||||
for (const bool reverse_phi : {false, true}) {
|
||||
auto plan = make_plan(take_true, reverse_phi, false, false, false);
|
||||
const std::array<uint32_t, 4> user_data{0x444u, 0x555u, 0x666u, 0x777u};
|
||||
DescriptorValue selected;
|
||||
Check(SrtWalker(plan, {.user_data = user_data}).EvaluateDescriptor(
|
||||
plan.info.samplers[0].source, selected) &&
|
||||
std::equal(user_data.begin(), user_data.end(), selected.dwords.begin()),
|
||||
"guarded sampler did not match descriptor and predicate predecessors");
|
||||
}
|
||||
}
|
||||
CheckFatal([&] { make_plan(true, false, true, false, false); },
|
||||
"not a valid runtime value", "sampler guard accepted an unguarded path");
|
||||
CheckFatal([&] { make_plan(true, false, false, true, false); },
|
||||
"not a valid runtime value", "sampler guard ignored predecessor identity");
|
||||
CheckFatal([&] { make_plan(true, false, false, false, true); },
|
||||
"not a valid runtime value", "sampler guard discarded a reachable alternative");
|
||||
}
|
||||
|
||||
void TestLoopCycleEnteredThroughRuntimeValue() {
|
||||
Fixture fixture;
|
||||
auto *entry = fixture.block;
|
||||
@@ -2852,7 +2768,7 @@ void TestResourceLimitIsTransactional() {
|
||||
fixture.AddMemory(memory, index * 4u));
|
||||
}
|
||||
BuildSrtPlan(fixture.program);
|
||||
CheckFatal([&] { TrackResources(fixture.program); },
|
||||
CheckFatal([&] { TrackResources(fixture.program, {}, {}); },
|
||||
"buffer resource limit exceeded",
|
||||
"resource-limit failure was not reported");
|
||||
Check(!fixture.program.resource_tracking_complete &&
|
||||
@@ -2872,7 +2788,7 @@ void TestMalformedMemoryKindsRejected() {
|
||||
fixture.AddMemory(memory, 4));
|
||||
BuildSrtPlan(fixture.program);
|
||||
CheckFatal(
|
||||
[&] { TrackResources(fixture.program); },
|
||||
[&] { TrackResources(fixture.program, {}, {}); },
|
||||
"address operation has invalid resource kind",
|
||||
"resource tracking accepted an address opcode with buffer metadata");
|
||||
}
|
||||
@@ -2889,7 +2805,7 @@ void TestMalformedMemoryKindsRejected() {
|
||||
fixture.AddMemory(memory, 8));
|
||||
BuildSrtPlan(fixture.program);
|
||||
CheckFatal(
|
||||
[&] { TrackResources(fixture.program); },
|
||||
[&] { TrackResources(fixture.program, {}, {}); },
|
||||
"image operation has invalid resource kind",
|
||||
"resource tracking accepted an image opcode with address metadata");
|
||||
}
|
||||
@@ -2924,7 +2840,6 @@ int main() {
|
||||
Run("dynamic SRT", TestDynamicSrtReadRemainsExplicit);
|
||||
Run("phi validation", TestPhiValidation);
|
||||
Run("conditional sampler phi", TestConditionalSamplerPhi);
|
||||
Run("guarded sampler phi", TestGuardedSamplerPhi);
|
||||
Run("runtime-rooted loop", TestLoopCycleEnteredThroughRuntimeValue);
|
||||
Run("invariant loop phi", TestInvariantLoopPhi);
|
||||
Run("DMA address materialization", TestDmaAddressMaterialization);
|
||||
|
||||
+282
-56
@@ -7778,8 +7778,8 @@ void TestNewShaderRecompilerCfgLoopHeaderDsAppendConsumeStructured() {
|
||||
const auto original_block_count = graph.blocks.size();
|
||||
const auto original_coverage =
|
||||
CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph),
|
||||
graph.unsupported_reason.c_str());
|
||||
graph = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
||||
Check(graph.natural_loops.size() == 1u, "DS loop was not preserved");
|
||||
Check(graph.blocks.size() == original_block_count + 1u,
|
||||
"DS loop structurization did not add exactly one empty header");
|
||||
@@ -7914,7 +7914,8 @@ void TestNewShaderRecompilerCfgLoopExitSharedWithSelection() {
|
||||
const auto coverage = CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
Check(block_count == 8u && graph.natural_loops.size() == 1u,
|
||||
"shared loop-exit fixture has the wrong native CFG");
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph), graph.unsupported_reason.c_str());
|
||||
graph = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == coverage,
|
||||
"shared loop-exit routing changed semantic instruction coverage");
|
||||
|
||||
@@ -8029,8 +8030,8 @@ void TestNewShaderRecompilerCfgNestedLoopExitTailMergeSplit() {
|
||||
ShaderRecompiler::CFG::Graph graph;
|
||||
graph = ShaderRecompiler::CFG::BuildGraph(program);
|
||||
const auto original_block_count = graph.blocks.size();
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph),
|
||||
graph.unsupported_reason.c_str());
|
||||
graph = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
||||
Check(graph.blocks.size() > original_block_count,
|
||||
"nested loop exit tails did not create a private inner merge");
|
||||
|
||||
@@ -8177,8 +8178,8 @@ void TestNewShaderRecompilerCfgLoopGatewaySelection() {
|
||||
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
||||
const auto original_coverage =
|
||||
CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph),
|
||||
graph.unsupported_reason.c_str());
|
||||
graph = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
||||
original_coverage,
|
||||
"loop gateway structurization duplicated semantic instructions");
|
||||
@@ -8216,8 +8217,8 @@ void TestNewShaderRecompilerCfgConditionalLoopHeaderSelection() {
|
||||
ShaderRecompiler::CFG::Graph graph;
|
||||
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
||||
const auto original_block_count = graph.blocks.size();
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph),
|
||||
graph.unsupported_reason.c_str());
|
||||
graph = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
||||
Check(graph.blocks.size() > original_block_count,
|
||||
"conditional guest loop header did not create a synthetic header");
|
||||
|
||||
@@ -8269,8 +8270,8 @@ void TestNewShaderRecompilerCfgMultipleLoopLatches() {
|
||||
const auto coverage = CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
Check(graph.back_edges.size() == 2u,
|
||||
"multiple-latch fixture lacks two native backedges");
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph),
|
||||
graph.unsupported_reason.c_str());
|
||||
graph = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == coverage,
|
||||
"shared backedge construction duplicated guest instructions");
|
||||
Check(graph.back_edges.size() == 1u && graph.natural_loops.size() == 1u,
|
||||
@@ -8344,8 +8345,8 @@ void TestNewShaderRecompilerCfgNestedEarlyExitLoopForwarders() {
|
||||
CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
Check(original_block_count == 7u && graph.natural_loops.size() == 1u,
|
||||
"nested early-exit fixture has the wrong native CFG");
|
||||
const bool structured = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(structured, graph.unsupported_reason.c_str());
|
||||
graph = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == original_coverage,
|
||||
"nested early-exit structurization changed semantic coverage");
|
||||
std::unordered_set<uint32_t> merges;
|
||||
@@ -8391,8 +8392,8 @@ void TestNewShaderRecompilerCfgExecSccSharedArm() {
|
||||
Check(std::ranges::all_of(original_coverage,
|
||||
[](uint32_t uses) { return uses == 1u; }),
|
||||
"shared-arm fixture already duplicated a semantic instruction");
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph),
|
||||
graph.unsupported_reason.c_str());
|
||||
graph = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
||||
original_coverage,
|
||||
"EXEC/SCC shared-arm structurization changed semantic coverage");
|
||||
@@ -8431,8 +8432,8 @@ void TestSharedReturnPreservesDescriptorDominance() {
|
||||
auto graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
||||
const auto original_coverage =
|
||||
CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph),
|
||||
graph.unsupported_reason.c_str());
|
||||
graph = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
||||
const auto *overwrite = graph.FindBlockByPc(0x10u);
|
||||
const auto *body = graph.FindBlockByPc(0x20u);
|
||||
Check(overwrite != nullptr && body != nullptr &&
|
||||
@@ -8464,6 +8465,224 @@ void TestSharedReturnPreservesDescriptorDominance() {
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNestedSelectionPreservesDescriptorSources() {
|
||||
constexpr uint32_t nested_arm = 12, direct_arm = 18, join = 21, end = 23;
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopc(0x07, 9, 128),
|
||||
EncodeSopp(0x04, end - 1 - 1),
|
||||
EncodeSopc(0x07, 8, 130),
|
||||
EncodeSopp(0x04, direct_arm - 3 - 1),
|
||||
EncodeSmem0(0x02, 16, 10), 125u << 25u,
|
||||
EncodeSopc(0x07, 8, 129),
|
||||
EncodeSopp(0x04, nested_arm - 7 - 1),
|
||||
EncodeSmem0(0x08, 24, 8), 125u << 25u,
|
||||
EncodeVop1(0x01, 0, 24),
|
||||
EncodeSopp(0x02, join - 11 - 1),
|
||||
EncodeSmem0(0x02, 16, 10), (125u << 25u) | 16u,
|
||||
EncodeSmem0(0x08, 24, 8), 125u << 25u,
|
||||
EncodeVop1(0x01, 0, 24),
|
||||
EncodeSopp(0x02, join - 17 - 1),
|
||||
EncodeSmem0(0x08, 24, 8), 125u << 25u,
|
||||
EncodeVop1(0x01, 0, 24),
|
||||
EncodeMubuf0(0x1c, 0, false), EncodeMubuf1(0, 3, 0),
|
||||
EncodeSopp(0x01),
|
||||
};
|
||||
ShaderRecompiler::Decoder::Program decoded;
|
||||
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
||||
auto graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
||||
const auto coverage = CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
Check(graph.blocks.size() == 8u &&
|
||||
std::ranges::all_of(coverage, [](uint32_t uses) { return uses == 1u; }),
|
||||
"nested descriptor fixture has the wrong native topology");
|
||||
graph = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == coverage,
|
||||
"nested descriptor selection changed semantic instruction coverage");
|
||||
|
||||
const std::array<uint32_t, 8> table{
|
||||
0x3000u, 0u, 4u, 3u << 28u, 0x5000u, 0u, 4u, 3u << 28u};
|
||||
std::array<uint32_t, 14> user_data{};
|
||||
user_data[1] = 1u;
|
||||
user_data[4] = 0x4000u;
|
||||
user_data[6] = 4u;
|
||||
user_data[7] = 3u << 28u;
|
||||
user_data[10] = 4u;
|
||||
user_data[11] = 3u << 28u;
|
||||
const auto address = reinterpret_cast<uint64_t>(table.data());
|
||||
user_data[12] = static_cast<uint32_t>(address);
|
||||
user_data[13] = static_cast<uint32_t>(address >> 32u);
|
||||
for (uint32_t mode = 0; mode != 3; ++mode) {
|
||||
user_data[0] = mode;
|
||||
user_data[8] = 0x2000u + mode * 0x10000u;
|
||||
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
options.user_data_base = 8;
|
||||
options.user_data = user_data;
|
||||
const auto result = RecompileForTest(shader, options, ReadHostTestMemory);
|
||||
Check(!result.program.dispatcher_fallback && result.resources.buffers.size() == 4u,
|
||||
"nested descriptor selection lost its structured resource bindings");
|
||||
for (size_t i = 0; i < result.program.info.buffers.size(); ++i) {
|
||||
const auto pc = result.program.info.buffers[i].first_use_pc;
|
||||
const uint32_t expected = pc == 8u * 4u ? table[0]
|
||||
: pc == 14u * 4u ? table[4]
|
||||
: pc == direct_arm * 4u ? user_data[8]
|
||||
: pc == join * 4u ? user_data[4] : 0u;
|
||||
Check(expected != 0u && result.resources.buffers[i].dwords[0] == expected,
|
||||
"nested descriptor selection chose another arm's descriptor");
|
||||
}
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
}
|
||||
|
||||
void TestSharedExitPreservesNativeDescriptorSources() {
|
||||
constexpr uint32_t selected = 8, later = 18, normal = 26, killed = 29;
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopc(0x07, 0, 128),
|
||||
EncodeSopp(0x04, normal - 1 - 1),
|
||||
EncodeSmem0(0x02, 8, 6), (125u << 25u) | 32u,
|
||||
EncodeSopc(0x07, 1, 128),
|
||||
EncodeSopp(0x04, selected - 5 - 1),
|
||||
EncodeSmem0(0x02, 8, 6), (125u << 25u) | 16u,
|
||||
EncodeSopc(0x07, 2, 128),
|
||||
EncodeSopp(0x04, later - 9 - 1),
|
||||
EncodeSmem0(0x02, 8, 6), (125u << 25u) | 32u,
|
||||
EncodeSopc(0x07, 3, 128),
|
||||
EncodeSopp(0x04, killed - 13 - 1),
|
||||
EncodeSmem0(0x08, 16, 4), 125u << 25u,
|
||||
EncodeVop1(0x01, 0, 16),
|
||||
EncodeSopp(0x02, normal - 17 - 1),
|
||||
EncodeSmem0(0x02, 8, 6), (125u << 25u) | 48u,
|
||||
EncodeSopc(0x07, 4, 128),
|
||||
EncodeSopp(0x04, killed - 21 - 1),
|
||||
EncodeSmem0(0x08, 16, 4), 125u << 25u,
|
||||
EncodeVop1(0x01, 0, 16),
|
||||
EncodeSopp(0x02, normal - 25 - 1),
|
||||
EncodeExp0(0x00, 0x1), EncodeExp1(0, 0, 0, 0),
|
||||
EncodeSopp(0x01),
|
||||
EncodeSopp(0x01),
|
||||
};
|
||||
ShaderRecompiler::Decoder::Program decoded;
|
||||
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
||||
auto graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
||||
const auto coverage = CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
Check(graph.blocks.size() == 10u &&
|
||||
std::ranges::all_of(coverage, [](uint32_t uses) { return uses == 1u; }),
|
||||
"shared-exit descriptor fixture has the wrong native topology");
|
||||
graph = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == coverage,
|
||||
"shared-exit descriptor selection changed semantic instruction coverage");
|
||||
|
||||
const std::array<uint32_t, 16> table{
|
||||
0x2000u, 0u, 4u, 3u << 28u, 0x3000u, 0u, 4u, 3u << 28u,
|
||||
0x4000u, 0u, 4u, 3u << 28u, 0x5000u, 0u, 4u, 3u << 28u};
|
||||
std::array<uint32_t, 14> user_data{};
|
||||
std::fill_n(user_data.begin(), 5, 1u);
|
||||
user_data[8] = 0x1000u;
|
||||
user_data[10] = 4u;
|
||||
user_data[11] = 3u << 28u;
|
||||
const auto address = reinterpret_cast<uint64_t>(table.data());
|
||||
user_data[12] = static_cast<uint32_t>(address);
|
||||
user_data[13] = static_cast<uint32_t>(address >> 32u);
|
||||
for (uint32_t arm = 0; arm != 2; ++arm) {
|
||||
user_data[2] = arm;
|
||||
auto options = MakeCompileOptions(ShaderType::Pixel);
|
||||
options.user_data = user_data;
|
||||
const auto result = RecompileForTest(shader, options, ReadHostTestMemory);
|
||||
Check(!result.program.dispatcher_fallback && result.resources.buffers.size() == 2u,
|
||||
"shared-exit descriptor selection lost its structured resource bindings");
|
||||
for (size_t i = 0; i < result.program.info.buffers.size(); ++i) {
|
||||
const auto pc = result.program.info.buffers[i].first_use_pc;
|
||||
const uint32_t expected = pc == 14u * 4u ? table[8]
|
||||
: pc == 22u * 4u ? table[12] : 0u;
|
||||
Check(expected != 0u && result.resources.buffers[i].dwords[0] == expected,
|
||||
"shared-exit descriptor selection retained an overwritten native source");
|
||||
}
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
}
|
||||
|
||||
void TestNativeGuardedSamplerSource() {
|
||||
constexpr uint32_t nested = 12, sample = 18, join = 20, end = 22;
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopc(0x07, 21, 128),
|
||||
EncodeSopp(0x04, end - 1 - 1),
|
||||
EncodeSopc(0x07, 20, 130),
|
||||
EncodeSopp(0x04, sample - 3 - 1),
|
||||
EncodeSMovB32(16, 133), EncodeSMovB32(17, 134),
|
||||
EncodeSMovB32(18, 135), EncodeSMovB32(19, 136),
|
||||
EncodeSopc(0x07, 20, 129),
|
||||
EncodeSopp(0x04, nested - 9 - 1),
|
||||
EncodeVop1(0x01, 0, 128),
|
||||
EncodeSopp(0x02, join - 11 - 1),
|
||||
EncodeSMovB32(16, 137), EncodeSMovB32(17, 138),
|
||||
EncodeSMovB32(18, 139), EncodeSMovB32(19, 140),
|
||||
EncodeVop1(0x01, 0, 129),
|
||||
EncodeSopp(0x02, join - 17 - 1),
|
||||
EncodeMimg0(0x20, 0x1), EncodeMimg1(0, 2, 4, 2),
|
||||
EncodeExp0(0x00, 0x1), EncodeExp1(0, 0, 0, 0),
|
||||
EncodeSopp(0x01),
|
||||
};
|
||||
auto user_data = ImageTestUserData();
|
||||
const std::array<uint32_t, 4> sampler{0u, 0u, 0x09500000u, 0u};
|
||||
std::ranges::copy(sampler, user_data.begin() + 8);
|
||||
user_data[13] = 1u;
|
||||
for (uint32_t mode = 0; mode != 3; ++mode) {
|
||||
user_data[12] = mode;
|
||||
auto options = MakeCompileOptions(ShaderType::Pixel);
|
||||
options.user_data_base = 8;
|
||||
options.user_data = user_data;
|
||||
const auto result = RecompileForTest(shader, options);
|
||||
const auto expected = mode == 2u ? sampler : std::array<uint32_t, 4>{};
|
||||
Check(!result.program.dispatcher_fallback && result.resources.samplers.size() == 1u &&
|
||||
result.resources.samplers[0].dword_count == expected.size() &&
|
||||
std::equal(expected.begin(), expected.end(), result.resources.samplers[0].dwords.begin()),
|
||||
"guarded native sample selected an unrelated arm's sampler constants");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
}
|
||||
|
||||
void TestNativeDescriptorProvenanceRejectsGpuSelection() {
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
const std::array<uint32_t, 13> selected{
|
||||
EncodeSmem0(0x02, 8, 6), 125u << 25u,
|
||||
EncodeVop1(0x01, 1, 128),
|
||||
EncodeVopc(0xc4, 256, 1),
|
||||
EncodeSopp(0x06, 2),
|
||||
EncodeSmem0(0x02, 8, 6), (125u << 25u) | 16u,
|
||||
EncodeSmem0(0x08, 16, 4), 125u << 25u,
|
||||
EncodeVop1(0x01, 2, 16),
|
||||
EncodeMubuf0(0x1c, 0, false), EncodeMubuf1(2, 0, 0),
|
||||
EncodeSopp(0x01),
|
||||
};
|
||||
auto partial = selected;
|
||||
partial[5] = EncodeVop1(0x02, 11, 256);
|
||||
partial[6] = EncodeSopp(0x00);
|
||||
const std::array<uint32_t, 13> loop{
|
||||
EncodeSmem0(0x02, 8, 6), 125u << 25u,
|
||||
EncodeSmem0(0x08, 16, 4), 125u << 25u,
|
||||
EncodeVop1(0x01, 2, 16),
|
||||
EncodeMubuf0(0x1c, 0, false), EncodeMubuf1(2, 0, 0),
|
||||
EncodeSmem0(0x02, 8, 6), (125u << 25u) | 16u,
|
||||
EncodeVop1(0x01, 1, 128),
|
||||
EncodeVopc(0xc4, 256, 1),
|
||||
EncodeSopp(0x07, 0xfff6u),
|
||||
EncodeSopp(0x01),
|
||||
};
|
||||
std::array<uint32_t, 14> user_data{};
|
||||
ShaderComputeInputInfo input_info{};
|
||||
input_info.thread_ids_num = 1;
|
||||
input_info.threads_num[0] = 64;
|
||||
input_info.threads_num[1] = input_info.threads_num[2] = 1;
|
||||
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
options.input_info.compute = &input_info;
|
||||
options.user_data = user_data;
|
||||
for (const auto &shader : {selected, partial, loop}) {
|
||||
ExpectFatal([&] { ShaderRecompiler::TranslateProgram(shader, options); },
|
||||
"native descriptor provenance accepted a GPU-selected or loop-carried value");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void TestCfgSiblingSharedExit() {
|
||||
// f7030726b9470dd8: a nested exit on one side and two exits on the
|
||||
// other side share an epilogue, separate from the normal return.
|
||||
@@ -8481,8 +8700,8 @@ void TestCfgSiblingSharedExit() {
|
||||
Decoder::Program decoded;
|
||||
Decoder::DecodeProgram(std::span{shader}, decoded);
|
||||
const auto original = CFG::BuildGraph(decoded);
|
||||
auto structured = original;
|
||||
Check(CFG::Structurize(structured), structured.unsupported_reason.c_str());
|
||||
auto structured = CFG::Structurize(original);
|
||||
Check(!structured.unsupported, structured.unsupported_reason.c_str());
|
||||
Check(CfgInstructionCoverage(structured, decoded.instructions.size()) ==
|
||||
CfgInstructionCoverage(original, decoded.instructions.size()),
|
||||
"shared sibling exit duplicated or dropped guest instructions");
|
||||
@@ -8564,8 +8783,8 @@ void TestNewShaderRecompilerCfgNestedTailEarlyExit() {
|
||||
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
||||
const auto original_coverage =
|
||||
CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph),
|
||||
graph.unsupported_reason.c_str());
|
||||
graph = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
||||
original_coverage,
|
||||
"nested-tail routing changed semantic instruction coverage");
|
||||
@@ -8603,8 +8822,8 @@ void TestNewShaderRecompilerCfgSharedReturnAfterNestedSelections() {
|
||||
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
||||
const auto original_coverage =
|
||||
CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph),
|
||||
graph.unsupported_reason.c_str());
|
||||
graph = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
||||
original_coverage,
|
||||
"shared-exit route ordering changed semantic instruction coverage");
|
||||
@@ -8641,7 +8860,8 @@ void TestNewShaderRecompilerCfgAlternatingSharedReturns() {
|
||||
auto graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
||||
const auto coverage = CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
Check(graph.blocks.size() == 5u, "alternating returns fixture has the wrong CFG");
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph), graph.unsupported_reason.c_str());
|
||||
graph = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == coverage,
|
||||
"alternating returns duplicated a terminal epilogue");
|
||||
|
||||
@@ -8680,8 +8900,8 @@ void TestNewShaderRecompilerCfgLoopSharedRegion() {
|
||||
CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
Check(graph.natural_loops.size() == 1u,
|
||||
"loop shared-region fixture has the wrong native CFG");
|
||||
const bool structured = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(structured, graph.unsupported_reason.c_str());
|
||||
graph = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
||||
const auto *loop_header = graph.FindBlockByPc(0x00u);
|
||||
Check(graph.natural_loops.size() == 1u && graph.back_edges.size() == 1u &&
|
||||
loop_header != nullptr && loop_header->terminator.loop_header &&
|
||||
@@ -8734,8 +8954,8 @@ void TestNewShaderRecompilerCfgSharedRegionBeforeEarlyBreakLoop() {
|
||||
CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
Check(graph.blocks.size() == 9u && graph.natural_loops.size() == 1u,
|
||||
"shared-region/early-break fixture has the wrong native CFG");
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph),
|
||||
graph.unsupported_reason.c_str());
|
||||
graph = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
||||
Check(graph.natural_loops.size() == 1u && graph.back_edges.size() == 1u,
|
||||
"selection routing introduced a cycle around a structured loop exit");
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
||||
@@ -8795,8 +9015,8 @@ void TestNewShaderRecompilerCfgOverlappingEarlyExitLadder() {
|
||||
const auto original_block_count = graph.blocks.size();
|
||||
const auto original_coverage =
|
||||
CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
const bool structured = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(structured, graph.unsupported_reason.c_str());
|
||||
graph = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
||||
original_coverage,
|
||||
"early-exit ladder routing changed semantic instruction coverage");
|
||||
@@ -8834,8 +9054,8 @@ void TestNewShaderRecompilerCfgNestedEarlyExitSharedTerminal() {
|
||||
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
||||
const auto original_coverage =
|
||||
CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
const bool structured = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(structured, graph.unsupported_reason.c_str());
|
||||
graph = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
||||
original_coverage,
|
||||
"nested early-exit structurization changed semantic coverage");
|
||||
@@ -8877,8 +9097,8 @@ void TestNewShaderRecompilerCfgEarlyReturnSharedLoopContinuation() {
|
||||
auto graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
||||
const auto original_coverage =
|
||||
CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph),
|
||||
graph.unsupported_reason.c_str());
|
||||
graph = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
||||
original_coverage &&
|
||||
graph.natural_loops.size() == 2u,
|
||||
@@ -8914,8 +9134,8 @@ void TestNewShaderRecompilerCfgSharedTerminalEarlyExit() {
|
||||
graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
||||
const auto original_coverage =
|
||||
CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph),
|
||||
graph.unsupported_reason.c_str());
|
||||
graph = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
||||
Check(
|
||||
CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
||||
original_coverage,
|
||||
@@ -8950,8 +9170,8 @@ void TestNewShaderRecompilerCfgPrunesUnreachableSelectionEntry() {
|
||||
CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
Check(original_coverage[1] == 0u,
|
||||
"CFG retained an unreachable external selection entry");
|
||||
Check(ShaderRecompiler::CFG::Structurize(graph),
|
||||
graph.unsupported_reason.c_str());
|
||||
graph = ShaderRecompiler::CFG::Structurize(graph);
|
||||
Check(!graph.unsupported, graph.unsupported_reason.c_str());
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
||||
original_coverage,
|
||||
"selection structurization changed reachable semantic code");
|
||||
@@ -8979,28 +9199,24 @@ void TestNewShaderRecompilerCfgFailedStructurizationPreservesGraph() {
|
||||
const auto original = graph;
|
||||
Check(graph.natural_loops.size() == 1u && !graph.irreducible,
|
||||
"failed structurization fixture must have one reducible loop");
|
||||
Check(!CFG::Structurize(graph) && graph.unsupported &&
|
||||
graph.failure_kind == CFG::FailureKind::StructuredControlFlow &&
|
||||
!graph.unsupported_reason.empty(),
|
||||
const auto failed = CFG::Structurize(graph);
|
||||
Check(failed.unsupported &&
|
||||
failed.failure_kind == CFG::FailureKind::StructuredControlFlow &&
|
||||
!failed.unsupported_reason.empty(),
|
||||
"loop without a merge did not retain its structurization failure");
|
||||
Check(graph.failure_block == UINT32_MAX,
|
||||
Check(failed.failure_block == UINT32_MAX,
|
||||
"failed structurization exposed a discarded synthetic block ID");
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
||||
CfgInstructionCoverage(original, decoded.instructions.size()),
|
||||
"failed structurization changed instruction coverage");
|
||||
auto topology = graph;
|
||||
topology.unsupported = original.unsupported;
|
||||
topology.failure_kind = original.failure_kind;
|
||||
topology.failure_block = original.failure_block;
|
||||
topology.unsupported_reason = original.unsupported_reason;
|
||||
Check(CFG::GraphToString(topology) == CFG::GraphToString(original),
|
||||
Check(CFG::GraphToString(graph) == CFG::GraphToString(original),
|
||||
"failed structurization changed CFG topology or analyses");
|
||||
|
||||
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
auto result = RecompileForTest(shader, options);
|
||||
Check(result.program.dispatcher_fallback &&
|
||||
result.program.cfg_failure_kind == graph.failure_kind &&
|
||||
result.program.fallback_reason == graph.unsupported_reason,
|
||||
result.program.cfg_failure_kind == failed.failure_kind &&
|
||||
result.program.fallback_reason == failed.unsupported_reason,
|
||||
"compiler did not consume CFG failure diagnostics directly");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
@@ -12231,7 +12447,13 @@ void TestTypedDescriptorRealWideMoveTranslation() {
|
||||
};
|
||||
ShaderRecompiler::IR::Program ir;
|
||||
BuildTypedPlan(shader, static_cast<uint32_t>(std::size(shader)), ir);
|
||||
ShaderRecompiler::IR::TrackResources(ir);
|
||||
for (uint32_t reg = 0; reg < 4; ++reg) {
|
||||
Check(std::ranges::any_of(ir.scalar_writes, [&](const auto &write) {
|
||||
return write.pc == (reg / 2u) * 4u &&
|
||||
ShaderRecompiler::IR::RegIndex(write.reg) == reg;
|
||||
}), "wide scalar move lost a native descriptor component write");
|
||||
}
|
||||
ShaderRecompiler::IR::TrackResources(ir, {}, {});
|
||||
Check(ir.info.buffers.size() == 1,
|
||||
"real wide-move shader did not track one buffer use");
|
||||
const auto *source = TypedDescriptorSource(ir, ir.info.buffers[0].source);
|
||||
@@ -12421,7 +12643,7 @@ void TestTypedDescriptorRealCarryAndScalarLoads() {
|
||||
}
|
||||
Check(address_reads == 4 && buffer_reads == 4,
|
||||
"real scalar loads used the wrong raw typed operations");
|
||||
ShaderRecompiler::IR::TrackResources(load_ir);
|
||||
ShaderRecompiler::IR::TrackResources(load_ir, {}, {});
|
||||
Check(load_ir.info.buffers.size() == 2,
|
||||
"real scalar-load descriptor sources were not attached");
|
||||
for (const auto &buffer : load_ir.info.buffers) {
|
||||
@@ -12452,7 +12674,7 @@ void TestTypedDescriptorRealCarryAndScalarLoads() {
|
||||
BuildTypedPlan(inline_sampler_shader,
|
||||
static_cast<uint32_t>(std::size(inline_sampler_shader)),
|
||||
inline_sampler_ir);
|
||||
ShaderRecompiler::IR::TrackResources(inline_sampler_ir);
|
||||
ShaderRecompiler::IR::TrackResources(inline_sampler_ir, {}, {});
|
||||
ShaderRecompiler::IR::DescriptorValue sampler;
|
||||
ShaderRecompiler::IR::SrtRuntime runtime;
|
||||
Check(inline_sampler_ir.info.samplers.size() == 1 &&
|
||||
@@ -12643,7 +12865,7 @@ void TestScalarMemoryLoadCrossesIntoVcc() {
|
||||
"wide SMEM destination crossing into VCC lost scalar provenance");
|
||||
CheckFlattenedReadSlots(
|
||||
ir, 4, "wide SMEM destination crossing into VCC used wrong flat offsets");
|
||||
ShaderRecompiler::IR::TrackResources(ir);
|
||||
ShaderRecompiler::IR::TrackResources(ir, {}, {});
|
||||
Check(ir.info.buffers.size() == 1,
|
||||
"wide SMEM destination crossing into VCC lost its buffer use");
|
||||
const auto *source =
|
||||
@@ -12705,7 +12927,7 @@ void TestResourceTrackingRealDensePatching() {
|
||||
};
|
||||
ShaderRecompiler::IR::Program ir;
|
||||
BuildTypedPlan(shader, static_cast<uint32_t>(std::size(shader)), ir);
|
||||
ShaderRecompiler::IR::TrackResources(ir);
|
||||
ShaderRecompiler::IR::TrackResources(ir, {}, {});
|
||||
Check(ir.info.buffers.size() == 2 && ir.info.images.size() == 2 &&
|
||||
ir.info.samplers.size() == 1,
|
||||
"real resource tracking produced the wrong dense list sizes");
|
||||
@@ -12772,7 +12994,7 @@ void TestDirectTranslationResetsAnalysisState() {
|
||||
ShaderRecompiler::IR::Program ir;
|
||||
BuildTypedPlan(first_shader, static_cast<uint32_t>(std::size(first_shader)),
|
||||
ir);
|
||||
ShaderRecompiler::IR::TrackResources(ir);
|
||||
ShaderRecompiler::IR::TrackResources(ir, {}, {});
|
||||
ShaderComputeInputInfo compute;
|
||||
ShaderRecompiler::IR::CollectShaderInfo(ir, {.compute = &compute});
|
||||
Check(ir.resource_tracking_complete && ir.shader_info_complete &&
|
||||
@@ -13648,6 +13870,10 @@ int main() {
|
||||
TestNewShaderRecompilerCfgNestedEarlyExitLoopForwarders();
|
||||
TestNewShaderRecompilerCfgExecSccSharedArm();
|
||||
TestSharedReturnPreservesDescriptorDominance();
|
||||
TestSharedExitPreservesNativeDescriptorSources();
|
||||
TestNestedSelectionPreservesDescriptorSources();
|
||||
TestNativeGuardedSamplerSource();
|
||||
TestNativeDescriptorProvenanceRejectsGpuSelection();
|
||||
TestCfgSiblingSharedExit();
|
||||
TestNewShaderRecompilerCfgNestedTailEarlyExit();
|
||||
TestNewShaderRecompilerCfgSharedReturnAfterNestedSelections();
|
||||
|
||||
Reference in New Issue
Block a user