mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-19 18:36:53 +02:00
shader: factor live execution guards before resource planning
This commit is contained in:
@@ -789,19 +789,20 @@ TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileO
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
static_cast<uint64_t>(ir.blocks.size()), phase_ms());
|
||||
IR::RewriteToSsa(ir.blocks);
|
||||
IR::ConstantPropagationPass(ir.blocks, ir.wave_size);
|
||||
IR::ResolveControlFlowIdentities(ir);
|
||||
IR::RemoveIdentities(ir.blocks);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
const auto read_lane_stats = IR::EliminateReadLane(ir, ir.wave_size);
|
||||
if (read_lane_stats.rewritten_reads != 0) {
|
||||
LOGF("%s read-lane elimination: reads=%" PRIu32 "\n", GetDumpLabel(options),
|
||||
read_lane_stats.rewritten_reads);
|
||||
const auto simplify = [&ir] {
|
||||
IR::ConstantPropagationPass(ir.blocks, ir.wave_size);
|
||||
IR::ResolveControlFlowIdentities(ir);
|
||||
IR::RemoveIdentities(ir.blocks);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
};
|
||||
simplify();
|
||||
const auto read_lane_stats = IR::EliminateReadLane(ir, ir.wave_size);
|
||||
if (read_lane_stats.rewritten_reads != 0) {
|
||||
LOGF("%s read-lane elimination: reads=%" PRIu32 "\n", GetDumpLabel(options),
|
||||
read_lane_stats.rewritten_reads);
|
||||
}
|
||||
// Cleanup also exposes profitable folds by removing dead predicate consumers.
|
||||
simplify();
|
||||
if (decoded.has_swap_pc) {
|
||||
// Leaf returns are resolved CFG edges; native PC values must not escape into GPU data.
|
||||
for (const auto* block: ir.blocks)
|
||||
|
||||
@@ -22,6 +22,13 @@ void Replace(Inst& inst, Value value) {
|
||||
inst.ReplaceUsesWith(value.Resolve());
|
||||
}
|
||||
|
||||
bool HasOneUse(const Inst& inst) {
|
||||
// Replacement aliases preserve external Values but emit no additional operation.
|
||||
return std::ranges::count_if(inst.Uses(), [](const Use& use) {
|
||||
return use.user->GetOpcode() != ValueOpcode::Identity;
|
||||
}) == 1;
|
||||
}
|
||||
|
||||
class FoldWorklist {
|
||||
public:
|
||||
struct Location {
|
||||
@@ -744,7 +751,7 @@ void FoldInstruction(Block& block, Block::iterator instruction,
|
||||
if (inverse) {
|
||||
test = Arg(*test, 0).TryInstruction();
|
||||
if (test == nullptr) return false;
|
||||
if (test->GetOpcode() == ValueOpcode::LogicalAnd)
|
||||
if (test->GetOpcode() == ValueOpcode::LogicalAnd && HasOneUse(*test))
|
||||
for (uint32_t arg = 0; arg < 2u; ++arg)
|
||||
if (Arg(*test, arg) == assumption)
|
||||
return replace(negate(Arg(*test, arg ^ 1u)));
|
||||
@@ -786,6 +793,25 @@ void FoldInstruction(Block& block, Block::iterator instruction,
|
||||
Replace(inst, lhs.U1() ? lhs : rhs);
|
||||
} else if (IsImmediate(rhs, Type::U1)) {
|
||||
Replace(inst, rhs.U1() ? rhs : lhs);
|
||||
} else {
|
||||
const auto* left = lhs.TryInstruction();
|
||||
const auto* right = rhs.TryInstruction();
|
||||
if (left == nullptr || right == nullptr ||
|
||||
left->GetOpcode() != ValueOpcode::LogicalAnd ||
|
||||
right->GetOpcode() != ValueOpcode::LogicalAnd ||
|
||||
(!HasOneUse(*left) && !HasOneUse(*right))) return;
|
||||
// Factor a shared execution predicate without retaining more operations.
|
||||
for (uint32_t a = 0; a < 2u; ++a) {
|
||||
for (uint32_t b = 0; b < 2u; ++b) {
|
||||
const auto common = Arg(*left, a);
|
||||
if (common != Arg(*right, b)) continue;
|
||||
const auto either = Value(&*worklist.Prepend(block, instruction,
|
||||
ValueOpcode::LogicalOr, {Arg(*left, a ^ 1u), Arg(*right, b ^ 1u)}));
|
||||
Replace(inst, Value(&*worklist.Prepend(block, instruction,
|
||||
ValueOpcode::LogicalAnd, {common, either})));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -566,10 +566,11 @@ void TestGuardedDirectImageTable() {
|
||||
Bounded, BoundMissing, BoundUnrelated, BoundBypass, BoundAnyLane,
|
||||
BoundDiamond, BoundBranchSpecific, BoundMismatched,
|
||||
BoundExec, BoundExecUnguarded, BoundExecMismatched, BoundExecAnd, BoundExecUnprovenActive,
|
||||
BoundExecFactored,
|
||||
Finite, FiniteZero, FiniteMissing, FiniteWrong, FiniteSigned, FiniteAdd, FiniteXor,
|
||||
FiniteShared, FiniteSharedBypass, FiniteLate
|
||||
};
|
||||
const auto make_plan = [](Guard guard) {
|
||||
const auto make_plan = [](Guard guard, uint32_t operand_order = 0u) {
|
||||
const bool finite = guard >= Guard::Finite;
|
||||
const bool bounded = guard >= Guard::Bounded && !finite;
|
||||
const bool exec_bound = bounded && guard >= Guard::BoundExec;
|
||||
@@ -650,8 +651,19 @@ void TestGuardedDirectImageTable() {
|
||||
? ValueOpcode::BitwiseAnd32 : ValueOpcode::BitwiseOr32, {encoded, varying});
|
||||
const auto retained = fixture.Emit(ValueOpcode::SelectU32,
|
||||
{previous, stopped, Value(0u)});
|
||||
const auto vcc = fixture.Emit(ValueOpcode::LogicalAnd, {previous,
|
||||
fixture.Emit(ValueOpcode::INotEqual32, {retained, Value(0u)})});
|
||||
const auto stopped_lane = fixture.Emit(ValueOpcode::INotEqual32, {retained, Value(0u)});
|
||||
auto vcc = fixture.Emit(ValueOpcode::LogicalAnd,
|
||||
operand_order & 1u ? std::initializer_list<Value>{stopped_lane, previous}
|
||||
: std::initializer_list<Value>{previous, stopped_lane});
|
||||
if (guard == Guard::BoundExecFactored) {
|
||||
const auto empty = fixture.Emit(ValueOpcode::IEqual32, {key, Value(UINT32_MAX)});
|
||||
const auto sentinel = fixture.Emit(ValueOpcode::LogicalAnd,
|
||||
operand_order & 2u ? std::initializer_list<Value>{empty, previous}
|
||||
: std::initializer_list<Value>{previous, empty});
|
||||
vcc = fixture.Emit(ValueOpcode::LogicalOr,
|
||||
operand_order & 4u ? std::initializer_list<Value>{sentinel, vcc}
|
||||
: std::initializer_list<Value>{vcc, sentinel});
|
||||
}
|
||||
const auto remaining = fixture.Emit(ValueOpcode::LogicalNot, {vcc});
|
||||
active = guard == Guard::BoundExecUnprovenActive ? remaining
|
||||
: fixture.Emit(ValueOpcode::LogicalAnd, {previous, remaining});
|
||||
@@ -799,10 +811,12 @@ void TestGuardedDirectImageTable() {
|
||||
fixture.AddMemory(memory, 0x130));
|
||||
}
|
||||
if (exec_bound) ConstantPropagationPass(fixture.program.blocks);
|
||||
if (guard == Guard::BoundExec) {
|
||||
if (guard == Guard::BoundExec || guard == Guard::BoundExecFactored) {
|
||||
std::vector<const Inst *> pending{active.ResolveInstruction()};
|
||||
uint32_t conjunctions = 0;
|
||||
for (size_t index = 0; index < pending.size(); ++index) {
|
||||
const auto *inst = pending[index];
|
||||
conjunctions += inst->GetOpcode() == ValueOpcode::LogicalAnd;
|
||||
Check(inst->GetOpcode() != ValueOpcode::SelectU32 &&
|
||||
inst->GetOpcode() != ValueOpcode::BitwiseOr32,
|
||||
"EXEC bound retained its integer Boolean encoding");
|
||||
@@ -812,6 +826,7 @@ void TestGuardedDirectImageTable() {
|
||||
pending.push_back(operand);
|
||||
}
|
||||
}
|
||||
Check(conjunctions == 1u, "EXEC bound retained repeated active-lane conjunctions");
|
||||
}
|
||||
fixture.PlanAndTrack();
|
||||
const auto source = fixture.program.info.images[0].source;
|
||||
@@ -829,6 +844,7 @@ void TestGuardedDirectImageTable() {
|
||||
auto plan = make_plan(Guard::Nonzero);
|
||||
make_plan(Guard::SccNonZero);
|
||||
make_plan(Guard::Plain);
|
||||
for (uint32_t order = 0; order < 8u; ++order) make_plan(Guard::BoundExecFactored, order);
|
||||
for (const auto guard : {Guard::Zero, Guard::Unrelated, Guard::Bypass,
|
||||
Guard::ExecZero, Guard::VccZero, Guard::BoundMissing,
|
||||
Guard::BoundUnrelated, Guard::BoundBypass, Guard::BoundAnyLane,
|
||||
@@ -914,13 +930,14 @@ void TestGuardedDirectImageTable() {
|
||||
"batched descriptor read crossed the 48-bit endpoint");
|
||||
|
||||
make_plan(Guard::Bounded);
|
||||
for (const auto guard : {Guard::BoundDiamond, Guard::BoundExec}) {
|
||||
for (const auto guard : {Guard::BoundDiamond, Guard::BoundExec, Guard::BoundExecFactored}) {
|
||||
auto bounded = make_plan(guard);
|
||||
Check(bounded.capture_specialization_reads && bounded.info.images[1].written,
|
||||
"bounded table with image writes omitted specialization read capture");
|
||||
LinearTestMemory records;
|
||||
const uint32_t record_offset = guard == Guard::BoundExec ? 672u : 3696u;
|
||||
const uint32_t record_stride = guard == Guard::BoundExec ? 784u : 272u;
|
||||
const bool exec_bound = guard != Guard::BoundDiamond;
|
||||
const uint32_t record_offset = exec_bound ? 672u : 3696u;
|
||||
const uint32_t record_stride = exec_bound ? 784u : 272u;
|
||||
for (uint32_t key = 0; key < 2u; ++key) {
|
||||
const auto word = (record_offset + key * record_stride) / 4u;
|
||||
records.words[word] = 0x500u + key;
|
||||
|
||||
@@ -36569,8 +36569,11 @@ void CheckGuardedRecordImageTable(VulkanHarness &vulkan) {
|
||||
EncodeSop1(0x04, 126, 28)});
|
||||
code.push_back(EncodeSopc(0x09, 22, 21));
|
||||
if (masked) code.insert(code.end(), {
|
||||
EncodeVopc(0xc2, 249, 22), // Mask the FF1 sentinel into s2:s3.
|
||||
EncodeVopcSdwa(193, 2, 1, 6, 6, 0, 0, 0, 0, 0, 0, 1, 1),
|
||||
EncodeVop2(0x1c, 10, 253, 13), // SCC encoded as 0/1, OR per-lane done.
|
||||
EncodeVopc(0xc5, InlineU32(0), 10),
|
||||
EncodeSop2(0x11, 106, 106, 2),
|
||||
EncodeSop2(0x15, 126, 28, 106)});
|
||||
range_exit = code.size(); code.push_back(0u);
|
||||
}
|
||||
@@ -36621,7 +36624,8 @@ void CheckGuardedRecordImageTable(VulkanHarness &vulkan) {
|
||||
else test.opcodes.insert(test.opcodes.end(), {O::S_CMP_GE_U32, O::S_CBRANCH_SCC1,
|
||||
O::S_BITCMP1_B32});
|
||||
if (masked) test.opcodes.insert(test.opcodes.end(), {O::S_CSELECT_B64,
|
||||
O::V_OR_B32, O::V_CMP_NE_U32, O::S_ANDN2_B64, O::S_CBRANCH_EXECZ});
|
||||
O::V_CMP_EQ_U32, O::V_OR_B32, O::V_CMP_NE_U32, O::S_OR_B64,
|
||||
O::S_ANDN2_B64, O::S_CBRANCH_EXECZ});
|
||||
test.opcodes.insert(test.opcodes.end(), {O::S_MOV_B64, O::V_ADD_NC_U32});
|
||||
auto compiled = CompileCase(test, vulkan.SubgroupSize());
|
||||
const auto sampled_count = std::ranges::count_if(compiled.program.info.images,
|
||||
|
||||
Reference in New Issue
Block a user