mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-26 21:06:53 +01:00
ce5e57aa36
Co-authored-by: Senaxx <michelbeld@gmail.com> Co-authored-by: Mac <me@itsmemac.com>
244 lines
8.0 KiB
C++
244 lines
8.0 KiB
C++
#include "graphics/shader/recompiler/ir/passes/ReadLaneElimination.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include <queue>
|
|
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
namespace Libs::Graphics::ShaderRecompiler::IR {
|
|
namespace {
|
|
|
|
struct ChainResult {
|
|
Value value;
|
|
Inst* write = nullptr;
|
|
};
|
|
|
|
ChainResult SearchChain(Value value, uint32_t lane, uint32_t wave_size) {
|
|
for (;;) {
|
|
value = value.Resolve();
|
|
auto* inst = value.TryInstruction();
|
|
if (inst == nullptr || inst->GetOpcode() != ValueOpcode::WriteLane) {
|
|
return {value};
|
|
}
|
|
const auto selector = inst->Arg(2).Resolve();
|
|
if (!selector.IsImmediate() || selector.GetType() != Type::U32) {
|
|
return {value};
|
|
}
|
|
if (selector.U32() % wave_size == lane) {
|
|
return {value, inst};
|
|
}
|
|
value = inst->Arg(0);
|
|
}
|
|
}
|
|
|
|
bool IsPossibleToEliminate(Value source, uint32_t lane, uint32_t wave_size) {
|
|
std::queue<Value> queue;
|
|
std::unordered_set<Inst*> visited;
|
|
queue.push(source);
|
|
|
|
while (!queue.empty()) {
|
|
const auto chain = SearchChain(queue.front(), lane, wave_size);
|
|
queue.pop();
|
|
if (chain.write != nullptr) {
|
|
continue;
|
|
}
|
|
auto* inst = chain.value.TryInstruction();
|
|
if (inst == nullptr || inst->GetOpcode() != ValueOpcode::Phi || inst->NumArgs() == 0) {
|
|
return false;
|
|
}
|
|
if (!visited.insert(inst).second) {
|
|
continue;
|
|
}
|
|
for (size_t index = inst->NumArgs(); index-- > 0;) {
|
|
queue.push(inst->Arg(index));
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
using PhiMap = std::unordered_map<Inst*, Inst*>;
|
|
|
|
Value GetRealValue(PhiMap& phi_map, Value source, uint32_t lane, uint32_t wave_size) {
|
|
const auto chain = SearchChain(source, lane, wave_size);
|
|
if (chain.write != nullptr) {
|
|
return chain.write->Arg(1);
|
|
}
|
|
|
|
auto* inst = chain.value.ResolveInstruction();
|
|
EXIT_IF(inst->GetOpcode() != ValueOpcode::Phi);
|
|
const auto [entry, is_new] = phi_map.try_emplace(inst);
|
|
if (!is_new) {
|
|
return Value(entry->second);
|
|
}
|
|
|
|
auto* block = inst->Parent();
|
|
auto insertion_point = std::find_if(block->begin(), block->end(),
|
|
[&](const Inst& candidate) { return &candidate == inst; });
|
|
EXIT_IF(insertion_point == block->end());
|
|
auto& phi = *block->PrependNewInst(insertion_point, ValueOpcode::Phi);
|
|
phi.SetFlags(Type::U32);
|
|
entry->second = φ
|
|
|
|
std::vector<Value> arguments;
|
|
arguments.reserve(inst->NumArgs());
|
|
for (size_t index = 0; index < inst->NumArgs(); index++) {
|
|
arguments.push_back(GetRealValue(phi_map, inst->Arg(index), lane, wave_size));
|
|
}
|
|
const auto first = arguments.front().Resolve();
|
|
if (std::ranges::all_of(arguments,
|
|
[&](Value argument) { return argument.Resolve() == first; })) {
|
|
phi.ReplaceUsesWith(first);
|
|
} else {
|
|
for (size_t index = 0; index < arguments.size(); index++) {
|
|
phi.AddPhiOperand(inst->PhiBlock(index), arguments[index]);
|
|
}
|
|
}
|
|
return Value(&phi);
|
|
}
|
|
|
|
// A full-EXEC prefix scan with both half-wave endpoint reads is a reduction. Wave64 first joins
|
|
// the two rows of each half; a wave32 half is one row.
|
|
// Fragment subgroups may have no physical invocation at either endpoint.
|
|
class WaveReductionMatcher {
|
|
public:
|
|
WaveReductionMatcher(const Block* block, ValueOpcode operation)
|
|
: block(block), operation(operation) {}
|
|
|
|
Value Match(const Inst& reduction, uint32_t wave_size) const {
|
|
const auto* low = Get(reduction.Arg(0), ValueOpcode::ReadLane);
|
|
const auto* high = Get(reduction.Arg(1), ValueOpcode::ReadLane);
|
|
if (low == nullptr || high == nullptr || low->Arg(0).Resolve() != high->Arg(0).Resolve()) {
|
|
return {};
|
|
}
|
|
const auto lane = [&](Value value, uint32_t expected) {
|
|
value = value.Resolve();
|
|
return value.IsImmediate() && value.GetType() == Type::U32 &&
|
|
value.U32() % wave_size == expected;
|
|
};
|
|
const auto half = wave_size / 2u;
|
|
if (!((lane(low->Arg(1), half - 1u) && lane(high->Arg(1), wave_size - 1u)) ||
|
|
(lane(low->Arg(1), wave_size - 1u) && lane(high->Arg(1), half - 1u)))) {
|
|
return {};
|
|
}
|
|
if (wave_size == 32u) return MatchScan(low->Arg(0));
|
|
const auto* joined = Get(low->Arg(0), operation);
|
|
if (joined == nullptr) return {};
|
|
for (size_t index = 0; index < 2; index++) {
|
|
const auto* cross = Get(joined->Arg(index), ValueOpcode::Permlane16U32);
|
|
if (cross == nullptr) continue;
|
|
const auto flags = cross->Flags<PermlaneFlags>();
|
|
const auto prefix = joined->Arg(1 - index).Resolve();
|
|
if (!flags.x16 || flags.fetch_inactive || !flags.bound_control ||
|
|
cross->Arg(1).Resolve() != Value(UINT32_MAX) ||
|
|
cross->Arg(2).Resolve() != Value(UINT32_MAX) || !FullExec(cross->Arg(3)) ||
|
|
cross->Arg(0).Resolve() != prefix) {
|
|
continue;
|
|
}
|
|
if (const auto seed = MatchScan(prefix); !seed.IsEmpty()) return seed;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
private:
|
|
// The row scan's input.
|
|
Value MatchScan(Value prefix) const {
|
|
for (const uint32_t shift: {8u, 4u, 2u, 1u}) {
|
|
prefix = MatchRow(prefix, shift);
|
|
}
|
|
return prefix;
|
|
}
|
|
|
|
Inst* Get(Value value, ValueOpcode opcode) const {
|
|
auto* inst = value.Resolve().TryInstruction();
|
|
return inst != nullptr && inst->Parent() == block && inst->GetOpcode() == opcode
|
|
? inst : nullptr;
|
|
}
|
|
|
|
// Constant propagation folds the x | ~x EXEC of s_orn2_saveexec to true.
|
|
static bool FullExec(Value predicate) { return predicate.Resolve() == Value(true); }
|
|
|
|
static bool IsRowShift(const Inst& inst, uint32_t shift) {
|
|
const auto flags = inst.Flags<DppMoveFlags>();
|
|
return flags.control == 0x110u + shift && flags.row_mask == 0xf &&
|
|
flags.bank_mask == 0xf && !flags.fetch_inactive && !flags.bound_control && !flags.dpp8;
|
|
}
|
|
|
|
Value MatchRow(Value value, uint32_t shift) const {
|
|
const auto* update = Get(value, ValueOpcode::DppUpdateU32);
|
|
if (update == nullptr || !IsRowShift(*update, shift) || !FullExec(update->Arg(2))) return {};
|
|
const auto* combined = Get(update->Arg(0), operation);
|
|
if (combined == nullptr) return {};
|
|
for (size_t index = 0; index < 2; index++) {
|
|
const auto* move = Get(combined->Arg(index), ValueOpcode::DppMoveU32);
|
|
if (move == nullptr || !IsRowShift(*move, shift) || !FullExec(move->Arg(1))) continue;
|
|
const auto previous = combined->Arg(1 - index).Resolve();
|
|
if (move->Arg(0).Resolve() == previous && update->Arg(1).Resolve() == previous) {
|
|
return previous;
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
const Block* block;
|
|
ValueOpcode operation;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
ReadLaneStats EliminateReadLane(Program& program, uint32_t wave_size) {
|
|
ReadLaneStats stats;
|
|
if (wave_size != 32u && wave_size != 64u) {
|
|
return stats;
|
|
}
|
|
|
|
for (auto* block: program.blocks) {
|
|
for (auto& inst: *block) {
|
|
const auto opcode = inst.GetOpcode();
|
|
if (opcode == ValueOpcode::UMin32 || opcode == ValueOpcode::UMax32) {
|
|
const auto seed = WaveReductionMatcher(block, opcode).Match(inst, wave_size);
|
|
if (!seed.IsEmpty()) {
|
|
const auto position = std::find_if(block->begin(), block->end(),
|
|
[&](const Inst& candidate) { return &candidate == &inst; });
|
|
const auto reduce = opcode == ValueOpcode::UMin32
|
|
? ValueOpcode::SubgroupUMinU32
|
|
: ValueOpcode::SubgroupUMaxU32;
|
|
auto& reduction = *block->PrependNewInst(position, reduce, {seed});
|
|
inst.ReplaceUsesWith(Value(&reduction));
|
|
stats.rewritten_reductions++;
|
|
}
|
|
continue;
|
|
}
|
|
if (opcode != ValueOpcode::ReadLane) {
|
|
continue;
|
|
}
|
|
const auto selector = inst.Arg(1).Resolve();
|
|
if (!selector.IsImmediate() || selector.GetType() != Type::U32) {
|
|
continue;
|
|
}
|
|
|
|
const auto lane = selector.U32() % wave_size;
|
|
const auto chain = SearchChain(inst.Arg(0), lane, wave_size);
|
|
if (chain.write != nullptr) {
|
|
inst.ReplaceUsesWith(chain.write->Arg(1));
|
|
stats.rewritten_reads++;
|
|
continue;
|
|
}
|
|
auto* producer = chain.value.TryInstruction();
|
|
if (producer == nullptr || producer->GetOpcode() != ValueOpcode::Phi ||
|
|
!IsPossibleToEliminate(chain.value, lane, wave_size)) {
|
|
continue;
|
|
}
|
|
|
|
PhiMap phi_map;
|
|
inst.ReplaceUsesWith(GetRealValue(phi_map, chain.value, lane, wave_size));
|
|
stats.rewritten_reads++;
|
|
}
|
|
}
|
|
return stats;
|
|
}
|
|
|
|
} // namespace Libs::Graphics::ShaderRecompiler::IR
|