#include "graphics/shader/recompiler/ir/passes/ReadLaneElimination.h" #include #include #include #include #include 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 queue; std::unordered_set 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; 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 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); } } // 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) { if (inst.GetOpcode() != 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