shader_recompiler: add SSA

This commit is contained in:
nmzik committed 2026-08-18 03:53:41 +02:00
1 parent bc2f077c37
commit b42380ced6
113 files changed
+40415 -32616

No files matched your search

@@ -0,0 +1,84 @@
#include "graphics/shader/recompiler/ir/Block.h"
#include <algorithm>
#include <limits>
namespace Libs::Graphics::ShaderRecompiler::IR {
Block::~Block() {
// Instructions own reverse-use links. Unlink every argument while all definitions are still
// alive, then let the list destroy the detached instructions.
for (auto& inst: instructions) {
inst.Invalidate();
}
}
Inst& Block::AppendNewInst(ValueOpcode opcode, std::initializer_list<Value> args, uint64_t flags) {
return *PrependNewInst(end(), opcode, args, flags);
}
Block::iterator Block::PrependNewInst(iterator insertion_point, ValueOpcode opcode,
std::initializer_list<Value> args, uint64_t flags) {
const auto expected = NumArgsOf(opcode);
EXIT_IF(expected != std::numeric_limits<size_t>::max() && expected != args.size());
const auto inst = instructions.emplace(insertion_point, opcode, flags);
inst->SetParent(this);
size_t index = 0;
for (const auto arg: args) {
inst->SetArg(index++, arg);
}
return inst;
}
void Block::AddBranch(Block* block) {
EXIT_IF(block == nullptr || std::ranges::find(successors, block) != successors.end());
EXIT_IF(std::ranges::find(block->predecessors, this) != block->predecessors.end());
successors.push_back(block);
block->predecessors.push_back(this);
}
Block::InstructionList& Block::Instructions() {
return instructions;
}
const Block::InstructionList& Block::Instructions() const {
return instructions;
}
std::span<Block* const> Block::ImmPredecessors() const {
return predecessors;
}
std::span<Block* const> Block::ImmSuccessors() const {
return successors;
}
void Block::SsaSeal() {
ssa_sealed = true;
}
bool Block::IsSsaSealed() const {
return ssa_sealed;
}
Block::iterator Block::begin() {
return instructions.begin();
}
Block::const_iterator Block::begin() const {
return instructions.begin();
}
Block::iterator Block::end() {
return instructions.end();
}
Block::const_iterator Block::end() const {
return instructions.end();
}
bool Block::empty() const {
return instructions.empty();
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
+63
View File
@@ -0,0 +1,63 @@
#pragma once
#include "graphics/shader/recompiler/ir/Reg.h"
#include "graphics/shader/recompiler/ir/Value.h"
#include <array>
#include <initializer_list>
#include <list>
#include <span>
#include <vector>
namespace Libs::Graphics::ShaderRecompiler::IR {
// A standard list provides stable instruction addresses without adding an intrusive-container
// dependency.
class Block {
public:
using InstructionList = std::list<Inst>;
using iterator = InstructionList::iterator;
using const_iterator = InstructionList::const_iterator;
Block() = default;
~Block();
Block(const Block&) = delete;
Block& operator=(const Block&) = delete;
Block(Block&&) = delete;
Block& operator=(Block&&) = delete;
Inst& AppendNewInst(ValueOpcode opcode, std::initializer_list<Value> args = {},
uint64_t flags = 0);
iterator PrependNewInst(iterator insertion_point, ValueOpcode opcode,
std::initializer_list<Value> args = {}, uint64_t flags = 0);
void AddBranch(Block* block);
[[nodiscard]] InstructionList& Instructions();
[[nodiscard]] const InstructionList& Instructions() const;
[[nodiscard]] std::span<Block* const> ImmPredecessors() const;
[[nodiscard]] std::span<Block* const> ImmSuccessors() const;
void SsaSeal();
[[nodiscard]] bool IsSsaSealed() const;
iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;
bool empty() const;
std::array<Value, NumScalarRegs> ssa_sreg_values {};
std::array<Value, NumScalarRegs> ssa_thread_bit_sreg_values {};
std::array<Value, NumVectorRegs> ssa_vreg_values {};
private:
InstructionList instructions;
std::vector<Block*> predecessors;
std::vector<Block*> successors;
bool ssa_sealed = false;
};
using BlockList = std::vector<Block*>;
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -0,0 +1,218 @@
#include "graphics/shader/recompiler/ir/IREmitter.h"
namespace Libs::Graphics::ShaderRecompiler::IR {
IREmitter::IREmitter(Block* value_block): block(value_block) {
EXIT_IF(block == nullptr);
}
void IREmitter::SetBlock(Block* value_block) {
EXIT_IF(value_block == nullptr);
block = value_block;
}
Value IREmitter::Emit(ValueOpcode opcode, std::initializer_list<Value> args, uint64_t flags) {
auto& inst = block->AppendNewInst(opcode, args, flags);
return TypeOf(opcode) == Type::Void ? Value {} : Value(&inst);
}
U32 IREmitter::GetUserData(ScalarReg reg) {
return U32(Emit(ValueOpcode::GetUserData, {Value(reg)}));
}
U32 IREmitter::GetScalarReg(ScalarReg reg) {
return U32(Emit(ValueOpcode::GetScalarRegister, {Value(reg)}));
}
void IREmitter::SetScalarReg(ScalarReg reg, U32 value) {
Emit(ValueOpcode::SetScalarRegister, {Value(reg), value});
}
U1 IREmitter::GetThreadBitScalarReg(ScalarReg reg) {
return U1(Emit(ValueOpcode::GetThreadBitScalarRegister, {Value(reg)}));
}
void IREmitter::SetThreadBitScalarReg(ScalarReg reg, U1 value) {
Emit(ValueOpcode::SetThreadBitScalarRegister, {Value(reg), value});
}
U32 IREmitter::GetVectorReg(VectorReg reg) {
return U32(Emit(ValueOpcode::GetVectorRegister, {Value(reg)}));
}
void IREmitter::SetVectorReg(VectorReg reg, U32 value) {
Emit(ValueOpcode::SetVectorRegister, {Value(reg), value});
}
U1 IREmitter::GetGotoVariable(uint32_t id) {
return U1(Emit(ValueOpcode::GetGotoVariable, {Value(id)}));
}
void IREmitter::SetGotoVariable(uint32_t id, U1 value) {
Emit(ValueOpcode::SetGotoVariable, {Value(id), value});
}
U1 IREmitter::GetScc() {
return U1(Emit(ValueOpcode::GetScc));
}
void IREmitter::SetScc(U1 value) {
Emit(ValueOpcode::SetScc, {value});
}
U1 IREmitter::GetExec() {
return U1(Emit(ValueOpcode::GetExec));
}
void IREmitter::SetExec(U1 value) {
Emit(ValueOpcode::SetExec, {value});
}
U32 IREmitter::GetExecLo() {
return U32(Emit(ValueOpcode::GetExecLo));
}
void IREmitter::SetExecLo(U32 value) {
Emit(ValueOpcode::SetExecLo, {value});
}
U32 IREmitter::GetExecHi() {
return U32(Emit(ValueOpcode::GetExecHi));
}
void IREmitter::SetExecHi(U32 value) {
Emit(ValueOpcode::SetExecHi, {value});
}
U1 IREmitter::GetVcc() {
return U1(Emit(ValueOpcode::GetVcc));
}
void IREmitter::SetVcc(U1 value) {
Emit(ValueOpcode::SetVcc, {value});
}
U32 IREmitter::GetVccLo() {
return U32(Emit(ValueOpcode::GetVccLo));
}
void IREmitter::SetVccLo(U32 value) {
Emit(ValueOpcode::SetVccLo, {value});
}
U32 IREmitter::GetVccHi() {
return U32(Emit(ValueOpcode::GetVccHi));
}
void IREmitter::SetVccHi(U32 value) {
Emit(ValueOpcode::SetVccHi, {value});
}
U32 IREmitter::GetM0() {
return U32(Emit(ValueOpcode::GetM0));
}
void IREmitter::SetM0(U32 value) {
Emit(ValueOpcode::SetM0, {value});
}
F32 IREmitter::BitCastF32(U32 value) {
return F32(Emit(ValueOpcode::BitCastF32U32, {value}));
}
U32 IREmitter::BitCastU32(F32 value) {
return U32(Emit(ValueOpcode::BitCastU32F32, {value}));
}
F16 IREmitter::BitCastF16(U32 value) {
const auto half = U16(Emit(ValueOpcode::ConvertU16U32, {value}));
return F16(Emit(ValueOpcode::BitCastF16U16, {half}));
}
U32 IREmitter::BitCastU32(F16 value) {
const auto half = U16(Emit(ValueOpcode::BitCastU16F16, {value}));
return U32(Emit(ValueOpcode::ConvertU32U16, {half}));
}
U64 IREmitter::PackUint2x32(U32 low, U32 high) {
const auto pair = Emit(ValueOpcode::CompositeConstructU32x2, {low, high});
return U64(Emit(ValueOpcode::PackUint2x32, {pair}));
}
U32 IREmitter::CompositeExtract(Value composite, uint32_t index) {
return U32(Emit(ValueOpcode::CompositeExtractU32x2, {composite, Value(index)}));
}
U32 IREmitter::IAdd(U32 lhs, U32 rhs) {
return U32(Emit(ValueOpcode::IAdd32, {lhs, rhs}));
}
U32 IREmitter::ISub(U32 lhs, U32 rhs) {
return U32(Emit(ValueOpcode::ISub32, {lhs, rhs}));
}
U32 IREmitter::IMul(U32 lhs, U32 rhs) {
return U32(Emit(ValueOpcode::IMul32, {lhs, rhs}));
}
U32 IREmitter::ShiftLeftLogical(U32 value, U32 shift) {
return U32(Emit(ValueOpcode::ShiftLeftLogical32, {value, shift}));
}
U32 IREmitter::ShiftRightLogical(U32 value, U32 shift) {
return U32(Emit(ValueOpcode::ShiftRightLogical32, {value, shift}));
}
U32 IREmitter::ShiftRightArithmetic(U32 value, U32 shift) {
return U32(Emit(ValueOpcode::ShiftRightArithmetic32, {value, shift}));
}
U32 IREmitter::BitwiseAnd(U32 lhs, U32 rhs) {
return U32(Emit(ValueOpcode::BitwiseAnd32, {lhs, rhs}));
}
U32 IREmitter::BitwiseOr(U32 lhs, U32 rhs) {
return U32(Emit(ValueOpcode::BitwiseOr32, {lhs, rhs}));
}
U32 IREmitter::BitwiseXor(U32 lhs, U32 rhs) {
return U32(Emit(ValueOpcode::BitwiseXor32, {lhs, rhs}));
}
U32 IREmitter::BitwiseNot(U32 value) {
return U32(Emit(ValueOpcode::BitwiseNot32, {value}));
}
U32 IREmitter::Select(U1 condition, U32 true_value, U32 false_value) {
return U32(Emit(ValueOpcode::SelectU32, {condition, true_value, false_value}));
}
U1 IREmitter::IEqual(U32 lhs, U32 rhs) {
return U1(Emit(ValueOpcode::IEqual32, {lhs, rhs}));
}
U1 IREmitter::INotEqual(U32 lhs, U32 rhs) {
return U1(Emit(ValueOpcode::INotEqual32, {lhs, rhs}));
}
U1 IREmitter::ULessThan(U32 lhs, U32 rhs) {
return U1(Emit(ValueOpcode::ULessThan32, {lhs, rhs}));
}
U1 IREmitter::UGreaterThan(U32 lhs, U32 rhs) {
return U1(Emit(ValueOpcode::UGreaterThan32, {lhs, rhs}));
}
U1 IREmitter::LogicalAnd(U1 lhs, U1 rhs) {
return U1(Emit(ValueOpcode::LogicalAnd, {lhs, rhs}));
}
U1 IREmitter::LogicalOr(U1 lhs, U1 rhs) {
return U1(Emit(ValueOpcode::LogicalOr, {lhs, rhs}));
}
U1 IREmitter::LogicalNot(U1 value) {
return U1(Emit(ValueOpcode::LogicalNot, {value}));
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -0,0 +1,83 @@
#pragma once
#include "graphics/shader/recompiler/ir/Block.h"
#include <cstring>
#include <initializer_list>
#include <type_traits>
namespace Libs::Graphics::ShaderRecompiler::IR {
class IREmitter {
public:
explicit IREmitter(Block* block);
void SetBlock(Block* block);
Value Emit(ValueOpcode opcode, std::initializer_list<Value> args = {}, uint64_t flags = 0);
template <typename T>
requires(sizeof(T) <= sizeof(uint64_t) && std::is_trivially_copyable_v<T>)
Value Emit(ValueOpcode opcode, std::initializer_list<Value> args, T flags) {
uint64_t bits = 0;
std::memcpy(&bits, &flags, sizeof(flags));
return Emit(opcode, args, bits);
}
U32 GetUserData(ScalarReg reg);
U32 GetScalarReg(ScalarReg reg);
void SetScalarReg(ScalarReg reg, U32 value);
U1 GetThreadBitScalarReg(ScalarReg reg);
void SetThreadBitScalarReg(ScalarReg reg, U1 value);
U32 GetVectorReg(VectorReg reg);
void SetVectorReg(VectorReg reg, U32 value);
U1 GetGotoVariable(uint32_t id);
void SetGotoVariable(uint32_t id, U1 value);
U1 GetScc();
void SetScc(U1 value);
U1 GetExec();
void SetExec(U1 value);
U32 GetExecLo();
void SetExecLo(U32 value);
U32 GetExecHi();
void SetExecHi(U32 value);
U1 GetVcc();
void SetVcc(U1 value);
U32 GetVccLo();
void SetVccLo(U32 value);
U32 GetVccHi();
void SetVccHi(U32 value);
U32 GetM0();
void SetM0(U32 value);
F32 BitCastF32(U32 value);
U32 BitCastU32(F32 value);
F16 BitCastF16(U32 value);
U32 BitCastU32(F16 value);
U64 PackUint2x32(U32 low, U32 high);
U32 CompositeExtract(Value composite, uint32_t index);
U32 IAdd(U32 lhs, U32 rhs);
U32 ISub(U32 lhs, U32 rhs);
U32 IMul(U32 lhs, U32 rhs);
U32 ShiftLeftLogical(U32 value, U32 shift);
U32 ShiftRightLogical(U32 value, U32 shift);
U32 ShiftRightArithmetic(U32 value, U32 shift);
U32 BitwiseAnd(U32 lhs, U32 rhs);
U32 BitwiseOr(U32 lhs, U32 rhs);
U32 BitwiseXor(U32 lhs, U32 rhs);
U32 BitwiseNot(U32 value);
U32 Select(U1 condition, U32 true_value, U32 false_value);
U1 IEqual(U32 lhs, U32 rhs);
U1 INotEqual(U32 lhs, U32 rhs);
U1 ULessThan(U32 lhs, U32 rhs);
U1 UGreaterThan(U32 lhs, U32 rhs);
U1 LogicalAnd(U1 lhs, U1 rhs);
U1 LogicalOr(U1 lhs, U1 rhs);
U1 LogicalNot(U1 value);
private:
Block* block = nullptr;
};
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -1,323 +0,0 @@
#include "graphics/shader/recompiler/ir/ReadLaneElimination.h"
#include "graphics/shader/recompiler/ir/SrtWalker.h"
#include <algorithm>
#include <iterator>
#include <map>
#include <set>
#include <utility>
namespace Libs::Graphics::ShaderRecompiler::IR {
namespace {
constexpr uint32_t FirstTemporaryScalarRegister = 128;
struct LaneKey {
uint32_t reg = 0;
uint32_t lane = 0;
auto operator<=>(const LaneKey&) const = default;
};
using LaneSet = std::set<LaneKey>;
bool PairDwordOpcode(Opcode op) {
switch (op) {
case Opcode::MoveU64:
case Opcode::WqmB64:
case Opcode::SaveexecB64:
case Opcode::BitwiseAndU64:
case Opcode::BitwiseAndNotU64:
case Opcode::BitwiseOrU64:
case Opcode::BitwiseOrNotU64:
case Opcode::BitwiseXorU64:
case Opcode::BitwiseNandU64:
case Opcode::BitwiseNorU64:
case Opcode::BitwiseXnorU64:
case Opcode::BitwiseNotU64:
case Opcode::BitFieldMaskU64:
case Opcode::BitFieldExtractU64:
case Opcode::BitReplicateB64B32:
case Opcode::ShiftLeftLogicalU64:
case Opcode::ShiftRightLogicalU64:
case Opcode::SelectU64: return true;
default: return false;
}
}
bool ResolveLane(const Program& program, const Instruction& inst, uint32_t source_index,
uint32_t& lane) {
if (source_index >= inst.src_count || (program.wave_size != 32 && program.wave_size != 64)) {
return false;
}
const auto& selector = inst.src[source_index];
if (selector.kind == OperandKind::ImmediateU32) {
lane = selector.imm % program.wave_size;
return true;
}
uint32_t folded = 0;
if (!FoldScalarConstant(program.provenance, inst.scalar_sources[source_index], folded)) {
return false;
}
lane = folded % program.wave_size;
return true;
}
bool UniformWriteSource(const Instruction& inst) {
if (inst.src_count == 0) {
return false;
}
const auto& source = inst.src[0];
if (source.kind == OperandKind::ImmediateU32 || source.kind == OperandKind::PcRelativeU32) {
return true;
}
return source.kind == OperandKind::Register &&
(source.reg.file == RegisterFile::Scalar || source.reg.file == RegisterFile::Scc ||
source.reg.file == RegisterFile::M0);
}
bool WriteLaneKey(const Program& program, const Instruction& inst, LaneKey& key) {
if (inst.op != Opcode::WriteLaneU32 || inst.dst.kind != OperandKind::Register ||
inst.dst.reg.file != RegisterFile::Vector || !UniformWriteSource(inst)) {
return false;
}
uint32_t lane = 0;
if (!ResolveLane(program, inst, 1, lane)) {
return false;
}
key = {inst.dst.reg.index, lane};
return true;
}
bool ReadLaneKey(const Program& program, const Instruction& inst, LaneKey& key) {
if (inst.op != Opcode::ReadLaneU32 || inst.src_count < 2 ||
inst.src[0].kind != OperandKind::Register || inst.src[0].reg.file != RegisterFile::Vector) {
return false;
}
uint32_t lane = 0;
if (!ResolveLane(program, inst, 1, lane)) {
return false;
}
key = {inst.src[0].reg.index, lane};
return true;
}
void InvalidateRegister(LaneSet& valid, uint32_t reg) {
const auto first = valid.lower_bound({reg, 0});
const auto last = valid.lower_bound({reg + 1u, 0});
valid.erase(first, last);
}
void ApplyInstruction(const Program& program, const Instruction& inst, LaneSet& valid) {
if (inst.op == Opcode::WriteLaneU32 && inst.dst.kind == OperandKind::Register &&
inst.dst.reg.file == RegisterFile::Vector) {
LaneKey key;
if (WriteLaneKey(program, inst, key)) {
valid.insert(key);
return;
}
uint32_t lane = 0;
if (ResolveLane(program, inst, 1, lane)) {
valid.erase({inst.dst.reg.index, lane});
} else {
InvalidateRegister(valid, inst.dst.reg.index);
}
return;
}
if (inst.op == Opcode::MoveRelDestU32 && inst.dst.kind == OperandKind::Register &&
inst.dst.reg.file == RegisterFile::Vector) {
valid.clear();
return;
}
if (inst.dst.kind == OperandKind::Register && inst.dst.reg.file == RegisterFile::Vector) {
uint32_t dwords = std::max(inst.memory.data_dwords, 1u);
if (PairDwordOpcode(inst.op) || inst.op == Opcode::UMadU64U32) {
dwords = std::max(dwords, 2u);
}
for (uint32_t i = 0; i < dwords && inst.dst.reg.index <= UINT32_MAX - i; i++) {
InvalidateRegister(valid, inst.dst.reg.index + i);
}
}
if (inst.dst2.kind == OperandKind::Register && inst.dst2.reg.file == RegisterFile::Vector) {
InvalidateRegister(valid, inst.dst2.reg.index);
}
}
LaneSet TransferBlock(const Program& program, const BasicBlock& block, LaneSet state) {
for (const auto& inst: block.instructions) {
ApplyInstruction(program, inst, state);
}
return state;
}
LaneSet Intersect(const LaneSet& left, const LaneSet& right) {
LaneSet result;
std::set_intersection(left.begin(), left.end(), right.begin(), right.end(),
std::inserter(result, result.end()));
return result;
}
uint32_t NextTemporaryScalarRegister(const Program& program) {
uint32_t next = FirstTemporaryScalarRegister;
const auto consider = [&next](const Operand& operand) {
if (operand.kind == OperandKind::Register && operand.reg.file == RegisterFile::Scalar &&
operand.reg.index >= next && operand.reg.index != UINT32_MAX) {
next = operand.reg.index + 1u;
}
};
for (const auto& block: program.blocks) {
for (const auto& inst: block.instructions) {
consider(inst.dst);
consider(inst.dst2);
for (uint32_t i = 0; i < inst.src_count; i++) {
consider(inst.src[i]);
}
}
}
return next;
}
Operand ScalarRegisterOperand(uint32_t reg) {
Operand operand;
operand.kind = OperandKind::Register;
operand.reg.file = RegisterFile::Scalar;
operand.reg.index = reg;
return operand;
}
Instruction ShadowWrite(const Instruction& write, uint32_t temporary) {
Instruction shadow;
shadow.pc = write.pc;
shadow.op = Opcode::MoveU32;
shadow.dst = ScalarRegisterOperand(temporary);
shadow.src[0] = write.src[0];
shadow.src_count = 1;
return shadow;
}
Instruction ShadowRead(const Instruction& read, uint32_t temporary) {
Instruction rewritten;
rewritten.pc = read.pc;
rewritten.op = Opcode::MoveU32;
rewritten.dst = read.dst;
rewritten.src[0] = ScalarRegisterOperand(temporary);
rewritten.src_count = 1;
return rewritten;
}
} // namespace
ReadLaneEliminationStats EliminateReadLane(Program& program) {
ReadLaneEliminationStats stats;
if (program.blocks.empty() || (program.wave_size != 32 && program.wave_size != 64)) {
return stats;
}
LaneSet universe;
for (const auto& block: program.blocks) {
for (const auto& inst: block.instructions) {
LaneKey key;
if (WriteLaneKey(program, inst, key)) {
universe.insert(key);
}
}
}
if (universe.empty()) {
return stats;
}
const size_t block_count = program.blocks.size();
std::vector<LaneSet> entry(block_count, universe);
std::vector<LaneSet> exit(block_count, universe);
entry[0].clear();
for (size_t block = 0; block < block_count; block++) {
exit[block] = TransferBlock(program, program.blocks[block], entry[block]);
}
bool changed = true;
while (changed) {
changed = false;
for (size_t block_index = 0; block_index < block_count; block_index++) {
LaneSet next_entry;
const auto& block = program.blocks[block_index];
if (block_index != 0 && !block.predecessors.empty()) {
next_entry = universe;
for (const auto predecessor: block.predecessors) {
if (predecessor >= block_count) {
next_entry.clear();
break;
}
next_entry = Intersect(next_entry, exit[predecessor]);
}
}
auto next_exit = TransferBlock(program, block, next_entry);
if (next_entry != entry[block_index] || next_exit != exit[block_index]) {
entry[block_index] = std::move(next_entry);
exit[block_index] = std::move(next_exit);
changed = true;
}
}
}
LaneSet forwarded;
for (size_t block_index = 0; block_index < block_count; block_index++) {
auto state = entry[block_index];
for (const auto& inst: program.blocks[block_index].instructions) {
LaneKey key;
if (ReadLaneKey(program, inst, key) && state.contains(key)) {
forwarded.insert(key);
}
ApplyInstruction(program, inst, state);
}
}
if (forwarded.empty()) {
return stats;
}
std::map<LaneKey, uint32_t> temporaries;
auto next_temporary = NextTemporaryScalarRegister(program);
for (const auto& key: forwarded) {
if (next_temporary == UINT32_MAX) {
return {};
}
temporaries.emplace(key, next_temporary++);
}
for (size_t block_index = 0; block_index < block_count; block_index++) {
const auto original = std::move(program.blocks[block_index].instructions);
auto& rewritten = program.blocks[block_index].instructions;
rewritten.clear();
rewritten.reserve(original.size() + temporaries.size());
auto state = entry[block_index];
for (const auto& inst: original) {
LaneKey read_key;
if (ReadLaneKey(program, inst, read_key) && state.contains(read_key)) {
const auto temporary = temporaries.find(read_key);
if (temporary != temporaries.end()) {
rewritten.push_back(ShadowRead(inst, temporary->second));
stats.rewritten_reads++;
ApplyInstruction(program, inst, state);
continue;
}
}
rewritten.push_back(inst);
LaneKey write_key;
if (WriteLaneKey(program, inst, write_key)) {
const auto temporary = temporaries.find(write_key);
if (temporary != temporaries.end()) {
rewritten.push_back(ShadowWrite(inst, temporary->second));
stats.shadow_writes++;
}
}
ApplyInstruction(program, inst, state);
}
}
return stats;
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -1,20 +0,0 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_READLANEELIMINATION_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_READLANEELIMINATION_H_
#include "graphics/shader/recompiler/ir/ShaderIR.h"
namespace Libs::Graphics::ShaderRecompiler::IR {
struct ReadLaneEliminationStats {
uint32_t rewritten_reads = 0;
uint32_t shadow_writes = 0;
};
// Replaces fixed-lane ReadLane operations that are reached by a matching WriteLane on every
// control-flow path. A synthetic scalar register snapshots the value at WriteLane execution time,
// so the rewrite remains valid when the source SGPR is subsequently overwritten.
[[nodiscard]] ReadLaneEliminationStats EliminateReadLane(Program& program);
} // namespace Libs::Graphics::ShaderRecompiler::IR
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_READLANEELIMINATION_H_ */
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <cstdint>
namespace Libs::Graphics::ShaderRecompiler::IR {
enum class ScalarReg : uint16_t {};
enum class VectorReg : uint16_t {};
// Architectural SGPRs occupy the low range. The current shader-only migration also accepts the
// scalar shadow registers produced by Kyty's pre-existing read-lane pass; that pass disappears
// once read-lane elimination operates on the SSA graph.
constexpr uint32_t NumScalarRegs = 512;
constexpr uint32_t NumVectorRegs = 256;
constexpr uint32_t RegIndex(ScalarReg reg) {
return static_cast<uint32_t>(reg);
}
constexpr uint32_t RegIndex(VectorReg reg) {
return static_cast<uint32_t>(reg);
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -1,464 +0,0 @@
#include "graphics/shader/recompiler/ir/ResourceTracking.h"
#include "graphics/shader/recompiler/ir/ScalarProvenance.h"
#include <algorithm>
#include <fmt/format.h>
#include <functional>
namespace Libs::Graphics::ShaderRecompiler::IR {
namespace {
const char* StageName(ShaderType stage) {
switch (stage) {
case ShaderType::Vertex: return "vertex";
case ShaderType::Pixel: return "pixel";
case ShaderType::Fetch: return "fetch";
case ShaderType::Compute: return "compute";
default: return "unknown";
}
}
bool IsAtomic(Opcode op) {
switch (op) {
case Opcode::AtomicSwapU32:
case Opcode::AtomicAddU32:
case Opcode::AtomicSubU32:
case Opcode::AtomicSMinI32:
case Opcode::AtomicUMinU32:
case Opcode::AtomicSMaxI32:
case Opcode::AtomicUMaxU32:
case Opcode::AtomicAndU32:
case Opcode::AtomicOrU32:
case Opcode::AtomicXorU32:
case Opcode::AtomicFMinF32:
case Opcode::AtomicFMaxF32: return true;
default: return false;
}
}
bool IsWrite(Opcode op) {
switch (op) {
case Opcode::BufferStoreByte:
case Opcode::BufferStoreShort:
case Opcode::BufferStoreDword:
case Opcode::ImageStore: return true;
default: return IsAtomic(op);
}
}
bool NeedsSampler(Opcode op) {
return op == Opcode::ImageSample || op == Opcode::ImageGather4 || op == Opcode::ImageGetLod;
}
bool IsDepthCompare(const Instruction& inst) {
return (inst.memory.image_sample_flags & Decoder::ImageSampleFlagCompare) != 0;
}
ImageMipMode MipMode(const Instruction& inst) {
const bool storage = inst.memory.kind == ResourceKind::StorageImage ||
inst.memory.kind == ResourceKind::StorageImageUint;
return storage && inst.memory.image_has_mip ? ImageMipMode::DynamicStorage : ImageMipMode::None;
}
bool IsBuffer(const Instruction& inst) {
return inst.memory.kind == ResourceKind::Buffer ||
(inst.memory.kind == ResourceKind::ScalarBuffer && inst.op == Opcode::SBufferLoadDword);
}
bool IsImage(const Instruction& inst) {
return inst.memory.kind == ResourceKind::Image || inst.memory.kind == ResourceKind::ImageUint ||
inst.memory.kind == ResourceKind::StorageImage ||
inst.memory.kind == ResourceKind::StorageImageUint;
}
bool IsAddress(const Instruction& inst) {
return inst.op == Opcode::SLoadDword || inst.memory.kind == ResourceKind::Flat ||
inst.memory.kind == ResourceKind::Global || inst.memory.kind == ResourceKind::Scratch;
}
uint32_t ByteExtent(const Instruction& inst) {
const auto bytes = std::max((inst.memory.data_bits + 7u) / 8u, 1u);
const auto count = std::max(inst.memory.data_dwords, 1u);
const auto end =
static_cast<uint64_t>(inst.memory.offset) + static_cast<uint64_t>(bytes) * count;
return end > UINT32_MAX ? UINT32_MAX : static_cast<uint32_t>(end);
}
bool ContainsUnknown(const ScalarProvenance& provenance, uint32_t id, std::vector<uint8_t>& visited,
std::vector<uint32_t>& path) {
path.push_back(id);
if (id <= ScalarProvenance::Unknown || id >= provenance.values.size()) {
return true;
}
if (visited[id] != 0) {
path.pop_back();
return false;
}
visited[id] = 1;
const auto& value = provenance.values[id];
if (value.op == ScalarValueOp::Phi) {
for (const auto arg: value.phi_args) {
if (ContainsUnknown(provenance, arg, visited, path)) {
return true;
}
}
path.pop_back();
return false;
}
const auto args = ScalarValueArgCount(value.op);
for (uint32_t i = 0; i < args; i++) {
if (ContainsUnknown(provenance, value.args[i], visited, path)) {
return true;
}
}
path.pop_back();
return false;
}
bool IsLoopInvariantValue(const ScalarProvenance& provenance, uint32_t id,
std::vector<uint8_t>& visiting) {
if (id <= ScalarProvenance::Unknown || id >= provenance.values.size()) {
return false;
}
if (visiting[id] != 0) {
return true;
}
visiting[id] = 1;
const auto& value = provenance.values[id];
if (value.op == ScalarValueOp::Phi) {
uint32_t invariant = ScalarProvenance::Undefined;
for (const auto arg: value.phi_args) {
if (arg == id) {
continue;
}
if (invariant == ScalarProvenance::Undefined) {
invariant = arg;
} else if (arg != invariant) {
return false;
}
}
return invariant != ScalarProvenance::Undefined &&
IsLoopInvariantValue(provenance, invariant, visiting);
}
for (uint32_t i = 0; i < ScalarValueArgCount(value.op); i++) {
if (!IsLoopInvariantValue(provenance, value.args[i], visiting)) {
return false;
}
}
return true;
}
bool IsLoopInvariantDescriptor(const ScalarProvenance& provenance,
const DescriptorValue& descriptor) {
std::vector<uint8_t> visiting(provenance.values.size());
for (uint32_t i = 0; i < descriptor.dword_count; i++) {
if (!IsLoopInvariantValue(provenance, descriptor.dwords[i], visiting)) {
return false;
}
}
return true;
}
class Tracker {
public:
explicit Tracker(Program& program): m_program(program) {}
bool Run(std::string* error) {
if (m_program.resource_tracking_complete) {
return Fail(0, error, "resources already tracked");
}
if (!m_program.srt_plan_complete) {
return Fail(0, error, "SRT plan is not ready");
}
if (!m_program.srt_patching_complete) {
return Fail(0, error, "SRT reads were not patched");
}
for (auto& block: m_program.blocks) {
for (auto& inst: block.instructions) {
if (!Collect(inst, error)) {
return false;
}
}
}
LinkImageAliases();
m_program.info = std::move(m_info);
for (const auto& patch: m_patches) {
auto& inst = patch.inst.get();
inst.memory.resource = patch.resource;
inst.memory.sampler = patch.sampler;
inst.memory.resource_source = ScalarProvenance::Undefined;
inst.memory.sampler_source = ScalarProvenance::Undefined;
}
m_program.resource_tracking_complete = true;
return true;
}
private:
struct Patch {
std::reference_wrapper<Instruction> inst;
uint32_t resource = 0;
uint32_t sampler = 0;
};
bool Fail(uint32_t pc, std::string* error, const std::string& reason) const {
if (error != nullptr) {
*error = fmt::format("shader resource tracking: hash=0x{:016x} stage={} pc=0x{:08x} {}",
m_program.shader_hash, StageName(m_program.stage), pc, reason);
}
return false;
}
bool ValidateSource(uint32_t source, uint32_t dwords, uint32_t pc, std::string* error) const {
const auto* descriptor = GetDescriptorSource(m_program, source);
if (descriptor == nullptr || descriptor->dword_count != dwords) {
return Fail(pc, error,
fmt::format("descriptor source {} is missing or has wrong width", source));
}
std::vector<uint8_t> visited(m_program.provenance.values.size());
for (uint32_t i = 0; i < descriptor->dword_count; i++) {
std::vector<uint32_t> path;
if (ContainsUnknown(m_program.provenance, descriptor->dwords[i], visited, path)) {
const auto value = descriptor->dwords[i];
std::string chain;
for (const auto id: path) {
const auto op = id < m_program.provenance.values.size()
? static_cast<uint32_t>(m_program.provenance.values[id].op)
: UINT32_MAX;
chain += fmt::format("{}{}:{}({})", chain.empty() ? "" : " -> ", id, op,
ScalarValueToString(m_program.provenance, id));
}
return Fail(
pc, error,
fmt::format(
"descriptor source {} dword {} contains an unknown value {} ({}) path {}",
source, i, value, ScalarValueToString(m_program.provenance, value), chain));
}
}
const auto dynamic =
std::find(m_program.srt.dynamic_sources.begin(), m_program.srt.dynamic_sources.end(),
source) != m_program.srt.dynamic_sources.end();
if (dynamic && !IsLoopInvariantDescriptor(m_program.provenance, *descriptor)) {
std::string detail;
for (uint32_t i = 0; i < descriptor->dword_count; i++) {
const auto id = descriptor->dwords[i];
detail +=
fmt::format(" d{}={}({})", i, id,
id < m_program.provenance.values.size()
? static_cast<uint32_t>(m_program.provenance.values[id].op)
: UINT32_MAX);
if (id < m_program.provenance.values.size()) {
for (const auto arg: m_program.provenance.values[id].phi_args) {
detail += fmt::format(
"/{}({})", arg,
arg < m_program.provenance.values.size()
? static_cast<uint32_t>(m_program.provenance.values[arg].op)
: UINT32_MAX);
}
}
}
return Fail(pc, error,
fmt::format("descriptor source {} requires unsupported GPU selection{}",
source, detail));
}
if (!DescriptorSourceResolved(m_program, source) && !dynamic) {
return Fail(pc, error, fmt::format("descriptor source {} is unresolved", source));
}
return true;
}
uint32_t AddBuffer(const Instruction& inst) {
for (uint32_t i = 0; i < m_info.buffers.size(); i++) {
if (m_info.buffers[i].source == inst.memory.resource_source) {
Merge(&m_info.buffers[i], inst);
return i;
}
}
if (m_info.buffers.size() >= ShaderInfo::MaxBuffers) {
return UINT32_MAX;
}
BufferResource resource;
resource.source = inst.memory.resource_source;
resource.first_use_pc = inst.pc;
Merge(&resource, inst);
m_info.buffers.push_back(resource);
return static_cast<uint32_t>(m_info.buffers.size() - 1);
}
void Merge(BufferResource* resource, const Instruction& inst) const {
resource->first_use_pc = std::min(resource->first_use_pc, inst.pc);
resource->max_byte_extent = std::max(resource->max_byte_extent, ByteExtent(inst));
resource->read = resource->read || !IsWrite(inst.op) || IsAtomic(inst.op);
resource->written = resource->written || IsWrite(inst.op);
resource->atomic = resource->atomic || IsAtomic(inst.op);
resource->formatted = resource->formatted || inst.memory.formatted;
resource->scalar = resource->scalar || inst.memory.kind == ResourceKind::ScalarBuffer;
}
void LinkImageAliases() {
for (auto& buffer: m_info.buffers) {
const auto* buffer_source = GetDescriptorSource(m_program, buffer.source);
if (buffer_source == nullptr || buffer_source->dword_count != 4) {
continue;
}
for (uint32_t i = 0; i < m_info.images.size(); i++) {
const auto* image_source = GetDescriptorSource(m_program, m_info.images[i].source);
if (image_source != nullptr && image_source->dword_count == 8 &&
std::equal(buffer_source->dwords.begin(), buffer_source->dwords.begin() + 4,
image_source->dwords.begin())) {
buffer.image_alias = i;
break;
}
}
}
}
uint32_t AddImage(const Instruction& inst) {
for (uint32_t i = 0; i < m_info.images.size(); i++) {
auto& image = m_info.images[i];
if (image.source == inst.memory.resource_source && image.kind == inst.memory.kind &&
image.dimension == inst.memory.image_dimension && image.mip_mode == MipMode(inst) &&
image.depth_compare == IsDepthCompare(inst)) {
Merge(&image, inst);
return i;
}
}
if (m_info.images.size() >= ShaderInfo::MaxImages) {
return UINT32_MAX;
}
ImageResource image;
image.source = inst.memory.resource_source;
image.first_use_pc = inst.pc;
image.kind = inst.memory.kind;
image.dimension = inst.memory.image_dimension;
image.mip_mode = MipMode(inst);
image.depth_compare = IsDepthCompare(inst);
Merge(&image, inst);
m_info.images.push_back(image);
return static_cast<uint32_t>(m_info.images.size() - 1);
}
uint32_t AddAddress(const Instruction& inst) {
auto immediate = static_cast<int32_t>(inst.memory.offset);
if (inst.memory.kind == ResourceKind::ScalarBuffer) {
immediate = static_cast<int32_t>(static_cast<uint32_t>(immediate) & ~3u);
}
const auto min_offset =
inst.memory.resource_source == ScalarProvenance::Unknown ? 0 : std::min(immediate, 0);
for (uint32_t i = 0; i < m_info.addresses.size(); i++) {
auto& address = m_info.addresses[i];
if (address.source == inst.memory.resource_source && address.kind == inst.memory.kind) {
address.first_use_pc = std::min(address.first_use_pc, inst.pc);
address.min_offset = std::min(address.min_offset, min_offset);
address.read = address.read || !IsWrite(inst.op) || IsAtomic(inst.op);
address.written = address.written || IsWrite(inst.op);
address.atomic = address.atomic || IsAtomic(inst.op);
return i;
}
}
if (m_info.addresses.size() >= ShaderInfo::MaxAddresses) {
return UINT32_MAX;
}
AddressResource address {inst.memory.resource_source, inst.pc, inst.memory.kind,
min_offset};
address.read = !IsWrite(inst.op) || IsAtomic(inst.op);
address.written = IsWrite(inst.op);
address.atomic = IsAtomic(inst.op);
m_info.addresses.push_back(address);
return static_cast<uint32_t>(m_info.addresses.size() - 1);
}
void Merge(ImageResource* resource, const Instruction& inst) const {
resource->first_use_pc = std::min(resource->first_use_pc, inst.pc);
resource->read = resource->read || !IsWrite(inst.op) || IsAtomic(inst.op);
resource->written = resource->written || IsWrite(inst.op);
resource->atomic = resource->atomic || IsAtomic(inst.op);
}
uint32_t AddSampler(const Instruction& inst) {
for (uint32_t i = 0; i < m_info.samplers.size(); i++) {
if (m_info.samplers[i].source == inst.memory.sampler_source) {
m_info.samplers[i].first_use_pc =
std::min(m_info.samplers[i].first_use_pc, inst.pc);
return i;
}
}
if (m_info.samplers.size() >= ShaderInfo::MaxSamplers) {
return UINT32_MAX;
}
m_info.samplers.push_back({inst.memory.sampler_source, inst.pc});
return static_cast<uint32_t>(m_info.samplers.size() - 1);
}
bool AddSampledPair(uint32_t image, uint32_t sampler, uint32_t pc, std::string* error) {
for (auto& pair: m_info.sampled_pairs) {
if (pair.image == image && pair.sampler == sampler) {
pair.first_use_pc = std::min(pair.first_use_pc, pc);
return true;
}
}
if (m_info.sampled_pairs.size() >= ShaderInfo::MaxSampledPairs) {
return Fail(pc, error, "sampled image/sampler pair limit exceeded");
}
m_info.sampled_pairs.push_back({image, sampler, pc});
return true;
}
bool Collect(Instruction& inst, std::string* error) {
if (IsAddress(inst)) {
const bool unbased = inst.memory.resource_source == ScalarProvenance::Unknown;
if ((!unbased && !ValidateSource(inst.memory.resource_source, 2, inst.pc, error)) ||
(unbased && inst.memory.kind != ResourceKind::Flat &&
inst.memory.kind != ResourceKind::Global &&
inst.memory.kind != ResourceKind::Scratch)) {
return unbased ? Fail(inst.pc, error, "scalar memory base is unresolved") : false;
}
const auto resource = AddAddress(inst);
if (resource == UINT32_MAX) {
return Fail(inst.pc, error, "address resource limit exceeded");
}
m_patches.push_back({std::ref(inst), resource, 0});
return true;
}
if (!IsBuffer(inst) && !IsImage(inst)) {
return true;
}
if (!ValidateSource(inst.memory.resource_source, IsBuffer(inst) ? 4u : 8u, inst.pc,
error)) {
return false;
}
const auto resource = IsBuffer(inst) ? AddBuffer(inst) : AddImage(inst);
if (resource == UINT32_MAX) {
return Fail(inst.pc, error,
IsBuffer(inst) ? "buffer resource limit exceeded"
: "image resource limit exceeded");
}
uint32_t sampler = 0;
if (NeedsSampler(inst.op)) {
if (!ValidateSource(inst.memory.sampler_source, 4, inst.pc, error)) {
return false;
}
sampler = AddSampler(inst);
if (sampler == UINT32_MAX) {
return Fail(inst.pc, error, "sampler resource limit exceeded");
}
if (!AddSampledPair(resource, sampler, inst.pc, error)) {
return false;
}
}
m_patches.push_back({std::ref(inst), resource, sampler});
return true;
}
Program& m_program;
ShaderInfo m_info;
std::vector<Patch> m_patches;
};
} // namespace
bool TrackResources(Program& program, std::string* error) {
return Tracker(program).Run(error);
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -1,963 +0,0 @@
#include "graphics/shader/recompiler/ir/ScalarProvenance.h"
#include <algorithm>
#include <array>
#include <compare>
#include <deque>
#include <fmt/format.h>
#include <map>
namespace Libs::Graphics::ShaderRecompiler::IR {
uint32_t ScalarValueArgCount(ScalarValueOp op) {
switch (op) {
case ScalarValueOp::Not: return 1;
case ScalarValueOp::Add:
case ScalarValueOp::Sub:
case ScalarValueOp::Mul:
case ScalarValueOp::And:
case ScalarValueOp::AndNot:
case ScalarValueOp::Or:
case ScalarValueOp::OrNot:
case ScalarValueOp::Xor:
case ScalarValueOp::ShiftLeft:
case ScalarValueOp::ShiftRight:
case ScalarValueOp::ShiftRightArithmetic:
case ScalarValueOp::BitFieldMaskU32:
case ScalarValueOp::BitFieldMaskU64Low:
case ScalarValueOp::BitFieldMaskU64High: return 2;
case ScalarValueOp::AddCarry:
case ScalarValueOp::Carry:
case ScalarValueOp::SubBorrow:
case ScalarValueOp::Borrow:
case ScalarValueOp::Add3:
case ScalarValueOp::ShiftLeftAdd:
case ScalarValueOp::ShiftLeftAddCarry:
case ScalarValueOp::AddShiftLeft:
case ScalarValueOp::XorAdd:
case ScalarValueOp::ShiftLeftOr:
case ScalarValueOp::ReadConst: return 3;
case ScalarValueOp::ReadConstBuffer: return 5;
default: return 0;
}
}
namespace {
constexpr uint32_t ScalarRegisters = 128;
constexpr uint32_t VectorRegisters = 256;
// Clamp X/Y/Z are consecutive three-bit fields; the high bit of each selects a border mode.
constexpr uint32_t SamplerBorderClampMask = (1u << 2u) | (1u << 5u) | (1u << 8u);
struct ScalarState {
std::array<uint32_t, ScalarRegisters> regs = {};
std::array<uint32_t, VectorRegisters> address_bases = {};
uint32_t scc = ScalarProvenance::Undefined;
uint32_t m0 = ScalarProvenance::Undefined;
std::map<uint64_t, uint32_t> vector_lanes;
void Fill(uint32_t value) {
regs.fill(value);
address_bases.fill(value);
scc = value;
m0 = value;
vector_lanes.clear();
}
bool operator==(const ScalarState&) const = default;
};
struct ValueKey {
ScalarValueOp op = ScalarValueOp::Undefined;
uint32_t imm = 0;
std::array<uint32_t, 6> args = {};
auto operator<=>(const ValueKey&) const = default;
};
struct DescriptorKey {
std::array<uint32_t, 8> dwords = {};
uint32_t dword_count = 0;
auto operator<=>(const DescriptorKey&) const = default;
};
bool ScalarRegister(const Operand& operand, uint32_t& reg) {
if (operand.kind != OperandKind::Register) {
return false;
}
if (operand.reg.file == RegisterFile::Scalar && operand.reg.index < ScalarRegisters) {
reg = operand.reg.index;
return true;
}
if (operand.reg.file == RegisterFile::Vcc && operand.reg.index < 2) {
reg = 106u + operand.reg.index;
return true;
}
return false;
}
bool VectorRegister(const Operand& operand, uint32_t& reg) {
if (operand.kind != OperandKind::Register || operand.reg.file != RegisterFile::Vector ||
operand.reg.index >= VectorRegisters) {
return false;
}
reg = operand.reg.index;
return true;
}
bool FlatStore(Opcode op) {
switch (op) {
case Opcode::FlatStoreByte:
case Opcode::FlatStoreShort:
case Opcode::FlatStoreDword: return true;
default: return false;
}
}
bool PairDwordOpcode(Opcode op) {
switch (op) {
case Opcode::MoveU64:
case Opcode::WqmB64:
case Opcode::SaveexecB64:
case Opcode::BitwiseAndU64:
case Opcode::BitwiseAndNotU64:
case Opcode::BitwiseOrU64:
case Opcode::BitwiseOrNotU64:
case Opcode::BitwiseXorU64:
case Opcode::BitwiseNandU64:
case Opcode::BitwiseNorU64:
case Opcode::BitwiseXnorU64:
case Opcode::BitwiseNotU64:
case Opcode::BitFieldMaskU64:
case Opcode::BitFieldExtractU64:
case Opcode::BitReplicateB64B32:
case Opcode::ShiftLeftLogicalU64:
case Opcode::ShiftRightLogicalU64:
case Opcode::SelectU64: return true;
default: return false;
}
}
bool ScalarMemoryLoad(Opcode op) {
return op == Opcode::SLoadDword || op == Opcode::SBufferLoadDword;
}
bool ValueResolved(const ScalarProvenance& provenance, uint32_t id, std::vector<uint8_t>& visited) {
if (id <= ScalarProvenance::Unknown || id >= provenance.values.size()) {
return false;
}
if (visited[id] == 1) {
return false;
}
if (visited[id] == 2) {
return true;
}
if (visited[id] == 3) {
return false;
}
visited[id] = 1;
const auto& value = provenance.values[id];
bool resolved = true;
if (value.op == ScalarValueOp::Phi) {
resolved = !value.phi_args.empty() &&
std::all_of(value.phi_args.begin(), value.phi_args.end(), [&](uint32_t arg) {
return ValueResolved(provenance, arg, visited);
});
} else {
const auto args = ScalarValueArgCount(value.op);
for (uint32_t i = 0; i < args && resolved; i++) {
resolved = ValueResolved(provenance, value.args[i], visited);
}
}
visited[id] = resolved ? 2 : 3;
return resolved;
}
class Builder {
public:
explicit Builder(Program& program): m_program(program), m_graph(program.provenance) {}
bool Run(std::string* error) {
m_graph = {};
m_graph.values.resize(2);
m_graph.values[ScalarProvenance::Undefined].op = ScalarValueOp::Undefined;
m_graph.values[ScalarProvenance::Unknown].op = ScalarValueOp::Unknown;
const auto block_count = m_program.blocks.size();
m_entry.resize(block_count);
m_exit.resize(block_count);
m_phi.resize(block_count);
m_exit_ready.assign(block_count, false);
m_queued.assign(block_count, false);
for (auto& state: m_entry) {
state.Fill(ScalarProvenance::Undefined);
}
for (auto& state: m_exit) {
state.Fill(ScalarProvenance::Undefined);
}
for (auto& phis: m_phi) {
phis.Fill(ScalarProvenance::Undefined);
}
m_user_data.Fill(ScalarProvenance::Undefined);
for (uint32_t reg = 0; reg < ScalarRegisters; reg++) {
m_user_data.regs[reg] =
reg >= m_program.user_data_base &&
reg - m_program.user_data_base < m_program.user_data_count
? InternValue({ScalarValueOp::UserData, 0, reg})
: ScalarProvenance::Unknown;
}
m_user_data.scc = ScalarProvenance::Unknown;
m_user_data.m0 = ScalarProvenance::Unknown;
if (block_count == 0) {
return true;
}
for (const auto& block: m_program.blocks) {
for (const auto predecessor: block.predecessors) {
if (predecessor >= block_count) {
return Fail(error, fmt::format("scalar provenance has invalid predecessor {}",
predecessor));
}
}
for (const auto successor: block.successors) {
if (successor >= block_count) {
return Fail(error, fmt::format("scalar provenance has invalid successor {}",
successor));
}
}
if (!ValidateScalarMemoryGroups(block, error)) {
return false;
}
}
Queue(0);
while (!m_work.empty()) {
const auto block_index = m_work.front();
m_work.pop_front();
m_queued[block_index] = false;
auto next_entry = MergeEntry(block_index);
const bool entry_changed = next_entry != m_entry[block_index];
m_entry[block_index] = next_entry;
auto next_exit = Execute(m_program.blocks[block_index], next_entry, false);
const bool was_ready = m_exit_ready[block_index];
if (was_ready && !entry_changed && next_exit == m_exit[block_index]) {
continue;
}
m_exit[block_index] = next_exit;
m_exit_ready[block_index] = true;
for (const auto successor: m_program.blocks[block_index].successors) {
Queue(successor);
}
}
for (size_t block = 0; block < block_count; block++) {
Execute(m_program.blocks[block], m_entry[block], true);
}
return true;
}
private:
bool Fail(std::string* error, std::string text) const {
if (error != nullptr) {
*error = std::move(text);
}
return false;
}
uint32_t AddValue(ScalarValue value) {
m_graph.values.push_back(std::move(value));
return static_cast<uint32_t>(m_graph.values.size() - 1);
}
bool ValidateScalarMemoryGroups(const BasicBlock& block, std::string* error) const {
for (size_t index = 0; index < block.instructions.size();) {
const auto& first = block.instructions[index];
if (!ScalarMemoryLoad(first.op) || first.memory.component_count == 1) {
index++;
continue;
}
const auto count = first.memory.component_count;
uint32_t first_dst = 0;
if (count == 0 || first.memory.component_index != 0 ||
index + count > block.instructions.size() ||
!ScalarRegister(first.dst, first_dst)) {
return Fail(error,
fmt::format("malformed scalar-memory group at pc=0x{:x}", first.pc));
}
for (uint32_t component = 0; component < count; component++) {
const auto& inst = block.instructions[index + component];
uint32_t dst = 0;
if (inst.op != first.op || inst.pc != first.pc ||
inst.memory.component_count != count ||
inst.memory.component_index != component ||
inst.memory.resource != first.memory.resource ||
inst.memory.offset != first.memory.offset + component * 4u ||
inst.src_count != first.src_count ||
!std::equal(std::begin(inst.src), std::end(inst.src), std::begin(first.src)) ||
!ScalarRegister(inst.dst, dst) || dst != first_dst + component) {
return Fail(error,
fmt::format("malformed scalar-memory component {} at pc=0x{:x}",
component, first.pc));
}
}
index += count;
}
return true;
}
uint32_t InternValue(ScalarValue value) {
const ValueKey key {value.op, value.imm, value.args};
if (const auto found = m_values.find(key); found != m_values.end()) {
return found->second;
}
const auto id = AddValue(std::move(value));
m_values.emplace(key, id);
return id;
}
uint32_t Constant(uint32_t value) { return InternValue({ScalarValueOp::Constant, 0, value}); }
static uint64_t VectorLaneKey(uint32_t reg, uint32_t lane) {
return (static_cast<uint64_t>(reg) << 32u) | lane;
}
uint32_t OperandValue(const Operand& operand, const ScalarState& state) {
uint32_t reg = 0;
if (ScalarRegister(operand, reg)) {
return state.regs[reg];
}
if (operand.kind == OperandKind::Register && operand.reg.file == RegisterFile::Scc) {
return state.scc;
}
if (operand.kind == OperandKind::Register && operand.reg.file == RegisterFile::M0) {
return state.m0;
}
if (operand.kind == OperandKind::ImmediateU32 ||
operand.kind == OperandKind::PcRelativeU32) {
if (operand.kind == OperandKind::PcRelativeU32) {
return InternValue({ScalarValueOp::PcRelativeLow, 0, operand.imm});
}
return Constant(operand.imm);
}
return ScalarProvenance::Unknown;
}
uint32_t Define(const Instruction& inst, ScalarValueOp op, const ScalarState& state,
uint32_t imm = 0) {
ScalarValue node;
node.op = op;
node.pc = inst.pc;
node.imm = imm;
for (uint32_t i = 0; i < inst.src_count && i < node.args.size(); i++) {
node.args[i] = OperandValue(inst.src[i], state);
}
return InternValue(std::move(node));
}
uint32_t DefineBorrow(const Instruction& inst, const ScalarState& state) {
ScalarValue node;
node.op = ScalarValueOp::Borrow;
node.pc = inst.pc;
node.args[0] =
inst.src_count > 0 ? OperandValue(inst.src[0], state) : ScalarProvenance::Unknown;
node.args[1] =
inst.src_count > 1 ? OperandValue(inst.src[1], state) : ScalarProvenance::Unknown;
node.args[2] = inst.src_count > 2 ? OperandValue(inst.src[2], state) : Constant(0);
return InternValue(std::move(node));
}
bool ConstantOperand(const Operand& operand, const ScalarState& state, uint32_t& value) {
const auto id = OperandValue(operand, state);
if (id >= m_graph.values.size() || m_graph.values[id].op != ScalarValueOp::Constant) {
return false;
}
value = m_graph.values[id].imm;
return true;
}
uint32_t ReadVectorLane(const Instruction& inst, const ScalarState& state) {
if (inst.src_count < 2 || inst.src[0].kind != OperandKind::Register ||
inst.src[0].reg.file != RegisterFile::Vector ||
(m_program.wave_size != 32 && m_program.wave_size != 64)) {
return ScalarProvenance::Unknown;
}
uint32_t lane = 0;
if (!ConstantOperand(inst.src[1], state, lane)) {
return ScalarProvenance::Unknown;
}
const auto found = state.vector_lanes.find(
VectorLaneKey(inst.src[0].reg.index, lane % m_program.wave_size));
return found != state.vector_lanes.end() ? found->second : ScalarProvenance::Unknown;
}
void ClearVectorLanes(uint32_t reg, ScalarState& state) {
const auto first = state.vector_lanes.lower_bound(VectorLaneKey(reg, 0));
const auto last = state.vector_lanes.lower_bound((static_cast<uint64_t>(reg) + 1u) << 32u);
state.vector_lanes.erase(first, last);
}
void WriteVectorDestination(const Instruction& inst, ScalarState& state) {
if (inst.dst.kind != OperandKind::Register || inst.dst.reg.file != RegisterFile::Vector) {
return;
}
const uint32_t reg = inst.dst.reg.index;
uint32_t address_base = ScalarProvenance::Unknown;
if (inst.op == Opcode::MoveU32 || inst.op == Opcode::IAddU32 ||
inst.op == Opcode::IAddCarryU32 || inst.op == Opcode::ISubU32) {
for (uint32_t i = 0; i < inst.src_count; i++) {
uint32_t src_reg = 0;
if (VectorRegister(inst.src[i], src_reg) &&
state.address_bases[src_reg] > ScalarProvenance::Unknown) {
address_base = state.address_bases[src_reg];
break;
}
}
if (address_base == ScalarProvenance::Unknown) {
for (uint32_t i = 0; i < inst.src_count; i++) {
uint32_t src_reg = 0;
if (ScalarRegister(inst.src[i], src_reg) &&
state.regs[src_reg] > ScalarProvenance::Unknown) {
address_base = state.regs[src_reg];
break;
}
}
}
}
state.address_bases[reg] = ScalarProvenance::Unknown;
if (inst.op == Opcode::MoveRelDestU32) {
state.vector_lanes.clear();
return;
}
if (inst.op != Opcode::WriteLaneU32) {
uint32_t dwords = std::max(inst.memory.data_dwords, 1u);
if (PairDwordOpcode(inst.op) || inst.op == Opcode::UMadU64U32) {
dwords = std::max(dwords, 2u);
}
for (uint32_t i = 0; i < dwords && reg <= UINT32_MAX - i; i++) {
ClearVectorLanes(reg + i, state);
if (reg + i < state.address_bases.size()) {
state.address_bases[reg + i] = ScalarProvenance::Unknown;
}
}
state.address_bases[reg] = address_base;
return;
}
if (inst.src_count < 2 || (m_program.wave_size != 32 && m_program.wave_size != 64)) {
ClearVectorLanes(reg, state);
return;
}
uint32_t lane = 0;
if (!ConstantOperand(inst.src[1], state, lane)) {
ClearVectorLanes(reg, state);
return;
}
state.vector_lanes[VectorLaneKey(reg, lane % m_program.wave_size)] =
OperandValue(inst.src[0], state);
}
uint32_t ReadConst(const Instruction& inst, const ScalarState& state, bool buffer) {
ScalarValue node;
node.op = buffer ? ScalarValueOp::ReadConstBuffer : ScalarValueOp::ReadConst;
node.pc = inst.pc;
const auto base = buffer ? inst.memory.resource * 4u : inst.memory.resource;
if (base + 1u >= ScalarRegisters) {
return ScalarProvenance::Unknown;
}
node.imm = inst.memory.offset;
if (buffer) {
if (base + 3u >= ScalarRegisters) {
return ScalarProvenance::Unknown;
}
for (uint32_t i = 0; i < 4; i++) {
node.args[i] = state.regs[base + i];
}
node.args[4] = inst.src_count != 0 ? OperandValue(inst.src[0], state) : Constant(0);
} else {
node.args[0] = state.regs[base];
node.args[1] = state.regs[base + 1u];
node.args[2] = inst.src_count != 0 ? OperandValue(inst.src[0], state) : Constant(0);
}
return InternValue(std::move(node));
}
ScalarValueOp Operation(Opcode op) const {
switch (op) {
case Opcode::IAddU32:
case Opcode::ScalarSignedAddOverflowI32: return ScalarValueOp::Add;
case Opcode::IAddCarryU32:
case Opcode::ScalarAddCarryU32: return ScalarValueOp::AddCarry;
case Opcode::ISubU32:
case Opcode::ScalarSubBorrowU32:
case Opcode::ScalarSignedSubOverflowI32: return ScalarValueOp::Sub;
case Opcode::ScalarSubBorrowCarryU32: return ScalarValueOp::SubBorrow;
case Opcode::IMulU32: return ScalarValueOp::Mul;
case Opcode::BitwiseAndU32: return ScalarValueOp::And;
case Opcode::BitwiseAndNotU32: return ScalarValueOp::AndNot;
case Opcode::BitwiseOrU32: return ScalarValueOp::Or;
case Opcode::BitwiseOrNotU32: return ScalarValueOp::OrNot;
case Opcode::BitwiseXorU32: return ScalarValueOp::Xor;
case Opcode::BitwiseNotU32: return ScalarValueOp::Not;
case Opcode::ShiftLeftLogicalU32: return ScalarValueOp::ShiftLeft;
case Opcode::ShiftRightLogicalU32: return ScalarValueOp::ShiftRight;
case Opcode::ShiftRightArithmeticI32: return ScalarValueOp::ShiftRightArithmetic;
case Opcode::BitFieldMaskU32: return ScalarValueOp::BitFieldMaskU32;
case Opcode::IAdd3U32: return ScalarValueOp::Add3;
case Opcode::ScalarShiftLeftAddCarryU32:
case Opcode::ShiftLeftAddU32: return ScalarValueOp::ShiftLeftAdd;
case Opcode::AddShiftLeftU32: return ScalarValueOp::AddShiftLeft;
case Opcode::XorAddU32: return ScalarValueOp::XorAdd;
case Opcode::ShiftLeftOrU32: return ScalarValueOp::ShiftLeftOr;
default: return ScalarValueOp::Unknown;
}
}
void UpdateScc(const Instruction& inst, const ScalarState& before, ScalarState& state) {
switch (inst.op) {
case Opcode::ScalarAddCarryU32:
state.scc = Define(inst, ScalarValueOp::Carry, before);
break;
case Opcode::ScalarSubBorrowU32:
case Opcode::ScalarSubBorrowCarryU32: state.scc = DefineBorrow(inst, before); break;
case Opcode::ScalarShiftLeftAddCarryU32:
state.scc = Define(inst, ScalarValueOp::ShiftLeftAddCarry, before);
break;
default: break;
}
}
void WriteDestination(const Instruction& inst, ScalarState& state) {
if (inst.dst.kind == OperandKind::Register && inst.dst.reg.file == RegisterFile::Scc) {
state.scc = inst.op == Opcode::MoveU32 && inst.src_count != 0
? OperandValue(inst.src[0], state)
: ScalarProvenance::Unknown;
return;
}
if (inst.dst.kind == OperandKind::Register && inst.dst.reg.file == RegisterFile::M0) {
const auto before = state;
const auto op = Operation(inst.op);
state.m0 = ScalarProvenance::Unknown;
if (inst.op == Opcode::MoveU32 && inst.src_count != 0) {
state.m0 = OperandValue(inst.src[0], before);
} else if (op != ScalarValueOp::Unknown) {
state.m0 = Define(inst, op, before);
}
UpdateScc(inst, before, state);
return;
}
uint32_t dst = 0;
if (!ScalarRegister(inst.dst, dst)) {
return;
}
const auto before = state;
state.regs[dst] = ScalarProvenance::Unknown;
if (PairDwordOpcode(inst.op) && dst + 1 < ScalarRegisters) {
state.regs[dst + 1] = ScalarProvenance::Unknown;
}
uint32_t value = ScalarProvenance::Unknown;
switch (inst.op) {
case Opcode::MoveU32:
case Opcode::MoveF32Bits:
if (inst.src_count != 0) {
if (inst.src[0].kind == OperandKind::PcRelativeU32) {
value =
InternValue({ScalarValueOp::PcRelativeLow, inst.pc, inst.src[0].imm});
} else {
value = OperandValue(inst.src[0], before);
}
}
if (inst.op == Opcode::MoveU32 && inst.src_count != 0 &&
inst.src[0].kind == OperandKind::ImmediateU32 && inst.src[0].imm == 0 &&
dst != 0) {
const auto low = before.regs[dst - 1];
if (low < m_graph.values.size() &&
m_graph.values[low].op == ScalarValueOp::PcRelativeLow &&
m_graph.values[low].pc == inst.pc) {
value = InternValue(
{ScalarValueOp::PcRelativeHigh, inst.pc, m_graph.values[low].imm});
}
}
break;
case Opcode::MoveU64: {
uint32_t src = 0;
if (inst.src_count != 0 && dst + 1 < ScalarRegisters) {
if (ScalarRegister(inst.src[0], src) && src + 1 < ScalarRegisters) {
value = before.regs[src];
state.regs[dst + 1] = before.regs[src + 1];
} else {
value = OperandValue(inst.src[0], before);
state.regs[dst + 1] = Constant(
inst.src[0].kind == OperandKind::ImmediateU32 && inst.src[0].sext_64
? UINT32_MAX
: 0u);
}
}
break;
}
case Opcode::BitFieldMaskU64:
value = Define(inst, ScalarValueOp::BitFieldMaskU64Low, before);
if (dst + 1 < ScalarRegisters) {
state.regs[dst + 1] = Define(inst, ScalarValueOp::BitFieldMaskU64High, before);
}
break;
case Opcode::SLoadDword: value = ReadConst(inst, before, false); break;
case Opcode::SBufferLoadDword: value = ReadConst(inst, before, true); break;
case Opcode::ReadLaneU32: value = ReadVectorLane(inst, before); break;
default: {
const auto op = Operation(inst.op);
if (op != ScalarValueOp::Unknown) {
value = Define(inst, op, before);
}
break;
}
}
state.regs[dst] = value;
UpdateScc(inst, before, state);
uint32_t dst2 = 0;
if (ScalarRegister(inst.dst2, dst2)) {
state.regs[dst2] =
inst.src_count > 1 ? OperandValue(inst.src[1], before) : ScalarProvenance::Unknown;
}
}
uint32_t AddDescriptor(DescriptorValue descriptor) {
const DescriptorKey key {descriptor.dwords, descriptor.dword_count};
if (const auto found = m_descriptors.find(key); found != m_descriptors.end()) {
return found->second;
}
m_graph.descriptors.push_back(descriptor);
const auto id = static_cast<uint32_t>(m_graph.descriptors.size() - 1) + 2u;
m_descriptors.emplace(key, id);
return id;
}
uint32_t AddDescriptor(const ScalarState& state, uint32_t base, uint32_t dwords) {
if (base >= ScalarRegisters || dwords == 0 || dwords > 8 ||
base + dwords > ScalarRegisters) {
return ScalarProvenance::Unknown;
}
DescriptorValue descriptor;
descriptor.dword_count = dwords;
for (uint32_t i = 0; i < dwords; i++) {
descriptor.dwords[i] = state.regs[base + i];
}
return AddDescriptor(descriptor);
}
uint32_t AddSamplerDescriptor(const ScalarState& state, uint32_t base) {
if (base >= ScalarRegisters || 4u > ScalarRegisters - base) {
return ScalarProvenance::Unknown;
}
DescriptorValue descriptor;
descriptor.dword_count = 4;
for (uint32_t i = 0; i < 4; i++) {
descriptor.dwords[i] = state.regs[base + i];
}
if (auto d0 = descriptor.dwords[0];
d0 < m_graph.values.size() && m_graph.values[d0].op == ScalarValueOp::Constant &&
(m_graph.values[d0].imm & SamplerBorderClampMask) == 0) {
// Without a border clamp, the border color and table index in dword 3 are unused.
descriptor.dwords[3] = Constant(0);
}
return AddDescriptor(descriptor);
}
uint32_t AddFlatAddressDescriptor(const Instruction& inst, const ScalarState& state) {
const uint32_t first = FlatStore(inst.op) ? 1u : 0u;
if (inst.src_count < first + 2u) {
return ScalarProvenance::Unknown;
}
uint32_t low = 0;
uint32_t high = 0;
if (!VectorRegister(inst.src[first], low) || !VectorRegister(inst.src[first + 1u], high) ||
state.address_bases[low] <= ScalarProvenance::Unknown ||
state.address_bases[high] <= ScalarProvenance::Unknown) {
return ScalarProvenance::Unknown;
}
DescriptorValue descriptor;
descriptor.dword_count = 2;
descriptor.dwords[0] = state.address_bases[low];
descriptor.dwords[1] = state.address_bases[high];
return AddDescriptor(descriptor);
}
void AttachSources(Instruction& inst, const ScalarState& state) {
inst.memory.resource_source = 0;
inst.memory.sampler_source = 0;
if (inst.op == Opcode::SLoadDword) {
inst.memory.resource_source = AddDescriptor(state, inst.memory.resource, 2);
return;
}
if (inst.memory.kind == ResourceKind::Flat || inst.memory.kind == ResourceKind::Global ||
inst.memory.kind == ResourceKind::Scratch) {
inst.memory.resource_source = ScalarProvenance::Unknown;
if (inst.memory.kind == ResourceKind::Flat) {
inst.memory.resource_source = AddFlatAddressDescriptor(inst, state);
}
for (uint32_t i = 0; i < inst.src_count; i++) {
const auto& source = inst.src[i];
if (inst.memory.resource_source == ScalarProvenance::Unknown &&
source.kind == OperandKind::Register &&
source.reg.file == RegisterFile::Scalar) {
inst.memory.resource_source = AddDescriptor(state, source.reg.index, 2);
break;
}
}
return;
}
if (inst.memory.kind == ResourceKind::Buffer ||
(inst.memory.kind == ResourceKind::ScalarBuffer &&
inst.op == Opcode::SBufferLoadDword)) {
const auto base = inst.memory.resource * 4u;
inst.memory.resource_source = AddDescriptor(state, base, 4);
return;
}
if (inst.memory.kind == ResourceKind::Image ||
inst.memory.kind == ResourceKind::ImageUint ||
inst.memory.kind == ResourceKind::StorageImage ||
inst.memory.kind == ResourceKind::StorageImageUint) {
inst.memory.resource_source = AddDescriptor(state, inst.memory.resource * 4u, 8);
if (inst.op == Opcode::ImageSample || inst.op == Opcode::ImageGather4 ||
inst.op == Opcode::ImageGetLod) {
inst.memory.sampler_source = AddSamplerDescriptor(state, inst.memory.sampler * 4u);
}
}
}
void AttachScalarSources(Instruction& inst, const ScalarState& state) {
std::fill(std::begin(inst.scalar_sources), std::end(inst.scalar_sources),
ScalarProvenance::Undefined);
for (uint32_t i = 0; i < inst.src_count; i++) {
const auto& source = inst.src[i];
if (source.kind == OperandKind::Register && source.reg.file != RegisterFile::Vector &&
source.reg.file != RegisterFile::Exec) {
inst.scalar_sources[i] = OperandValue(source, state);
}
}
}
ScalarState Execute(BasicBlock& block, ScalarState state, bool attach_sources) {
for (size_t index = 0; index < block.instructions.size();) {
auto& inst = block.instructions[index];
uint32_t first_dst = 0;
if (ScalarMemoryLoad(inst.op) && inst.memory.component_count > 1 &&
ScalarRegister(inst.dst, first_dst)) {
const auto before = state;
for (uint32_t component = 0; component < inst.memory.component_count; component++) {
auto& component_inst = block.instructions[index + component];
if (attach_sources) {
component_inst.scalar_value = ScalarProvenance::Undefined;
AttachScalarSources(component_inst, before);
AttachSources(component_inst, before);
}
auto component_state = before;
WriteDestination(component_inst, component_state);
uint32_t dst = 0;
ScalarRegister(component_inst.dst, dst);
state.regs[dst] = component_state.regs[dst];
if (attach_sources) {
component_inst.scalar_value = component_state.regs[dst];
}
}
index += inst.memory.component_count;
continue;
}
if (attach_sources) {
inst.scalar_value = ScalarProvenance::Undefined;
AttachScalarSources(inst, state);
AttachSources(inst, state);
}
WriteVectorDestination(inst, state);
WriteDestination(inst, state);
if (attach_sources) {
uint32_t dst = 0;
if (ScalarRegister(inst.dst, dst)) {
inst.scalar_value = state.regs[dst];
}
}
index++;
}
return state;
}
ScalarState MergeEntry(size_t block_index) {
ScalarState result;
result.Fill(ScalarProvenance::Undefined);
const auto& block = m_program.blocks[block_index];
const auto merge = [&](uint32_t initial, auto predecessor_value, uint32_t* phi) {
std::vector<uint32_t> incoming;
if (initial != ScalarProvenance::Undefined) {
incoming.push_back(initial);
}
for (const auto predecessor: block.predecessors) {
const auto value = predecessor_value(predecessor, m_exit[predecessor]);
if (value != ScalarProvenance::Undefined &&
std::find(incoming.begin(), incoming.end(), value) == incoming.end()) {
incoming.push_back(value);
}
}
if (incoming.empty()) {
return ScalarProvenance::Undefined;
}
if (*phi == ScalarProvenance::Undefined) {
if (incoming.size() == 1) {
return incoming[0];
}
*phi = AddValue({ScalarValueOp::Phi, block.start_pc});
}
m_graph.values[*phi].phi_args = std::move(incoming);
return *phi;
};
for (uint32_t reg = 0; reg < ScalarRegisters; reg++) {
const auto initial =
block_index == 0 ? m_user_data.regs[reg] : ScalarProvenance::Undefined;
result.regs[reg] = merge(
initial, [reg](uint32_t, const ScalarState& state) { return state.regs[reg]; },
&m_phi[block_index].regs[reg]);
}
for (uint32_t reg = 0; reg < VectorRegisters; reg++) {
result.address_bases[reg] = merge(
block_index == 0 ? ScalarProvenance::Unknown : ScalarProvenance::Undefined,
[reg](uint32_t, const ScalarState& state) { return state.address_bases[reg]; },
&m_phi[block_index].address_bases[reg]);
}
result.scc = merge(
block_index == 0 ? m_user_data.scc : ScalarProvenance::Undefined,
[](uint32_t, const ScalarState& state) { return state.scc; }, &m_phi[block_index].scc);
result.m0 = merge(
block_index == 0 ? m_user_data.m0 : ScalarProvenance::Undefined,
[](uint32_t, const ScalarState& state) { return state.m0; }, &m_phi[block_index].m0);
std::vector<uint64_t> vector_lanes;
for (const auto predecessor: block.predecessors) {
for (const auto& [lane, value]: m_exit[predecessor].vector_lanes) {
(void)value;
vector_lanes.push_back(lane);
}
}
std::sort(vector_lanes.begin(), vector_lanes.end());
vector_lanes.erase(std::unique(vector_lanes.begin(), vector_lanes.end()),
vector_lanes.end());
for (const auto lane: vector_lanes) {
auto& phi = m_phi[block_index].vector_lanes[lane];
result.vector_lanes[lane] = merge(
block_index == 0 ? ScalarProvenance::Unknown : ScalarProvenance::Undefined,
[this, lane](uint32_t predecessor, const ScalarState& state) {
if (!m_exit_ready[predecessor]) {
return ScalarProvenance::Undefined;
}
const auto found = state.vector_lanes.find(lane);
return found != state.vector_lanes.end() ? found->second
: ScalarProvenance::Unknown;
},
&phi);
}
return result;
}
void Queue(uint32_t block) {
if (!m_queued[block]) {
m_queued[block] = true;
m_work.push_back(block);
}
}
Program& m_program;
ScalarProvenance& m_graph;
ScalarState m_user_data;
std::vector<ScalarState> m_entry;
std::vector<ScalarState> m_exit;
std::vector<ScalarState> m_phi;
std::vector<bool> m_exit_ready;
std::vector<bool> m_queued;
std::deque<uint32_t> m_work;
std::map<ValueKey, uint32_t> m_values;
std::map<DescriptorKey, uint32_t> m_descriptors;
};
} // namespace
bool BuildScalarProvenance(Program& program, std::string* error) {
if (program.resource_tracking_complete) {
if (error != nullptr) {
*error = "cannot rebuild scalar provenance after resource tracking";
}
return false;
}
if (program.user_data_count > 64) {
if (error != nullptr) {
*error = "scalar provenance user-data count exceeds 64 SGPRs";
}
return false;
}
if (program.user_data_base > ScalarRegisters ||
program.user_data_count > ScalarRegisters - program.user_data_base) {
if (error != nullptr) {
*error = "scalar provenance user-data window is out of range";
}
return false;
}
if (program.srt_patching_complete) {
if (error != nullptr) {
*error = "cannot rebuild scalar provenance after SRT patching";
}
return false;
}
program.srt = {};
program.srt_plan_complete = false;
return Builder(program).Run(error);
}
const DescriptorValue* GetDescriptorSource(const Program& program, uint32_t source) {
if (source < 2 || source - 2 >= program.provenance.descriptors.size()) {
return nullptr;
}
return &program.provenance.descriptors[source - 2];
}
bool DescriptorSourceResolved(const Program& program, uint32_t source) {
const auto* descriptor = GetDescriptorSource(program, source);
if (descriptor == nullptr) {
return false;
}
std::vector<uint8_t> visited(program.provenance.values.size());
for (uint32_t i = 0; i < descriptor->dword_count; i++) {
if (!ValueResolved(program.provenance, descriptor->dwords[i], visited)) {
return false;
}
}
return true;
}
std::string ScalarValueToString(const ScalarProvenance& provenance, uint32_t value) {
if (value >= provenance.values.size()) {
return "invalid";
}
const auto& node = provenance.values[value];
switch (node.op) {
case ScalarValueOp::Undefined: return "undefined";
case ScalarValueOp::Unknown: return "unknown";
case ScalarValueOp::UserData: return fmt::format("ud{}", node.imm);
case ScalarValueOp::Constant: return fmt::format("0x{:08x}", node.imm);
case ScalarValueOp::PcRelativeLow: return fmt::format("pc_lo+0x{:x}", node.imm);
case ScalarValueOp::PcRelativeHigh: return fmt::format("pc_hi+0x{:x}", node.imm);
case ScalarValueOp::ReadConst:
return fmt::format("read_const({}, {}, {}, +{})", node.args[0], node.args[1],
node.args[2], node.imm);
case ScalarValueOp::ReadConstBuffer:
return fmt::format("read_const_buffer({}, {}, {}, {}, {}, +{})", node.args[0],
node.args[1], node.args[2], node.args[3], node.args[4], node.imm);
case ScalarValueOp::Phi: return fmt::format("phi{}", value);
case ScalarValueOp::BitFieldMaskU32:
return fmt::format("bfm_u32({}, {})", node.args[0], node.args[1]);
default: return fmt::format("value{}", value);
}
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -1,20 +0,0 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SCALARPROVENANCE_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SCALARPROVENANCE_H_
#include "graphics/shader/recompiler/ir/ShaderIR.h"
namespace Libs::Graphics::ShaderRecompiler::IR {
// Builds scalar reaching definitions and attaches descriptor sources to individual memory uses.
bool BuildScalarProvenance(Program& program, std::string* error);
uint32_t ScalarValueArgCount(ScalarValueOp op);
const DescriptorValue* GetDescriptorSource(const Program& program, uint32_t source);
bool DescriptorSourceResolved(const Program& program, uint32_t source);
std::string ScalarValueToString(const ScalarProvenance& provenance, uint32_t value);
} // namespace Libs::Graphics::ShaderRecompiler::IR
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SCALARPROVENANCE_H_ */
+27 -14
View File
@@ -32,6 +32,13 @@ Operand MakePcRelativeU32(uint32_t value) {
return operand;
}
Operand MakePcRelativeHighU32(uint32_t value) {
Operand operand;
operand.kind = OperandKind::PcRelativeHighU32;
operand.imm = value;
return operand;
}
bool AppendScalarResultSccNonZero(const Decoder::Instruction& decoded, BasicBlock& block,
std::string* error) {
if (!ScalarResultWritesSccNonZero(decoded.opcode)) {
@@ -74,6 +81,7 @@ uint32_t VectorByteConvertIndex(Decoder::Opcode opcode) {
void CopyOperandModifiers(const Decoder::Operand& decoded, Operand& operand) {
operand.sdwa_sel = decoded.sdwa_sel;
operand.sdwa_dst_unused = decoded.sdwa_dst_unused;
operand.omod = decoded.omod;
operand.sdwa_sext = decoded.sdwa_sext;
operand.op_sel = decoded.op_sel;
@@ -214,17 +222,20 @@ MemoryInfo MemoryInfoFromDecoded(const Decoder::Instruction& decoded, ResourceKi
mem.image_nsa_addr[i] = decoded.image_nsa_addr[i];
}
mem.memory_segment = decoded.memory_segment;
mem.data_signed = decoded.data_signed;
mem.typed = decoded.typed;
mem.formatted = decoded.formatted;
mem.image_has_mip = decoded.opcode == Decoder::Opcode::ImageLoadMip ||
decoded.opcode == Decoder::Opcode::ImageStoreMip;
mem.glc = decoded.glc;
mem.slc = decoded.slc;
mem.idxen = decoded.idxen;
mem.offen = decoded.offen;
mem.resource = ResourceIndexFromOperand(decoded.src1);
mem.sampler = ResourceIndexFromOperand(decoded.src2);
mem.address_is_full =
kind == ResourceKind::Flat || kind == ResourceKind::Scratch ||
(kind == ResourceKind::Global && decoded.src1.kind == Decoder::OperandKind::Vgpr);
mem.data_signed = decoded.data_signed;
mem.typed = decoded.typed;
mem.formatted = decoded.formatted;
mem.image_has_mip = decoded.opcode == Decoder::Opcode::ImageLoadMip ||
decoded.opcode == Decoder::Opcode::ImageStoreMip;
mem.glc = decoded.glc;
mem.slc = decoded.slc;
mem.idxen = decoded.idxen;
mem.offen = decoded.offen;
mem.resource = ResourceIndexFromOperand(decoded.src1);
mem.sampler = ResourceIndexFromOperand(decoded.src2);
if (kind == ResourceKind::Lds || kind == ResourceKind::Gds) {
mem.resource = 0;
mem.sampler = 0;
@@ -246,6 +257,7 @@ uint32_t RawScalarLoadBase(const Decoder::Operand& operand) {
void ClearRegisterOffsetModifiers(Decoder::Operand& operand) {
operand.sdwa_sel = 6;
operand.sdwa_dst_unused = 2;
operand.omod = 0;
operand.sdwa_sext = false;
operand.op_sel = false;
@@ -988,9 +1000,10 @@ bool LowerScalarGetpcB64(const Decoder::Instruction& decoded, BasicBlock& block,
std::string* error) {
for (uint32_t i = 0; i < 2u; i++) {
Instruction inst;
inst.pc = decoded.pc;
inst.op = Opcode::MoveU32;
inst.src[0] = i == 0u ? MakePcRelativeU32(decoded.pc + 4u) : MakeImmediateU32(0u);
inst.pc = decoded.pc;
inst.op = Opcode::MoveU32;
inst.src[0] =
i == 0u ? MakePcRelativeU32(decoded.pc + 4u) : MakePcRelativeHighU32(decoded.pc + 4u);
inst.src_count = 1;
if (!LowerRegisterOperand(OffsetDecodedRegister(decoded.dst, i), inst.dst, error)) {
return false;
+38 -148
View File
@@ -4,52 +4,19 @@
#include "common/common.h"
#include "common/stringUtils.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/shader/recompiler/cfg/ShaderCFG.h"
#include "graphics/shader/recompiler/decompiler/ShaderDecoder.h"
#include "graphics/shader/recompiler/frontend/cfg/ShaderCFG.h"
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
#include "graphics/shader/recompiler/ir/opcodes/Opcodes.h"
#include "graphics/shader/shader.h"
#include <array>
#include <memory>
#include <string_view>
#include <vector>
namespace Libs::Graphics::ShaderRecompiler::IR {
enum class Opcode {
#define IR_OPCODE(name, category) name,
#include "graphics/shader/recompiler/ir/ShaderIROpcodes.inc"
#undef IR_OPCODE
Count,
};
enum class OpcodeClass { General, Compare, Compare64 };
struct OpcodeInfo {
std::string_view name;
OpcodeClass opcode_class = OpcodeClass::General;
};
inline constexpr OpcodeInfo OPCODE_INFO[] = {
#define IR_OPCODE(name, category) {#name, OpcodeClass::category},
#include "graphics/shader/recompiler/ir/ShaderIROpcodes.inc"
#undef IR_OPCODE
};
static_assert(sizeof(OPCODE_INFO) / sizeof(OPCODE_INFO[0]) == static_cast<size_t>(Opcode::Count));
constexpr const OpcodeInfo& GetOpcodeInfo(Opcode opcode) {
return OPCODE_INFO[static_cast<size_t>(opcode)];
}
constexpr std::string_view OpcodeName(Opcode opcode) {
return GetOpcodeInfo(opcode).name;
}
constexpr bool IsCompareOpcode(Opcode opcode) {
return GetOpcodeInfo(opcode).opcode_class != OpcodeClass::General;
}
constexpr bool IsCompare64Opcode(Opcode opcode) {
return GetOpcodeInfo(opcode).opcode_class == OpcodeClass::Compare64;
}
struct ValueProgram;
enum class RegisterFile { Scalar, Vector, Vcc, Exec, Scc, M0 };
@@ -62,7 +29,7 @@ struct Register {
}
};
enum class OperandKind { Register, ImmediateU32, PcRelativeU32, Null };
enum class OperandKind { Register, ImmediateU32, PcRelativeU32, PcRelativeHighU32, Null };
struct Operand {
OperandKind kind = OperandKind::Null;
@@ -70,6 +37,7 @@ struct Operand {
uint32_t imm = 0;
bool sext_64 = false;
uint32_t sdwa_sel = 6;
uint32_t sdwa_dst_unused = 2;
uint32_t omod = 0;
bool sdwa_sext = false;
bool op_sel = false;
@@ -123,19 +91,17 @@ struct MemoryInfo {
uint32_t image_nsa_dwords = 0;
uint32_t image_nsa_addr[Decoder::MaxImageNsaAddressComponents] = {};
uint32_t memory_segment = 0;
// Dense resource indices are assigned by resource tracking. These source IDs refer to the
// exact scalar definitions reaching this instruction before that patching step.
uint32_t resource_source = 0;
uint32_t sampler_source = 0;
bool data_signed = false;
bool typed = false;
bool formatted = false;
bool image_has_mip = false;
bool image_cube = false;
bool glc = false;
bool slc = false;
bool idxen = false;
bool offen = false;
bool address_is_full = false;
bool data_signed = false;
bool typed = false;
bool formatted = false;
bool image_has_mip = false;
bool image_cube = false;
bool glc = false;
bool slc = false;
bool idxen = false;
bool offen = false;
bool planning_only = false;
bool operator==(const MemoryInfo& other) const = default;
};
@@ -169,9 +135,7 @@ struct Instruction {
Operand dst;
Operand dst2;
Operand src[4];
uint32_t src_count = 0;
uint32_t scalar_sources[4] = {};
uint32_t scalar_value = 0;
uint32_t src_count = 0;
MemoryInfo memory;
ExportInfo export_info;
InputInfo input_info;
@@ -192,53 +156,6 @@ struct BasicBlock {
std::vector<Instruction> instructions;
};
enum class ScalarValueOp {
Undefined,
Unknown,
UserData,
Constant,
PcRelativeLow,
PcRelativeHigh,
Add,
AddCarry,
Carry,
Sub,
SubBorrow,
Borrow,
Mul,
And,
AndNot,
Or,
OrNot,
Xor,
Not,
ShiftLeft,
ShiftRight,
ShiftRightArithmetic,
BitFieldMaskU32,
BitFieldMaskU64Low,
BitFieldMaskU64High,
Add3,
ShiftLeftAdd,
ShiftLeftAddCarry,
AddShiftLeft,
XorAdd,
ShiftLeftOr,
ReadConst,
ReadConstBuffer,
Phi,
};
struct ScalarValue {
ScalarValueOp op = ScalarValueOp::Undefined;
uint32_t pc = 0;
uint32_t imm = 0;
std::array<uint32_t, 6> args = {};
std::vector<uint32_t> phi_args;
bool operator==(const ScalarValue& other) const = default;
};
struct DescriptorValue {
std::array<uint32_t, 8> dwords = {};
uint32_t dword_count = 0;
@@ -248,32 +165,6 @@ struct DescriptorValue {
}
};
struct ScalarProvenance {
static constexpr uint32_t Undefined = 0;
static constexpr uint32_t Unknown = 1;
std::vector<ScalarValue> values;
std::vector<DescriptorValue> descriptors;
bool operator==(const ScalarProvenance& other) const = default;
};
struct SrtRead {
uint32_t value = 0;
uint32_t flat_offset = 0;
uint32_t use_pc = 0;
bool operator==(const SrtRead& other) const = default;
};
struct SrtPlan {
std::vector<SrtRead> reads;
std::vector<uint32_t> dynamic_reads;
std::vector<uint32_t> dynamic_sources;
bool operator==(const SrtPlan& other) const = default;
};
struct BufferResource {
static constexpr uint32_t NoImageAlias = UINT32_MAX;
@@ -328,11 +219,12 @@ struct SampledResourcePair {
};
struct AddressResource {
uint32_t source = 0;
uint32_t source = UINT32_MAX;
uint32_t first_use_pc = 0;
ResourceKind kind = ResourceKind::Flat;
int32_t min_offset = 0;
uint64_t specialized_base = 0;
bool unbased = true;
bool read = false;
bool written = false;
bool atomic = false;
@@ -451,25 +343,23 @@ struct ShaderInfo {
};
struct Program {
ShaderType stage = ShaderType::Unknown;
ShaderLaneMaskMode lane_mask_mode = ShaderLaneMaskMode::NativeWave;
uint64_t shader_hash = 0;
uint32_t wave_size = 64;
uint32_t user_data_base = 0;
uint32_t user_data_count = 64;
bool dispatcher_fallback = false;
CFG::FailureKind cfg_failure_kind = CFG::FailureKind::None;
std::string fallback_reason;
std::vector<BasicBlock> blocks;
ScalarProvenance provenance;
SrtPlan srt;
bool srt_plan_complete = false;
bool srt_patching_complete = false;
ShaderInfo info;
bool resource_tracking_complete = false;
bool shader_info_complete = false;
BindingLayout bindings;
bool binding_layout_complete = false;
ShaderType stage = ShaderType::Unknown;
ShaderLaneMaskMode lane_mask_mode = ShaderLaneMaskMode::NativeWave;
uint64_t shader_hash = 0;
uint32_t wave_size = 64;
uint32_t user_data_base = 0;
uint32_t user_data_count = 64;
bool dispatcher_fallback = false;
CFG::FailureKind cfg_failure_kind = CFG::FailureKind::None;
std::string fallback_reason;
std::vector<BasicBlock> blocks;
std::shared_ptr<ValueProgram> values;
bool srt_plan_complete = false;
ShaderInfo info;
bool resource_tracking_complete = false;
bool shader_info_complete = false;
BindingLayout bindings;
bool binding_layout_complete = false;
};
bool LowerProgram(const Decoder::Program& decoded, const CFG::Graph& cfg, ShaderType stage,
@@ -18,6 +18,7 @@ bool ScalarResultIs64Bit(Decoder::Opcode opcode);
Operand MakeSccOperand();
Operand MakeImmediateU32(uint32_t value);
Operand MakePcRelativeU32(uint32_t value);
Operand MakePcRelativeHighU32(uint32_t value);
bool AppendScalarResultSccNonZero(const Decoder::Instruction& decoded, BasicBlock& block,
std::string* error);
uint32_t VectorByteConvertIndex(Decoder::Opcode opcode);
@@ -39,6 +39,9 @@ std::string OperandToString(const Operand& operand) {
case OperandKind::Register: text = RegisterToString(operand.reg); break;
case OperandKind::ImmediateU32: text = fmt::format("0x{:08x}", operand.imm); break;
case OperandKind::PcRelativeU32: text = fmt::format("pc_rel(0x{:08x})", operand.imm); break;
case OperandKind::PcRelativeHighU32:
text = fmt::format("pc_rel_hi(0x{:08x})", operand.imm);
break;
case OperandKind::Null: text = "null"; break;
default: text = "unknown"; break;
}
@@ -1,342 +0,0 @@
// Central IR opcode catalog. Keep semantic categories here so enum identity, logging,
// and generic analysis cannot drift apart.
IR_OPCODE(ControlNop, General)
IR_OPCODE(Waitcnt, General)
IR_OPCODE(Barrier, General)
IR_OPCODE(Sendmsg, General)
IR_OPCODE(TtraceData, General)
IR_OPCODE(InstPrefetch, General)
IR_OPCODE(MoveU32, General)
IR_OPCODE(MoveF32Bits, General)
IR_OPCODE(MoveRelDestU32, General)
IR_OPCODE(MoveRelSourceU32, General)
IR_OPCODE(MoveU64, General)
IR_OPCODE(WqmB64, General)
IR_OPCODE(SaveexecB32, General)
IR_OPCODE(SaveexecB64, General)
IR_OPCODE(ReadFirstLaneU32, General)
IR_OPCODE(ReadLaneU32, General)
IR_OPCODE(WriteLaneU32, General)
IR_OPCODE(Permlane16B32, General)
IR_OPCODE(Permlanex16B32, General)
IR_OPCODE(AbsI32, General)
IR_OPCODE(IAddU32, General)
IR_OPCODE(IAddCarryU32, General)
IR_OPCODE(ISubBorrowU32, General)
IR_OPCODE(ScalarAddCarryU32, General)
IR_OPCODE(ScalarSubBorrowU32, General)
IR_OPCODE(ScalarSubBorrowCarryU32, General)
IR_OPCODE(ScalarSignedAddOverflowI32, General)
IR_OPCODE(ScalarSignedSubOverflowI32, General)
IR_OPCODE(ScalarShiftLeftAddCarryU32, General)
IR_OPCODE(ISubU32, General)
IR_OPCODE(IMulU32, General)
IR_OPCODE(UMulHighU32, General)
IR_OPCODE(SMulHighI32, General)
IR_OPCODE(IMadI24U32, General)
IR_OPCODE(UMadU24U32, General)
IR_OPCODE(UMadU64U32, General)
IR_OPCODE(SadU32, General)
IR_OPCODE(IAdd3U32, General)
IR_OPCODE(IMulI24U32, General)
IR_OPCODE(UMulU24U32, General)
IR_OPCODE(IMinI32, General)
IR_OPCODE(IMaxI32, General)
IR_OPCODE(IMin3I32, General)
IR_OPCODE(IMax3I32, General)
IR_OPCODE(IMed3I32, General)
IR_OPCODE(UMinU32, General)
IR_OPCODE(UMaxU32, General)
IR_OPCODE(UMin3U32, General)
IR_OPCODE(UMax3U32, General)
IR_OPCODE(UMed3U32, General)
IR_OPCODE(BitwiseAndU32, General)
IR_OPCODE(BitwiseAndU64, General)
IR_OPCODE(BitwiseAndNotU32, General)
IR_OPCODE(BitwiseAndNotU64, General)
IR_OPCODE(BitwiseOrU32, General)
IR_OPCODE(BitwiseOrU64, General)
IR_OPCODE(BitwiseOrNotU32, General)
IR_OPCODE(BitwiseOrNotU64, General)
IR_OPCODE(BitwiseAndOrU32, General)
IR_OPCODE(BitwiseOr3U32, General)
IR_OPCODE(BitwiseXorU32, General)
IR_OPCODE(BitwiseXorU64, General)
IR_OPCODE(BitwiseXor3U32, General)
IR_OPCODE(BitwiseNandU32, General)
IR_OPCODE(BitwiseNandU64, General)
IR_OPCODE(BitwiseNorU32, General)
IR_OPCODE(BitwiseNorU64, General)
IR_OPCODE(BitwiseXnorU32, General)
IR_OPCODE(BitwiseXnorU64, General)
IR_OPCODE(BitwiseNotU32, General)
IR_OPCODE(BitwiseNotU64, General)
IR_OPCODE(BitClearU32, General)
IR_OPCODE(BitSetU32, General)
IR_OPCODE(BitReverseU32, General)
IR_OPCODE(BitCountU32, General)
IR_OPCODE(BitCountU64, General)
IR_OPCODE(BitReplicateB64B32, General)
IR_OPCODE(BitCountAddU32, General)
IR_OPCODE(MaskedBitCountLowU32, General)
IR_OPCODE(MaskedBitCountHighU32, General)
IR_OPCODE(FindLsbU32, General)
IR_OPCODE(FindMsbFromHighU32, General)
IR_OPCODE(FindMsbFromHighU64, General)
IR_OPCODE(BitFieldMaskU32, General)
IR_OPCODE(BitFieldMaskU64, General)
IR_OPCODE(BitFieldExtractU32, General)
IR_OPCODE(BitFieldExtractU64, General)
IR_OPCODE(BitFieldExtract3U32, General)
IR_OPCODE(BitFieldExtract3I32, General)
IR_OPCODE(BitFieldInsertSelectU32, General)
IR_OPCODE(BitCompare0B32, General)
IR_OPCODE(BitCompare1B32, General)
IR_OPCODE(AlignBitU32, General)
IR_OPCODE(ShiftLeftAddU32, General)
IR_OPCODE(AddShiftLeftU32, General)
IR_OPCODE(XorAddU32, General)
IR_OPCODE(ShiftLeftOrU32, General)
IR_OPCODE(ShiftLeftLogicalU32, General)
IR_OPCODE(ShiftLeftLogicalU64, General)
IR_OPCODE(ShiftLeftLogicalU16, General)
IR_OPCODE(ShiftRightLogicalU32, General)
IR_OPCODE(ShiftRightLogicalU64, General)
IR_OPCODE(ShiftRightLogicalU16, General)
IR_OPCODE(ShiftRightArithmeticI32, General)
IR_OPCODE(ShiftRightArithmeticI16, General)
IR_OPCODE(SelectU32, General)
IR_OPCODE(SelectMaskU32, General)
IR_OPCODE(SelectF32Bits, General)
IR_OPCODE(SelectMaskF32Bits, General)
IR_OPCODE(SelectU64, General)
IR_OPCODE(PackLowLowU16, General)
IR_OPCODE(PackLowHighU16, General)
IR_OPCODE(PackHighHighU16, General)
IR_OPCODE(CompareFalse, Compare)
IR_OPCODE(CompareTrue, Compare)
IR_OPCODE(CompareEqU32, Compare)
IR_OPCODE(CompareNeU32, Compare)
IR_OPCODE(CompareGtU32, Compare)
IR_OPCODE(CompareGeU32, Compare)
IR_OPCODE(CompareLtU32, Compare)
IR_OPCODE(CompareLeU32, Compare)
IR_OPCODE(CompareEqU64, Compare64)
IR_OPCODE(CompareNeU64, Compare64)
IR_OPCODE(CompareMaskEqU32, Compare)
IR_OPCODE(CompareMaskNeU32, Compare)
IR_OPCODE(CompareMaskGtU32, Compare)
IR_OPCODE(CompareMaskGeU32, Compare)
IR_OPCODE(CompareMaskLtU32, Compare)
IR_OPCODE(CompareMaskLeU32, Compare)
IR_OPCODE(CompareEqI32, Compare)
IR_OPCODE(CompareNeI32, Compare)
IR_OPCODE(CompareGtI32, Compare)
IR_OPCODE(CompareGeI32, Compare)
IR_OPCODE(CompareLtI32, Compare)
IR_OPCODE(CompareLeI32, Compare)
IR_OPCODE(CompareEqI16, Compare)
IR_OPCODE(CompareNeI16, Compare)
IR_OPCODE(CompareGtI16, Compare)
IR_OPCODE(CompareGeI16, Compare)
IR_OPCODE(CompareLtI16, Compare)
IR_OPCODE(CompareLeI16, Compare)
IR_OPCODE(CompareMaskEqI32, Compare)
IR_OPCODE(CompareMaskNeI32, Compare)
IR_OPCODE(CompareMaskGtI32, Compare)
IR_OPCODE(CompareMaskGeI32, Compare)
IR_OPCODE(CompareMaskLtI32, Compare)
IR_OPCODE(CompareMaskLeI32, Compare)
IR_OPCODE(CompareEqU16, Compare)
IR_OPCODE(CompareNeU16, Compare)
IR_OPCODE(CompareGtU16, Compare)
IR_OPCODE(CompareGeU16, Compare)
IR_OPCODE(CompareLtU16, Compare)
IR_OPCODE(CompareLeU16, Compare)
IR_OPCODE(CompareEqF32, Compare)
IR_OPCODE(CompareNeF32, Compare)
IR_OPCODE(CompareGtF32, Compare)
IR_OPCODE(CompareGeF32, Compare)
IR_OPCODE(CompareLtF32, Compare)
IR_OPCODE(CompareLeF32, Compare)
IR_OPCODE(CompareOrderedF32, Compare)
IR_OPCODE(CompareUnorderedF32, Compare)
IR_OPCODE(CompareUnordEqF32, Compare)
IR_OPCODE(CompareUnordNeF32, Compare)
IR_OPCODE(CompareUnordGtF32, Compare)
IR_OPCODE(CompareUnordGeF32, Compare)
IR_OPCODE(CompareUnordLtF32, Compare)
IR_OPCODE(CompareUnordLeF32, Compare)
IR_OPCODE(CompareClassF32, Compare)
IR_OPCODE(CompareEqF16, Compare)
IR_OPCODE(CompareNeF16, Compare)
IR_OPCODE(CompareGtF16, Compare)
IR_OPCODE(CompareGeF16, Compare)
IR_OPCODE(CompareLtF16, Compare)
IR_OPCODE(CompareLeF16, Compare)
IR_OPCODE(CompareUnordNeF16, Compare)
IR_OPCODE(CompareMaskEqF16, Compare)
IR_OPCODE(CompareMaskNeF16, Compare)
IR_OPCODE(CompareMaskGtF16, Compare)
IR_OPCODE(CompareMaskGeF16, Compare)
IR_OPCODE(CompareMaskLtF16, Compare)
IR_OPCODE(CompareMaskLeF16, Compare)
IR_OPCODE(CompareMaskUnordNeF16, Compare)
IR_OPCODE(CompareMaskUnordGeF16, Compare)
IR_OPCODE(CompareMaskEqF32, Compare)
IR_OPCODE(CompareMaskNeF32, Compare)
IR_OPCODE(CompareMaskGtF32, Compare)
IR_OPCODE(CompareMaskGeF32, Compare)
IR_OPCODE(CompareMaskLtF32, Compare)
IR_OPCODE(CompareMaskLeF32, Compare)
IR_OPCODE(CompareMaskUnordEqF32, Compare)
IR_OPCODE(CompareMaskUnordNeF32, Compare)
IR_OPCODE(CompareMaskUnordGtF32, Compare)
IR_OPCODE(CompareMaskUnordGeF32, Compare)
IR_OPCODE(CompareMaskUnordLtF32, Compare)
IR_OPCODE(CompareMaskUnordLeF32, Compare)
IR_OPCODE(ConvertByteU32ToF32, General)
IR_OPCODE(ConvertU32ToF32, General)
IR_OPCODE(ConvertI32ToF32, General)
IR_OPCODE(ConvertF32ToU32, General)
IR_OPCODE(ConvertF32ToI32, General)
IR_OPCODE(ConvertF32ToF16, General)
IR_OPCODE(ConvertF16ToF32, General)
IR_OPCODE(ConvertU16ToF16, General)
IR_OPCODE(ConvertF16ToU16, General)
IR_OPCODE(ConvertI16ToF16, General)
IR_OPCODE(ConvertF16ToI16, General)
IR_OPCODE(ConvertRoundPlusInfF32ToI32, General)
IR_OPCODE(ConvertFloorF32ToI32, General)
IR_OPCODE(ConvertI4ToOffsetF32, General)
IR_OPCODE(LdexpF32, General)
IR_OPCODE(PackF32ToF16Rtz, General)
IR_OPCODE(PackSnorm2x16F32, General)
IR_OPCODE(PackUnorm2x16F32, General)
IR_OPCODE(PackU16U32, General)
IR_OPCODE(PackU8F32, General)
IR_OPCODE(PackB32F16, General)
IR_OPCODE(PackedMadI16, General)
IR_OPCODE(PackedMulLoU16, General)
IR_OPCODE(PackedAddI16, General)
IR_OPCODE(PackedSubI16, General)
IR_OPCODE(PackedLshlrevB16, General)
IR_OPCODE(PackedLshrrevB16, General)
IR_OPCODE(PackedAshrrevI16, General)
IR_OPCODE(PackedMaxI16, General)
IR_OPCODE(PackedMinI16, General)
IR_OPCODE(PackedMadU16, General)
IR_OPCODE(PackedAddU16, General)
IR_OPCODE(PackedSubU16, General)
IR_OPCODE(PackedMaxU16, General)
IR_OPCODE(PackedMinU16, General)
IR_OPCODE(PackedAddF16, General)
IR_OPCODE(PackedMulF16, General)
IR_OPCODE(PackedMinF16, General)
IR_OPCODE(PackedMaxF16, General)
IR_OPCODE(PackedFmaF16, General)
IR_OPCODE(AddF16, General)
IR_OPCODE(SubF16, General)
IR_OPCODE(MulF16, General)
IR_OPCODE(MinF16, General)
IR_OPCODE(MaxF16, General)
IR_OPCODE(FmaF16, General)
IR_OPCODE(MadMixF16, General)
IR_OPCODE(IAddU16, General)
IR_OPCODE(ISubI16, General)
IR_OPCODE(IMinI16, General)
IR_OPCODE(IMaxI16, General)
IR_OPCODE(UMinU16, General)
IR_OPCODE(UMaxU16, General)
IR_OPCODE(RcpF32, General)
IR_OPCODE(FractF32, General)
IR_OPCODE(TruncF32, General)
IR_OPCODE(CeilF32, General)
IR_OPCODE(RoundEvenF32, General)
IR_OPCODE(FloorF32, General)
IR_OPCODE(Exp2F32, General)
IR_OPCODE(Log2F32, General)
IR_OPCODE(InverseSqrtF32, General)
IR_OPCODE(SqrtF32, General)
IR_OPCODE(RcpF16, General)
IR_OPCODE(SqrtF16, General)
IR_OPCODE(InverseSqrtF16, General)
IR_OPCODE(Log2F16, General)
IR_OPCODE(Exp2F16, General)
IR_OPCODE(FloorF16, General)
IR_OPCODE(CeilF16, General)
IR_OPCODE(TruncF16, General)
IR_OPCODE(RoundEvenF16, General)
IR_OPCODE(SinF32, General)
IR_OPCODE(CosF32, General)
IR_OPCODE(CubeIdF32, General)
IR_OPCODE(CubeScF32, General)
IR_OPCODE(CubeTcF32, General)
IR_OPCODE(CubeMaF32, General)
IR_OPCODE(FAddF32, General)
IR_OPCODE(FSubF32, General)
IR_OPCODE(FMulF32, General)
IR_OPCODE(FMinF32, General)
IR_OPCODE(FMaxF32, General)
IR_OPCODE(FMadF32, General)
IR_OPCODE(Dot2AccF32F16, General)
IR_OPCODE(FMin3F32, General)
IR_OPCODE(FMax3F32, General)
IR_OPCODE(FMed3F32, General)
IR_OPCODE(Min3F16, General)
IR_OPCODE(Max3F16, General)
IR_OPCODE(Med3F16, General)
IR_OPCODE(LoadSrtDword, General)
IR_OPCODE(SLoadDword, General)
IR_OPCODE(SBufferLoadDword, General)
IR_OPCODE(BufferLoadUbyte, General)
IR_OPCODE(BufferLoadSbyte, General)
IR_OPCODE(BufferLoadUshort, General)
IR_OPCODE(BufferLoadSshort, General)
IR_OPCODE(BufferLoadDword, General)
IR_OPCODE(BufferStoreByte, General)
IR_OPCODE(BufferStoreShort, General)
IR_OPCODE(BufferStoreDword, General)
IR_OPCODE(AtomicSwapU32, General)
IR_OPCODE(AtomicAddU32, General)
IR_OPCODE(AtomicSubU32, General)
IR_OPCODE(AtomicSMinI32, General)
IR_OPCODE(AtomicUMinU32, General)
IR_OPCODE(AtomicSMaxI32, General)
IR_OPCODE(AtomicUMaxU32, General)
IR_OPCODE(AtomicAndU32, General)
IR_OPCODE(AtomicOrU32, General)
IR_OPCODE(AtomicXorU32, General)
IR_OPCODE(AtomicFMinF32, General)
IR_OPCODE(AtomicFMaxF32, General)
IR_OPCODE(FlatLoadUbyte, General)
IR_OPCODE(FlatLoadSbyte, General)
IR_OPCODE(FlatLoadUshort, General)
IR_OPCODE(FlatLoadSshort, General)
IR_OPCODE(FlatLoadDword, General)
IR_OPCODE(FlatStoreByte, General)
IR_OPCODE(FlatStoreShort, General)
IR_OPCODE(FlatStoreDword, General)
IR_OPCODE(DsReadUbyte, General)
IR_OPCODE(DsReadSbyte, General)
IR_OPCODE(DsReadUshort, General)
IR_OPCODE(DsReadSshort, General)
IR_OPCODE(DsReadB32, General)
IR_OPCODE(DsWriteByte, General)
IR_OPCODE(DsWriteShort, General)
IR_OPCODE(DsWriteB32, General)
IR_OPCODE(DsMinF32, General)
IR_OPCODE(DsMaxF32, General)
IR_OPCODE(DsSwizzleB32, General)
IR_OPCODE(DsConsume, General)
IR_OPCODE(DsAppend, General)
IR_OPCODE(DsWriteAddtidB32, General)
IR_OPCODE(DsReadAddtidB32, General)
IR_OPCODE(ImageGetResinfo, General)
IR_OPCODE(ImageGetLod, General)
IR_OPCODE(ImageLoad, General)
IR_OPCODE(ImageStore, General)
IR_OPCODE(ImageSample, General)
IR_OPCODE(ImageGather4, General)
IR_OPCODE(LoadInputF32, General)
IR_OPCODE(Export, General)
@@ -1,235 +0,0 @@
#include "graphics/shader/recompiler/ir/ShaderInfoCollection.h"
#include <algorithm>
#include <fmt/format.h>
namespace Libs::Graphics::ShaderRecompiler::IR {
namespace {
bool HasInput(const ShaderInfo& info, StageInputKind kind, uint32_t location) {
return std::any_of(info.inputs.begin(), info.inputs.end(), [=](const auto& input) {
return input.kind == kind && input.location == location;
});
}
void AddInput(ShaderInfo& info, StageInputKind kind, uint32_t location, uint32_t components,
std::string name) {
if (!HasInput(info, kind, location)) {
info.inputs.push_back({kind, location, components, std::move(name)});
}
}
bool HasOutput(const ShaderInfo& info, StageOutputKind kind, uint32_t index) {
return std::any_of(info.outputs.begin(), info.outputs.end(), [=](const auto& output) {
return output.kind == kind && output.index == index;
});
}
void AddOutput(ShaderInfo& info, StageOutputKind kind, uint32_t index, uint32_t location,
std::string name) {
if (!HasOutput(info, kind, index)) {
info.outputs.push_back({kind, index, location, std::move(name)});
}
}
bool NeedsLocalInvocationIndex(const Program& program) {
for (const auto& block: program.blocks) {
for (const auto& inst: block.instructions) {
if (inst.op == Opcode::DsWriteAddtidB32 || inst.op == Opcode::DsReadAddtidB32) {
return true;
}
}
}
return false;
}
bool ValidateOptions(const Program& program, const ShaderInfoOptions& options, std::string* error) {
auto Fail = [&](const char* message) {
if (error != nullptr) {
*error = message;
}
return false;
};
switch (program.stage) {
case ShaderType::Vertex:
if (options.vertex == nullptr) {
return Fail("vertex shader info requires vertex metadata");
}
if (options.vertex->resources_num < 0 ||
options.vertex->resources_num > ShaderVertexInputInfo::RES_MAX) {
return Fail("vertex resource count is out of range");
}
for (const auto& block: program.blocks) {
for (const auto& inst: block.instructions) {
if (inst.op == Opcode::LoadInputF32 &&
(inst.input_info.chan >= 4 ||
inst.input_info.attr >=
static_cast<uint32_t>(options.vertex->resources_num))) {
return Fail("vertex input reference is out of range");
}
}
}
return true;
case ShaderType::Pixel:
if (options.pixel == nullptr) {
return Fail("pixel shader info requires pixel metadata");
}
return options.pixel->input_num <= std::size(options.pixel->interpolator_settings) ||
Fail("pixel input count is out of range");
case ShaderType::Compute:
if (options.compute == nullptr) {
return Fail("compute shader info requires compute metadata");
}
return (options.compute->thread_ids_num >= 0 && options.compute->thread_ids_num <= 3) ||
Fail("compute thread ID count is out of range");
default: return Fail("unsupported shader stage for info collection");
}
}
void CollectVertexInputs(const Program& program, const ShaderVertexInputInfo* vertex,
ShaderInfo& info) {
AddInput(info, StageInputKind::VertexIndex, 0, 1, "gl_VertexIndex");
AddInput(info, StageInputKind::InstanceIndex, 0, 1, "gl_InstanceIndex");
if (vertex == nullptr) {
return;
}
uint32_t used_components[ShaderVertexInputInfo::RES_MAX] = {};
for (const auto& block: program.blocks) {
for (const auto& inst: block.instructions) {
if (inst.op == Opcode::LoadInputF32 &&
inst.input_info.attr < static_cast<uint32_t>(vertex->resources_num) &&
inst.input_info.attr < ShaderVertexInputInfo::RES_MAX) {
used_components[inst.input_info.attr] =
std::max(used_components[inst.input_info.attr], inst.input_info.chan + 1u);
}
}
}
for (uint32_t attr = 0; attr < static_cast<uint32_t>(vertex->resources_num) &&
attr < ShaderVertexInputInfo::RES_MAX;
attr++) {
if (used_components[attr] != 0) {
AddInput(info, StageInputKind::Parameter, attr, used_components[attr],
fmt::format("in_attr_{}", attr));
}
}
}
void CollectPixelInputs(const ShaderPixelInputInfo* pixel, ShaderInfo& info) {
if (pixel == nullptr) {
return;
}
if (pixel->HasPositionInput()) {
AddInput(info, StageInputKind::FragCoord, 0, 4, "gl_FragCoord");
}
if (pixel->ps_front_face) {
AddInput(info, StageInputKind::FrontFacing, 0, 1, "gl_FrontFacing");
}
for (uint32_t input = 0; input < pixel->input_num; input++) {
AddInput(info, StageInputKind::Parameter, input, 4, fmt::format("in_param_{}", input));
}
}
void CollectComputeInputs(const Program& program, const ShaderComputeInputInfo* compute,
ShaderInfo& info) {
if (compute != nullptr) {
if (compute->group_id[0] || compute->group_id[1] || compute->group_id[2]) {
AddInput(info, StageInputKind::WorkgroupId, 0, 3, "gl_WorkGroupID");
}
if (compute->thread_ids_num > 0) {
AddInput(info, StageInputKind::LocalInvocationId, 0, 3, "gl_LocalInvocationID");
}
if (compute->thread_ids_num > 0 || compute->tg_size_en) {
AddInput(info, StageInputKind::LocalInvocationIndex, 0, 1, "gl_LocalInvocationIndex");
}
if (compute->dispatch_thread_dimensions) {
AddInput(info, StageInputKind::GlobalInvocationId, 0, 3, "gl_GlobalInvocationID");
}
}
if (NeedsLocalInvocationIndex(program)) {
AddInput(info, StageInputKind::LocalInvocationIndex, 0, 1, "gl_LocalInvocationIndex");
}
}
void CollectOutputs(const Program& program, const ShaderPixelInputInfo* pixel, ShaderInfo& info) {
for (const auto& block: program.blocks) {
for (const auto& inst: block.instructions) {
if (inst.op != Opcode::Export) {
continue;
}
if (inst.export_info.kind == ExportTargetKind::MrtZ) {
if (pixel != nullptr && (inst.export_info.en & 0x1u) != 0 &&
pixel->ps_depth_export_enable) {
AddOutput(info, StageOutputKind::Depth, 0, 0, "gl_FragDepth");
}
if (pixel != nullptr && (inst.export_info.en & 0x4u) != 0 &&
pixel->ps_sample_mask_export_enable) {
AddOutput(info, StageOutputKind::SampleMask, 0, 0, "gl_SampleMask");
}
continue;
}
if (inst.export_info.en == 0) {
continue;
}
switch (inst.export_info.kind) {
case ExportTargetKind::Position:
AddOutput(info, StageOutputKind::Position, inst.export_info.index, 0,
"out_position");
break;
case ExportTargetKind::Parameter:
AddOutput(info, StageOutputKind::Parameter, inst.export_info.index,
inst.export_info.index,
fmt::format("out_param_{}", inst.export_info.index));
break;
case ExportTargetKind::Mrt:
AddOutput(info, StageOutputKind::Mrt, inst.export_info.index,
inst.export_info.index,
fmt::format("out_mrt_{}", inst.export_info.index));
break;
default: break;
}
}
}
}
} // namespace
bool CollectShaderInfo(Program& program, const ShaderInfoOptions& options, std::string* error) {
if (!program.resource_tracking_complete || program.shader_info_complete) {
if (error != nullptr) {
*error = !program.resource_tracking_complete ? "shader resources were not tracked"
: "shader info already collected";
}
return false;
}
if (!ValidateOptions(program, options, error)) {
return false;
}
auto next = program.info;
next.inputs.clear();
next.outputs.clear();
next.has_bitwise_xor =
std::any_of(program.blocks.begin(), program.blocks.end(), [](const auto& block) {
return std::any_of(block.instructions.begin(), block.instructions.end(),
[](const auto& inst) {
switch (inst.op) {
case Opcode::BitwiseXorU32:
case Opcode::BitwiseXor3U32:
case Opcode::XorAddU32: return true;
default: return false;
}
});
});
switch (program.stage) {
case ShaderType::Vertex: CollectVertexInputs(program, options.vertex, next); break;
case ShaderType::Pixel: CollectPixelInputs(options.pixel, next); break;
case ShaderType::Compute: CollectComputeInputs(program, options.compute, next); break;
default: return false;
}
CollectOutputs(program, options.pixel, next);
program.info = std::move(next);
program.shader_info_complete = true;
return true;
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -1,138 +0,0 @@
#include "graphics/shader/recompiler/ir/SrtPatcher.h"
#include <fmt/format.h>
#include <functional>
#include <map>
#include <set>
#include <vector>
namespace Libs::Graphics::ShaderRecompiler::IR {
bool PatchSrtReads(Program& program, std::string* error) {
if (!program.srt_plan_complete || program.srt_patching_complete ||
program.resource_tracking_complete) {
if (error != nullptr) {
if (!program.srt_plan_complete) {
*error = "SRT plan is not ready";
} else if (program.srt_patching_complete) {
*error = "SRT reads already patched";
} else {
*error = "resources already tracked";
}
}
return false;
}
std::map<uint32_t, uint32_t> offsets;
std::map<uint32_t, uint32_t> values;
for (const auto& read: program.srt.reads) {
if (read.value >= program.provenance.values.size() ||
read.flat_offset >= program.srt.reads.size() ||
(program.provenance.values[read.value].op != ScalarValueOp::ReadConst &&
program.provenance.values[read.value].op != ScalarValueOp::ReadConstBuffer)) {
if (error != nullptr) {
*error = fmt::format("invalid SRT read value={} flat_offset={}", read.value,
read.flat_offset);
}
return false;
}
const auto [it, inserted] = offsets.emplace(read.value, read.flat_offset);
if (!inserted && it->second != read.flat_offset) {
if (error != nullptr) {
*error = fmt::format("SRT read value {} has conflicting flat offsets", read.value);
}
return false;
}
const auto [value, unique] = values.emplace(read.flat_offset, read.value);
if (!unique && value->second != read.value) {
if (error != nullptr) {
*error = fmt::format("SRT flat offset {} has conflicting values", read.flat_offset);
}
return false;
}
}
if (offsets.size() != program.srt.reads.size() || values.size() != program.srt.reads.size()) {
if (error != nullptr) {
*error = "SRT reads do not form a dense value-to-offset bijection";
}
return false;
}
std::set<uint32_t> dynamic_reads;
for (const auto value: program.srt.dynamic_reads) {
if (value >= program.provenance.values.size() || offsets.contains(value) ||
(program.provenance.values[value].op != ScalarValueOp::ReadConst &&
program.provenance.values[value].op != ScalarValueOp::ReadConstBuffer) ||
!dynamic_reads.insert(value).second) {
if (error != nullptr) {
*error = fmt::format("invalid dynamic SRT read value={}", value);
}
return false;
}
}
std::set<uint32_t> dynamic_sources;
for (const auto source: program.srt.dynamic_sources) {
if (source == ScalarProvenance::Undefined ||
(source > ScalarProvenance::Unknown &&
source - 2u >= program.provenance.descriptors.size()) ||
!dynamic_sources.insert(source).second) {
if (error != nullptr) {
*error = fmt::format("invalid dynamic descriptor source={}", source);
}
return false;
}
}
struct Patch {
std::reference_wrapper<Instruction> inst;
uint32_t flat_offset = 0;
};
std::vector<Patch> patches;
std::map<uint32_t, bool> matched;
for (const auto& [value, offset]: offsets) {
matched.emplace(value, false);
}
for (auto& block: program.blocks) {
for (auto& inst: block.instructions) {
if (inst.op != Opcode::SLoadDword && inst.op != Opcode::SBufferLoadDword) {
continue;
}
if (const auto found = offsets.find(inst.scalar_value); found != offsets.end()) {
const auto value_op = program.provenance.values[found->first].op;
if ((inst.op == Opcode::SLoadDword) != (value_op == ScalarValueOp::ReadConst)) {
if (error != nullptr) {
*error = fmt::format("SRT read value {} has the wrong scalar-load producer",
found->first);
}
return false;
}
patches.push_back({std::ref(inst), found->second});
matched[found->first] = true;
}
}
}
for (const auto& [value, found]: matched) {
if (!found) {
if (error != nullptr) {
*error = fmt::format("SRT read value {} has no scalar-load producer", value);
}
return false;
}
}
for (const auto& patch: patches) {
auto& inst = patch.inst.get();
inst.op = Opcode::LoadSrtDword;
inst.src_count = 1;
for (auto& src: inst.src) {
src = {};
}
inst.src[0].kind = OperandKind::ImmediateU32;
inst.src[0].imm = patch.flat_offset;
inst.memory = {};
}
program.srt_patching_complete = true;
return true;
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -1,14 +0,0 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SRTPATCHER_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SRTPATCHER_H_
#include "graphics/shader/recompiler/ir/ShaderIR.h"
namespace Libs::Graphics::ShaderRecompiler::IR {
// Replaces each immediate ReadConst producer selected by the SRT plan with a dense flat-buffer
// dword load. Dynamic-offset reads remain explicit. On failure no instruction is changed.
bool PatchSrtReads(Program& program, std::string* error);
} // namespace Libs::Graphics::ShaderRecompiler::IR
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SRTPATCHER_H_ */
@@ -1,624 +0,0 @@
#include "graphics/shader/recompiler/ir/SrtWalker.h"
#include "graphics/shader/recompiler/ir/ScalarProvenance.h"
#include <algorithm>
#include <array>
#include <cstring>
#include <fmt/format.h>
namespace Libs::Graphics::ShaderRecompiler::IR {
namespace {
constexpr uint64_t AddressMask = 0x0000ffffffffffffull;
const char* StageName(ShaderType stage) {
switch (stage) {
case ShaderType::Vertex: return "vertex";
case ShaderType::Pixel: return "pixel";
case ShaderType::Fetch: return "fetch";
case ShaderType::Compute: return "compute";
default: return "unknown";
}
}
std::string Diagnostic(const Program& program, uint32_t pc, const std::string& message) {
return fmt::format("shader SRT: hash=0x{:016x} stage={} pc=0x{:08x} {}", program.shader_hash,
StageName(program.stage), pc, message);
}
bool AddSignedAddress(uint64_t base, int64_t offset, uint64_t& result) {
if (base > AddressMask) {
return false;
}
if (offset < 0) {
const auto magnitude = uint64_t {0} - static_cast<uint64_t>(offset);
if (magnitude > base) {
return false;
}
result = base - magnitude;
return true;
}
const auto magnitude = static_cast<uint64_t>(offset);
if (magnitude > AddressMask - base) {
return false;
}
result = base + magnitude;
return true;
}
bool ApplyOperation(ScalarValueOp op, const std::array<uint32_t, 3>& args, uint32_t& result) {
const auto shift = args[1] & 31u;
switch (op) {
case ScalarValueOp::Add: result = args[0] + args[1]; break;
case ScalarValueOp::AddCarry: result = args[0] + args[1] + (args[2] & 1u); break;
case ScalarValueOp::Carry:
result = static_cast<uint32_t>(
(static_cast<uint64_t>(args[0]) + args[1] + (args[2] & 1u)) >> 32u);
break;
case ScalarValueOp::Sub: result = args[0] - args[1]; break;
case ScalarValueOp::SubBorrow: result = args[0] - args[1] - (args[2] & 1u); break;
case ScalarValueOp::Borrow:
result = static_cast<uint64_t>(args[1]) + (args[2] & 1u) > args[0] ? 1u : 0u;
break;
case ScalarValueOp::Mul: result = args[0] * args[1]; break;
case ScalarValueOp::And: result = args[0] & args[1]; break;
case ScalarValueOp::AndNot: result = args[0] & ~args[1]; break;
case ScalarValueOp::Or: result = args[0] | args[1]; break;
case ScalarValueOp::OrNot: result = args[0] | ~args[1]; break;
case ScalarValueOp::Xor: result = args[0] ^ args[1]; break;
case ScalarValueOp::Not: result = ~args[0]; break;
case ScalarValueOp::ShiftLeft: result = args[0] << shift; break;
case ScalarValueOp::ShiftRight: result = args[0] >> shift; break;
case ScalarValueOp::ShiftRightArithmetic:
result = static_cast<uint32_t>(static_cast<int32_t>(args[0]) >> shift);
break;
case ScalarValueOp::BitFieldMaskU32: {
const auto count = args[0] & 31u;
const auto offset = args[1] & 31u;
result = ((uint32_t {1} << count) - 1u) << offset;
break;
}
case ScalarValueOp::BitFieldMaskU64Low:
case ScalarValueOp::BitFieldMaskU64High: {
const auto count = args[0] & 63u;
const auto value = ((uint64_t {1} << count) - 1u) << (args[1] & 63u);
result = op == ScalarValueOp::BitFieldMaskU64Low ? static_cast<uint32_t>(value)
: static_cast<uint32_t>(value >> 32u);
break;
}
case ScalarValueOp::Add3: result = args[0] + args[1] + args[2]; break;
case ScalarValueOp::ShiftLeftAdd: result = (args[0] << shift) + args[2]; break;
case ScalarValueOp::ShiftLeftAddCarry:
result =
static_cast<uint32_t>(((static_cast<uint64_t>(args[0]) << shift) + args[2]) >> 32u);
break;
case ScalarValueOp::AddShiftLeft: result = (args[0] + args[1]) << (args[2] & 31u); break;
case ScalarValueOp::XorAdd: result = (args[0] ^ args[1]) + args[2]; break;
case ScalarValueOp::ShiftLeftOr: result = (args[0] << shift) | args[2]; break;
default: return false;
}
return true;
}
class ConstantFolder {
public:
explicit ConstantFolder(const ScalarProvenance& provenance)
: m_provenance(provenance), m_values(provenance.values.size()),
m_state(provenance.values.size()) {}
bool Fold(uint32_t id, uint32_t& result) {
if (id >= m_provenance.values.size() || m_state[id] == 1 || m_state[id] == 3) {
return false;
}
if (m_state[id] == 2) {
result = m_values[id];
return true;
}
m_state[id] = 1;
const auto& value = m_provenance.values[id];
uint32_t out = 0;
switch (value.op) {
case ScalarValueOp::Constant: out = value.imm; break;
case ScalarValueOp::Phi:
if (value.phi_args.empty() || !Fold(value.phi_args[0], out)) {
return Dynamic(id);
}
for (size_t i = 1; i < value.phi_args.size(); i++) {
uint32_t other = 0;
if (!Fold(value.phi_args[i], other) || other != out) {
return Dynamic(id);
}
}
break;
default: {
const auto count = ScalarValueArgCount(value.op);
if (count == 0 || count > 3 || value.op == ScalarValueOp::ReadConst) {
return Dynamic(id);
}
std::array<uint32_t, 3> args {};
for (uint32_t i = 0; i < count; i++) {
if (!Fold(value.args[i], args[i])) {
return Dynamic(id);
}
}
if (!ApplyOperation(value.op, args, out)) {
return Dynamic(id);
}
break;
}
}
m_state[id] = 2;
m_values[id] = out;
result = out;
return true;
}
private:
bool Dynamic(uint32_t id) {
m_state[id] = 3;
return false;
}
const ScalarProvenance& m_provenance;
std::vector<uint32_t> m_values;
std::vector<uint8_t> m_state;
};
class PlanBuilder {
public:
explicit PlanBuilder(Program& program): m_program(program), m_folder(program.provenance) {
m_state.resize(program.provenance.values.size());
}
bool Run(std::string* error) {
m_program.srt = {};
for (const auto& block: m_program.blocks) {
for (const auto& inst: block.instructions) {
if (!CollectDescriptor(inst.memory.resource_source, inst.pc, error) ||
!CollectDescriptor(inst.memory.sampler_source, inst.pc, error)) {
return false;
}
}
}
for (const auto& block: m_program.blocks) {
for (const auto& inst: block.instructions) {
if (inst.op == Opcode::SLoadDword && inst.scalar_value < m_state.size() &&
m_state[inst.scalar_value] == 0) {
const auto& value = m_program.provenance.values[inst.scalar_value];
uint32_t ignored = 0;
if (value.op == ScalarValueOp::ReadConst &&
m_folder.Fold(value.args[2], ignored) &&
!CollectValue(inst.scalar_value, inst.pc, error)) {
return false;
}
}
}
}
return true;
}
private:
bool Fail(uint32_t pc, std::string* error, const std::string& message) const {
if (error != nullptr) {
*error = Diagnostic(m_program, pc, message);
}
return false;
}
bool CollectDescriptor(uint32_t source, uint32_t use_pc, std::string* error) {
const auto* descriptor = GetDescriptorSource(m_program, source);
if (descriptor == nullptr) {
if (source <= ScalarProvenance::Unknown) {
return source == ScalarProvenance::Undefined || AddDynamicSource(source);
}
return Fail(use_pc, error, fmt::format("invalid descriptor source {}", source));
}
if (!DescriptorSourceResolved(m_program, source) ||
DescriptorNeedsControlFlow(*descriptor)) {
MarkDescriptor(*descriptor);
return AddDynamicSource(source);
}
for (uint32_t i = 0; i < descriptor->dword_count; i++) {
if (!CollectValue(descriptor->dwords[i], use_pc, error)) {
return false;
}
}
return true;
}
void MarkDescriptor(const DescriptorValue& descriptor) {
for (uint32_t i = 0; i < descriptor.dword_count; i++) {
MarkValue(descriptor.dwords[i]);
}
}
void MarkValue(uint32_t id) {
if (id >= m_state.size() || m_state[id] != 0) {
return;
}
m_state[id] = 2;
const auto& value = m_program.provenance.values[id];
if (value.op == ScalarValueOp::Phi) {
for (const auto arg: value.phi_args) {
MarkValue(arg);
}
return;
}
for (uint32_t i = 0; i < ScalarValueArgCount(value.op); i++) {
MarkValue(value.args[i]);
}
}
bool DescriptorNeedsControlFlow(const DescriptorValue& descriptor) {
std::vector<uint8_t> visited(m_program.provenance.values.size());
for (uint32_t i = 0; i < descriptor.dword_count; i++) {
if (ValueNeedsControlFlow(descriptor.dwords[i], visited)) {
return true;
}
}
return false;
}
bool ValueNeedsControlFlow(uint32_t id, std::vector<uint8_t>& visited) {
if (id >= m_program.provenance.values.size() || visited[id] != 0) {
return false;
}
visited[id] = 1;
const auto& value = m_program.provenance.values[id];
if (value.op == ScalarValueOp::Phi) {
uint32_t ignored = 0;
return !m_folder.Fold(id, ignored);
}
for (uint32_t i = 0; i < ScalarValueArgCount(value.op); i++) {
if (ValueNeedsControlFlow(value.args[i], visited)) {
return true;
}
}
return false;
}
bool AddDynamicSource(uint32_t source) {
if (std::find(m_program.srt.dynamic_sources.begin(), m_program.srt.dynamic_sources.end(),
source) == m_program.srt.dynamic_sources.end()) {
m_program.srt.dynamic_sources.push_back(source);
}
return true;
}
bool CollectValue(uint32_t id, uint32_t use_pc, std::string* error) {
if (id >= m_program.provenance.values.size()) {
return Fail(use_pc, error, fmt::format("invalid scalar value {}", id));
}
if (m_state[id] == 2) {
return true;
}
if (m_state[id] == 1) {
return Fail(use_pc, error, fmt::format("cyclic scalar value {}", id));
}
m_state[id] = 1;
const auto& value = m_program.provenance.values[id];
if (value.op == ScalarValueOp::Phi) {
for (const auto arg: value.phi_args) {
if (!CollectValue(arg, use_pc, error)) {
return false;
}
}
} else {
for (uint32_t i = 0; i < ScalarValueArgCount(value.op); i++) {
if (!CollectValue(value.args[i], use_pc, error)) {
return false;
}
}
}
if (value.op == ScalarValueOp::ReadConst || value.op == ScalarValueOp::ReadConstBuffer) {
const auto offset_arg = value.op == ScalarValueOp::ReadConst ? 2u : 4u;
uint32_t ignored = 0;
if (m_folder.Fold(value.args[offset_arg], ignored)) {
m_program.srt.reads.push_back(
{id, static_cast<uint32_t>(m_program.srt.reads.size()), use_pc});
} else {
m_program.srt.dynamic_reads.push_back(id);
}
}
m_state[id] = 2;
return true;
}
Program& m_program;
ConstantFolder m_folder;
std::vector<uint8_t> m_state;
};
class Evaluator {
public:
Evaluator(const Program& program, const SrtRuntime& runtime, uint32_t use_pc)
: m_program(program), m_runtime(runtime), m_use_pc(use_pc),
m_values(program.provenance.values.size()), m_state(program.provenance.values.size()) {}
void SetUsePc(uint32_t use_pc) { m_use_pc = use_pc; }
bool Evaluate(uint32_t id, uint32_t& result, std::string* error) {
if (id >= m_program.provenance.values.size()) {
return Fail(error, fmt::format("invalid scalar value {}", id));
}
if (m_state[id] == 2) {
result = m_values[id];
return true;
}
if (m_state[id] == 1) {
return Fail(error, fmt::format("cyclic scalar value {}", id));
}
m_state[id] = 1;
const auto& value = m_program.provenance.values[id];
uint32_t out = 0;
switch (value.op) {
case ScalarValueOp::UserData:
if (value.imm < m_program.user_data_base ||
value.imm - m_program.user_data_base >= m_runtime.user_data.size()) {
return Fail(error, fmt::format("user SGPR {} is unavailable", value.imm));
}
out = m_runtime.user_data[value.imm - m_program.user_data_base];
break;
case ScalarValueOp::Constant: out = value.imm; break;
case ScalarValueOp::PcRelativeLow:
out = static_cast<uint32_t>(m_runtime.shader_base + value.imm);
break;
case ScalarValueOp::PcRelativeHigh:
out = static_cast<uint32_t>((m_runtime.shader_base + value.imm) >> 32u);
break;
case ScalarValueOp::Phi: {
const auto first = std::find_if(value.phi_args.begin(), value.phi_args.end(),
[id](uint32_t arg) { return arg != id; });
if (first == value.phi_args.end()) {
return Fail(error, fmt::format("empty scalar phi {}", id));
}
if (!Evaluate(*first, out, error)) {
return false;
}
for (const auto arg: value.phi_args) {
if (arg == id || arg == *first) {
continue;
}
uint32_t other = 0;
if (!Evaluate(arg, other, error)) {
return false;
}
if (other != out) {
return Fail(error,
fmt::format("scalar phi {} has runtime-dependent values", id));
}
}
break;
}
case ScalarValueOp::ReadConst:
case ScalarValueOp::ReadConstBuffer:
if (!Read(value, out, error)) {
return false;
}
break;
case ScalarValueOp::Undefined:
case ScalarValueOp::Unknown:
return Fail(error, fmt::format("scalar value {} is unresolved", id));
default:
if (!EvaluateOperation(value, out, error)) {
return false;
}
break;
}
m_state[id] = 2;
m_values[id] = out;
result = out;
return true;
}
private:
bool Fail(std::string* error, const std::string& message) const {
if (error != nullptr) {
*error = Diagnostic(m_program, m_use_pc, message);
}
return false;
}
bool EvaluateOperation(const ScalarValue& value, uint32_t& result, std::string* error) {
const auto count = ScalarValueArgCount(value.op);
if (count == 0 || count > 3) {
return Fail(error, fmt::format("unsupported scalar operation {}",
static_cast<uint32_t>(value.op)));
}
std::array<uint32_t, 3> args {};
for (uint32_t i = 0; i < count; i++) {
if (!Evaluate(value.args[i], args[i], error)) {
return false;
}
}
return ApplyOperation(value.op, args, result) ||
Fail(error, fmt::format("unsupported scalar operation {}",
static_cast<uint32_t>(value.op)));
}
bool Read(const ScalarValue& value, uint32_t& result, std::string* error) {
const bool buffer = value.op == ScalarValueOp::ReadConstBuffer;
uint32_t lo = 0;
uint32_t hi = 0;
uint32_t offset = 0;
if (!Evaluate(value.args[0], lo, error) || !Evaluate(value.args[1], hi, error) ||
!Evaluate(value.args[buffer ? 4u : 2u], offset, error)) {
return false;
}
const auto base = (static_cast<uint64_t>(hi) << 32u | lo) & AddressMask;
const auto immediate = static_cast<int64_t>(static_cast<int32_t>(value.imm));
uint64_t address = 0;
if (buffer) {
uint32_t num_records = 0;
uint32_t ignored = 0;
if (!Evaluate(value.args[2], num_records, error) ||
!Evaluate(value.args[3], ignored, error)) {
return false;
}
if (immediate < 0) {
return Fail(error,
fmt::format("ReadConstBuffer pc=0x{:08x} has negative immediate {}",
value.pc, immediate));
}
const auto byte_offset = static_cast<uint64_t>(immediate) + offset;
const auto aligned_offset = byte_offset & ~uint64_t {3};
const auto stride = (hi >> 16u) & 0x3fffu;
const auto size = stride == 0 ? static_cast<uint64_t>(num_records)
: static_cast<uint64_t>(stride) * num_records;
if (aligned_offset > size || size - aligned_offset < sizeof(uint32_t)) {
return Fail(error,
fmt::format("ReadConstBuffer pc=0x{:08x} offset={} exceeds size={}",
value.pc, aligned_offset, size));
}
address = ((base & ~uint64_t {3}) + byte_offset) & ~uint64_t {3};
} else {
const auto base_aligned = base & ~uint64_t {3};
const auto immediate_aligned = immediate & ~int64_t {3};
const auto relative = immediate_aligned + static_cast<int64_t>(offset & ~uint32_t {3});
if (!AddSignedAddress(base_aligned, relative, address)) {
return Fail(error,
fmt::format("ReadConst pc=0x{:08x} address base=0x{:016x} offset={} "
"is outside the 48-bit address space",
value.pc, base_aligned, relative));
}
}
if (m_runtime.read_memory != nullptr &&
!m_runtime.read_memory(m_runtime.userdata, address, &result)) {
return Fail(
error, fmt::format("ReadConst pc=0x{:08x} failed at 0x{:016x}", value.pc, address));
}
if (m_runtime.read_memory == nullptr) {
std::memcpy(&result, reinterpret_cast<const void*>(address), sizeof(result));
}
return true;
}
const Program& m_program;
const SrtRuntime& m_runtime;
uint32_t m_use_pc;
std::vector<uint32_t> m_values;
std::vector<uint8_t> m_state;
};
} // namespace
bool FoldScalarConstant(const ScalarProvenance& provenance, uint32_t value, uint32_t& result) {
ConstantFolder folder(provenance);
return folder.Fold(value, result);
}
bool BuildSrtPlan(Program& program, std::string* error) {
if (program.resource_tracking_complete) {
if (error != nullptr) {
*error = "cannot rebuild SRT plan after resource tracking";
}
return false;
}
if (program.srt_patching_complete) {
if (error != nullptr) {
*error = "cannot rebuild SRT plan after SRT patching";
}
return false;
}
program.srt_plan_complete = false;
if (!PlanBuilder(program).Run(error)) {
return false;
}
program.srt_plan_complete = true;
return true;
}
bool EvaluateDescriptorSource(const Program& program, uint32_t source, uint32_t use_pc,
const SrtRuntime& runtime, DescriptorValue& result,
std::string* error) {
const DescriptorSourceRequest request {source, use_pc};
std::vector<DescriptorValue> results;
if (!EvaluateDescriptorSources(program, std::span {&request, 1}, runtime, results, error)) {
return false;
}
result = results[0];
return true;
}
static bool EvaluateRuntimeSourcesImpl(const Program& program,
std::span<const DescriptorSourceRequest> requests,
const SrtRuntime& runtime,
std::vector<DescriptorValue>& results,
std::vector<uint32_t>& flat, bool evaluate_flat,
std::string* error) {
if (!program.srt_plan_complete) {
if (error != nullptr) {
*error = Diagnostic(program, 0, "SRT plan is not ready");
}
return false;
}
std::vector<DescriptorValue> evaluated;
evaluated.reserve(requests.size());
std::vector<uint32_t> flattened(evaluate_flat ? program.srt.reads.size() : 0u);
Evaluator evaluator(program, runtime, 0);
for (const auto& request: requests) {
const auto* descriptor = GetDescriptorSource(program, request.source);
const auto dynamic =
std::find(program.srt.dynamic_sources.begin(), program.srt.dynamic_sources.end(),
request.source) != program.srt.dynamic_sources.end();
if (descriptor == nullptr || dynamic) {
if (error != nullptr) {
*error = Diagnostic(program, request.use_pc,
fmt::format("descriptor source {} is {}", request.source,
dynamic ? "GPU-dynamic" : "unresolved"));
}
return false;
}
evaluator.SetUsePc(request.use_pc);
auto value = *descriptor;
for (uint32_t i = 0; i < descriptor->dword_count; i++) {
if (!evaluator.Evaluate(descriptor->dwords[i], value.dwords[i], error)) {
return false;
}
}
evaluated.push_back(value);
}
if (evaluate_flat) {
for (const auto& read: program.srt.reads) {
evaluator.SetUsePc(read.use_pc);
if (read.flat_offset >= flattened.size()) {
if (error != nullptr) {
*error = Diagnostic(program, read.use_pc, "invalid flat SRT offset");
}
return false;
}
if (!evaluator.Evaluate(read.value, flattened[read.flat_offset], error)) {
return false;
}
}
}
results = std::move(evaluated);
if (evaluate_flat) {
flat = std::move(flattened);
}
return true;
}
bool EvaluateDescriptorSources(const Program& program,
std::span<const DescriptorSourceRequest> requests,
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
std::string* error) {
std::vector<uint32_t> ignored;
return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, ignored, false, error);
}
bool EvaluateRuntimeSources(const Program& program,
std::span<const DescriptorSourceRequest> requests,
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
std::vector<uint32_t>& flat, std::string* error) {
return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, flat, true, error);
}
bool WalkSrt(const Program& program, const SrtRuntime& runtime, std::vector<uint32_t>& flat,
std::string* error) {
std::vector<DescriptorValue> ignored;
return EvaluateRuntimeSources(program, {}, runtime, ignored, flat, error);
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -0,0 +1,55 @@
#include "graphics/shader/recompiler/ir/Type.h"
#include <array>
namespace Libs::Graphics::ShaderRecompiler::IR {
bool AreTypesCompatible(Type lhs, Type rhs) {
return lhs == rhs || lhs == Type::Opaque || rhs == Type::Opaque || TypesOverlap(lhs, rhs);
}
std::string TypeName(Type type) {
static constexpr std::array names = {
"Opaque",
"ScalarReg",
"VectorReg",
"U1",
"U8",
"U16",
"U32",
"U64",
"F16",
"F32",
"U32x2",
"U32x3",
"U32x4",
"F32x2",
"F32x3",
"F32x4",
"SrtResource",
"BufferResource",
"AddressResource",
"ImageResource",
"SamplerResource",
"LdsResource",
"GdsResource",
"ImageAddress",
};
const auto bits = static_cast<uint32_t>(type);
if (bits == 0) {
return "Void";
}
std::string result;
for (uint32_t index = 0; index < names.size(); index++) {
if ((bits & (1u << index)) == 0) {
continue;
}
if (!result.empty()) {
result += '|';
}
result += names[index];
}
return result;
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
+51
View File
@@ -0,0 +1,51 @@
#pragma once
#include <cstdint>
#include <string>
namespace Libs::Graphics::ShaderRecompiler::IR {
enum class Type : uint32_t {
Void = 0,
Opaque = 1u << 0u,
ScalarReg = 1u << 1u,
VectorReg = 1u << 2u,
U1 = 1u << 3u,
U8 = 1u << 4u,
U16 = 1u << 5u,
U32 = 1u << 6u,
U64 = 1u << 7u,
F16 = 1u << 8u,
F32 = 1u << 9u,
U32x2 = 1u << 10u,
U32x3 = 1u << 11u,
U32x4 = 1u << 12u,
F32x2 = 1u << 13u,
F32x3 = 1u << 14u,
F32x4 = 1u << 15u,
SrtResource = 1u << 16u,
BufferResource = 1u << 17u,
AddressResource = 1u << 18u,
ImageResource = 1u << 19u,
SamplerResource = 1u << 20u,
LdsResource = 1u << 21u,
GdsResource = 1u << 22u,
ImageAddress = 1u << 23u,
};
constexpr Type operator|(Type lhs, Type rhs) {
return static_cast<Type>(static_cast<uint32_t>(lhs) | static_cast<uint32_t>(rhs));
}
constexpr Type operator&(Type lhs, Type rhs) {
return static_cast<Type>(static_cast<uint32_t>(lhs) & static_cast<uint32_t>(rhs));
}
constexpr bool TypesOverlap(Type lhs, Type rhs) {
return (lhs & rhs) != Type::Void;
}
[[nodiscard]] bool AreTypesCompatible(Type lhs, Type rhs);
[[nodiscard]] std::string TypeName(Type type);
} // namespace Libs::Graphics::ShaderRecompiler::IR
+277
View File
@@ -0,0 +1,277 @@
#include "graphics/shader/recompiler/ir/Value.h"
#include <algorithm>
#include <cstring>
#include <limits>
namespace Libs::Graphics::ShaderRecompiler::IR {
Value::Value(Inst* value): type(Type::Opaque), inst(value) {}
Value::Value(ScalarReg value): type(Type::ScalarReg), scalar_reg(value) {}
Value::Value(VectorReg value): type(Type::VectorReg), vector_reg(value) {}
Value::Value(bool value): type(Type::U1), imm_u1(value) {}
Value::Value(uint8_t value): type(Type::U8), imm_u8(value) {}
Value::Value(uint16_t value): type(Type::U16), imm_u16(value) {}
Value::Value(uint32_t value): type(Type::U32), imm_u32(value) {}
Value::Value(uint64_t value): type(Type::U64), imm_u64(value) {}
Value::Value(Type value_type, uint64_t bits): type(value_type), imm_u64(bits) {}
Value Value::F16(uint16_t bits) {
return Value(Type::F16, bits);
}
Value Value::F32(float value) {
return Value(Type::F32, std::bit_cast<uint32_t>(value));
}
bool Value::IsEmpty() const {
return type == Type::Void;
}
bool Value::IsImmediate() const {
return type != Type::Opaque;
}
bool Value::IsIdentity() const {
return type == Type::Opaque && inst->GetOpcode() == ValueOpcode::Identity;
}
bool Value::IsPhi() const {
return type == Type::Opaque && inst->GetOpcode() == ValueOpcode::Phi;
}
Type Value::GetType() const {
if (IsPhi()) {
return inst->Flags<Type>();
}
if (IsIdentity()) {
return inst->Arg(0).GetType();
}
return type == Type::Opaque ? inst->GetType() : type;
}
Inst* Value::Instruction() const {
EXIT_IF(type != Type::Opaque);
return inst;
}
Inst* Value::TryInstruction() const {
return type == Type::Opaque ? inst : nullptr;
}
Inst* Value::ResolveInstruction() const {
EXIT_IF(type != Type::Opaque);
return IsIdentity() ? inst->Arg(0).ResolveInstruction() : inst;
}
Value Value::Resolve() const {
return IsIdentity() ? inst->Arg(0).Resolve() : *this;
}
ScalarReg Value::ScalarRegister() const {
EXIT_IF(type != Type::ScalarReg);
return scalar_reg;
}
VectorReg Value::VectorRegister() const {
EXIT_IF(type != Type::VectorReg);
return vector_reg;
}
bool Value::U1() const {
EXIT_IF(type != Type::U1);
return imm_u1;
}
uint8_t Value::U8() const {
EXIT_IF(type != Type::U8);
return imm_u8;
}
uint16_t Value::U16() const {
EXIT_IF(type != Type::U16);
return imm_u16;
}
uint32_t Value::U32() const {
EXIT_IF(type != Type::U32);
return imm_u32;
}
uint64_t Value::U64() const {
EXIT_IF(type != Type::U64);
return imm_u64;
}
uint16_t Value::F16Bits() const {
EXIT_IF(type != Type::F16);
return imm_u16;
}
float Value::F32Value() const {
EXIT_IF(type != Type::F32);
return std::bit_cast<float>(imm_u32);
}
bool Value::operator==(const Value& other) const {
if (type != other.type) {
return false;
}
switch (type) {
case Type::Void: return true;
case Type::Opaque: return inst == other.inst;
case Type::ScalarReg: return scalar_reg == other.scalar_reg;
case Type::VectorReg: return vector_reg == other.vector_reg;
case Type::U1: return imm_u1 == other.imm_u1;
case Type::U8: return imm_u8 == other.imm_u8;
case Type::U16:
case Type::F16: return imm_u16 == other.imm_u16;
case Type::U32:
case Type::F32: return imm_u32 == other.imm_u32;
case Type::U64: return imm_u64 == other.imm_u64;
default: return false;
}
}
Inst::Inst(ValueOpcode value_opcode, uint64_t value_flags)
: opcode(value_opcode), flags(value_flags) {
const auto count = NumArgsOf(opcode);
if (count != std::numeric_limits<size_t>::max()) {
args.resize(count);
}
}
Inst::~Inst() {
ClearArgs();
}
ValueOpcode Inst::GetOpcode() const {
return opcode;
}
Type Inst::GetType() const {
if (opcode == ValueOpcode::Phi) {
return static_cast<Type>(flags);
}
if (opcode == ValueOpcode::Identity && !args.empty()) {
return args.front().GetType();
}
return TypeOf(opcode);
}
bool Inst::MayHaveSideEffects() const {
return HasSideEffects(opcode);
}
bool Inst::HasUses() const {
return !uses.empty();
}
size_t Inst::UseCount() const {
return uses.size();
}
size_t Inst::NumArgs() const {
return args.size();
}
Value Inst::Arg(size_t index) const {
EXIT_IF(index >= args.size());
return args[index];
}
Block* Inst::PhiBlock(size_t index) const {
EXIT_IF(opcode != ValueOpcode::Phi || index >= phi_blocks.size());
return phi_blocks[index];
}
Block* Inst::Parent() const {
return parent;
}
const std::vector<Use>& Inst::Uses() const {
return uses;
}
void Inst::SetParent(Block* block) {
parent = block;
}
void Inst::SetArg(size_t index, Value value) {
if (index >= args.size()) {
EXIT_IF(NumArgsOf(opcode) != std::numeric_limits<size_t>::max());
args.resize(index + 1);
}
const auto old = args[index];
if (auto* old_inst = old.TryInstruction(); old_inst != nullptr) {
RemoveUse(old_inst, index);
}
args[index] = value;
if (auto* new_inst = value.TryInstruction(); new_inst != nullptr) {
AddUse(new_inst, index);
}
}
void Inst::AddPhiOperand(Block* predecessor, Value value) {
EXIT_IF(opcode != ValueOpcode::Phi);
const auto index = args.size();
args.push_back(value);
phi_blocks.push_back(predecessor);
if (auto* value_inst = value.TryInstruction(); value_inst != nullptr) {
AddUse(value_inst, index);
}
}
void Inst::ReplaceUsesWith(Value replacement, bool preserve) {
const auto old_uses = uses;
for (const auto& use: old_uses) {
use.user->SetArg(use.operand, replacement);
}
Invalidate();
if (preserve) {
ReplaceOpcode(ValueOpcode::Identity);
args.resize(1);
SetArg(0, replacement);
}
}
void Inst::ReplaceOpcode(ValueOpcode value_opcode) {
opcode = value_opcode;
const auto count = NumArgsOf(opcode);
if (count != std::numeric_limits<size_t>::max()) {
EXIT_IF(!args.empty() && args.size() != count);
args.resize(count);
}
}
void Inst::Invalidate() {
ClearArgs();
opcode = ValueOpcode::Void;
}
void Inst::AddUse(Inst* used, size_t operand) {
const auto found = std::ranges::find_if(
used->uses, [&](const Use& use) { return use.user == this && use.operand == operand; });
EXIT_IF(found != used->uses.end());
used->uses.push_back({this, operand});
}
void Inst::RemoveUse(Inst* used, size_t operand) {
const auto found = std::ranges::find_if(
used->uses, [&](const Use& use) { return use.user == this && use.operand == operand; });
EXIT_IF(found == used->uses.end());
used->uses.erase(found);
}
void Inst::ClearArgs() {
for (size_t index = 0; index < args.size(); index++) {
if (auto* value_inst = args[index].TryInstruction(); value_inst != nullptr) {
RemoveUse(value_inst, index);
}
}
args.clear();
phi_blocks.clear();
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
+159
View File
@@ -0,0 +1,159 @@
#pragma once
#include "common/assert.h"
#include "graphics/shader/recompiler/ir/Reg.h"
#include "graphics/shader/recompiler/ir/opcodes/ValueOpcodes.h"
#include <bit>
#include <cstdint>
#include <cstring>
#include <type_traits>
#include <utility>
#include <vector>
namespace Libs::Graphics::ShaderRecompiler::IR {
class Block;
class Inst;
class Value {
public:
Value() = default;
explicit Value(Inst* value);
explicit Value(ScalarReg value);
explicit Value(VectorReg value);
explicit Value(bool value);
explicit Value(uint8_t value);
explicit Value(uint16_t value);
explicit Value(uint32_t value);
explicit Value(uint64_t value);
static Value F16(uint16_t bits);
static Value F32(float value);
[[nodiscard]] bool IsEmpty() const;
[[nodiscard]] bool IsImmediate() const;
[[nodiscard]] bool IsIdentity() const;
[[nodiscard]] bool IsPhi() const;
[[nodiscard]] Type GetType() const;
[[nodiscard]] Inst* Instruction() const;
[[nodiscard]] Inst* TryInstruction() const;
[[nodiscard]] Inst* ResolveInstruction() const;
[[nodiscard]] Value Resolve() const;
[[nodiscard]] ScalarReg ScalarRegister() const;
[[nodiscard]] VectorReg VectorRegister() const;
[[nodiscard]] bool U1() const;
[[nodiscard]] uint8_t U8() const;
[[nodiscard]] uint16_t U16() const;
[[nodiscard]] uint32_t U32() const;
[[nodiscard]] uint64_t U64() const;
[[nodiscard]] uint16_t F16Bits() const;
[[nodiscard]] float F32Value() const;
bool operator==(const Value& other) const;
private:
Type type = Type::Void;
union {
Inst* inst;
ScalarReg scalar_reg;
VectorReg vector_reg;
bool imm_u1;
uint8_t imm_u8;
uint16_t imm_u16;
uint32_t imm_u32;
uint64_t imm_u64;
};
explicit Value(Type type, uint64_t bits);
};
static_assert(std::is_trivially_copyable_v<Value>);
template <Type type_>
class TypedValue: public Value {
public:
TypedValue() = default;
template <Type other_type>
requires(TypesOverlap(type_, other_type))
TypedValue(const TypedValue<other_type>& value): Value(value) {}
explicit TypedValue(Value value): Value(value) {
EXIT_IF(!AreTypesCompatible(value.GetType(), type_));
}
};
using U1 = TypedValue<Type::U1>;
using U8 = TypedValue<Type::U8>;
using U16 = TypedValue<Type::U16>;
using U32 = TypedValue<Type::U32>;
using U64 = TypedValue<Type::U64>;
using F16 = TypedValue<Type::F16>;
using F32 = TypedValue<Type::F32>;
using U32F32 = TypedValue<Type::U32 | Type::F32>;
struct Use {
Inst* user = nullptr;
size_t operand = 0;
bool operator==(const Use&) const = default;
};
class Inst {
public:
explicit Inst(ValueOpcode opcode, uint64_t flags = 0);
~Inst();
Inst(const Inst&) = delete;
Inst& operator=(const Inst&) = delete;
Inst(Inst&&) = delete;
Inst& operator=(Inst&&) = delete;
[[nodiscard]] ValueOpcode GetOpcode() const;
[[nodiscard]] Type GetType() const;
[[nodiscard]] bool MayHaveSideEffects() const;
[[nodiscard]] bool HasUses() const;
[[nodiscard]] size_t UseCount() const;
[[nodiscard]] size_t NumArgs() const;
[[nodiscard]] Value Arg(size_t index) const;
[[nodiscard]] Block* PhiBlock(size_t index) const;
[[nodiscard]] Block* Parent() const;
[[nodiscard]] const std::vector<Use>& Uses() const;
void SetParent(Block* block);
void SetArg(size_t index, Value value);
void AddPhiOperand(Block* predecessor, Value value);
void ReplaceUsesWith(Value replacement, bool preserve = true);
void ReplaceOpcode(ValueOpcode opcode);
void Invalidate();
template <typename T>
requires(sizeof(T) <= sizeof(uint64_t) && std::is_trivially_copyable_v<T>)
[[nodiscard]] T Flags() const {
T result {};
std::memcpy(&result, &flags, sizeof(result));
return result;
}
template <typename T>
requires(sizeof(T) <= sizeof(uint64_t) && std::is_trivially_copyable_v<T>)
void SetFlags(T value) {
flags = 0;
std::memcpy(&flags, &value, sizeof(value));
}
private:
void AddUse(Inst* used, size_t operand);
void RemoveUse(Inst* used, size_t operand);
void ClearArgs();
ValueOpcode opcode;
uint64_t flags;
Block* parent = nullptr;
std::vector<Value> args;
std::vector<Block*> phi_blocks;
std::vector<Use> uses;
};
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -0,0 +1,241 @@
#include "graphics/shader/recompiler/ir/ValueProgram.h"
#include <fmt/format.h>
#include <map>
#include <unordered_set>
namespace Libs::Graphics::ShaderRecompiler::IR {
namespace {
bool Fail(std::string* error, const std::string& message) {
if (error != nullptr) {
*error = message;
}
return false;
}
bool IsRegisterGet(ValueOpcode opcode) {
switch (opcode) {
case ValueOpcode::GetThreadBitScalarRegister:
case ValueOpcode::GetScalarRegister:
case ValueOpcode::GetVectorRegister:
case ValueOpcode::GetGotoVariable:
case ValueOpcode::GetScc:
case ValueOpcode::GetExec:
case ValueOpcode::GetExecLo:
case ValueOpcode::GetExecHi:
case ValueOpcode::GetVcc:
case ValueOpcode::GetVccLo:
case ValueOpcode::GetVccHi:
case ValueOpcode::GetM0: return true;
default: return false;
}
}
bool IsRuntimeRead(ValueOpcode opcode) {
return opcode == ValueOpcode::LoadAddressU32 || opcode == ValueOpcode::ReadConstBuffer;
}
bool EquivalentValue(const ValueProgram& program, Value left, Value right,
std::vector<std::pair<const Inst*, const Inst*>>& visited) {
left = left.Resolve();
right = right.Resolve();
if (left == right) {
return true;
}
if (left.IsImmediate() || right.IsImmediate() || left.GetType() != right.GetType()) {
return false;
}
const auto* lhs = left.TryInstruction();
const auto* rhs = right.TryInstruction();
if (lhs == nullptr || rhs == nullptr || lhs->GetOpcode() != rhs->GetOpcode() ||
lhs->NumArgs() != rhs->NumArgs()) {
return false;
}
if (std::ranges::find(visited, std::pair {lhs, rhs}) != visited.end()) {
return true;
}
visited.emplace_back(lhs, rhs);
if (IsRuntimeRead(lhs->GetOpcode())) {
const auto li = lhs->Flags<MemoryFlags>().index;
const auto ri = rhs->Flags<MemoryFlags>().index;
if (li >= program.memory_info.size() || ri >= program.memory_info.size() ||
program.memory_info[li] != program.memory_info[ri]) {
return false;
}
} else if (lhs->Flags<uint64_t>() != rhs->Flags<uint64_t>()) {
return false;
}
for (size_t index = 0; index < lhs->NumArgs(); index++) {
if (lhs->GetOpcode() == ValueOpcode::Phi && lhs->PhiBlock(index) != rhs->PhiBlock(index)) {
return false;
}
if (!EquivalentValue(program, lhs->Arg(index), rhs->Arg(index), visited)) {
return false;
}
}
return true;
}
} // namespace
ValueProgram::~ValueProgram() {
// Values may cross block boundaries. Detach all arguments before any block starts destroying
// its instruction storage so reverse-use links always point to live definitions.
for (auto* block: blocks) {
for (auto& inst: *block) {
inst.Invalidate();
}
}
}
bool EquivalentValue(const ValueProgram& program, Value left, Value right) {
std::vector<std::pair<const Inst*, const Inst*>> visited;
return EquivalentValue(program, left, right, visited);
}
Value ResolveInvariantPhi(const ValueProgram& program, Value value) {
value = value.Resolve();
const auto* root = value.TryInstruction();
if (root == nullptr || root->GetOpcode() != ValueOpcode::Phi) {
return value;
}
Value invariant;
std::vector<Value> pending {value};
std::unordered_set<const Inst*> visited;
while (!pending.empty()) {
const auto current = pending.back().Resolve();
pending.pop_back();
const auto* inst = current.TryInstruction();
if (inst != nullptr && inst->GetOpcode() == ValueOpcode::Phi) {
if (!visited.insert(inst).second) {
continue;
}
for (size_t index = 0; index < inst->NumArgs(); index++) {
pending.push_back(inst->Arg(index));
}
continue;
}
if (invariant.IsEmpty()) {
invariant = current;
} else if (!EquivalentValue(program, invariant, current)) {
return {};
}
}
return invariant;
}
bool ValidateValueProgram(const ValueProgram& program, bool require_ssa, std::string* error) {
if (program.blocks.size() != program.block_info.size() ||
program.blocks.size() != program.block_storage.size()) {
return Fail(error, "value IR block storage is inconsistent");
}
for (size_t block_index = 0; block_index < program.blocks.size(); block_index++) {
const auto* block = program.blocks[block_index];
if (block != program.block_storage[block_index].get()) {
return Fail(error, "value IR block pointer is inconsistent");
}
for (const auto& inst: *block) {
if (inst.Parent() != block) {
return Fail(error, "value IR instruction has the wrong parent block");
}
if (require_ssa && IsRegisterGet(inst.GetOpcode())) {
return Fail(error, fmt::format("register getter {} survived SSA rewrite",
ValueOpcodeName(inst.GetOpcode())));
}
if (inst.GetOpcode() != ValueOpcode::Phi && inst.GetOpcode() != ValueOpcode::Identity &&
inst.GetType() == Type::Opaque) {
return Fail(error, fmt::format("untyped opcode {} survived translation",
ValueOpcodeName(inst.GetOpcode())));
}
for (size_t arg_index = 0; arg_index < inst.NumArgs(); arg_index++) {
const auto arg = inst.Arg(arg_index);
if (arg.IsEmpty()) {
return Fail(error, fmt::format("{} has an empty argument",
ValueOpcodeName(inst.GetOpcode())));
}
if (const auto* definition = arg.TryInstruction();
definition != nullptr && definition->Parent() == nullptr) {
return Fail(error, "value IR argument has a detached definition");
}
if (const auto* definition = arg.TryInstruction(); definition != nullptr) {
const auto& uses = definition->Uses();
const auto use = std::ranges::find_if(uses, [&](const Use& candidate) {
return candidate.user == &inst && candidate.operand == arg_index;
});
if (use == uses.end()) {
return Fail(error,
fmt::format("{} argument {} is absent from {} reverse uses",
ValueOpcodeName(inst.GetOpcode()), arg_index,
ValueOpcodeName(definition->GetOpcode())));
}
}
}
}
}
return true;
}
std::string ValueProgramToString(const ValueProgram& program) {
std::map<const Inst*, size_t> ids;
size_t next_id = 1;
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
ids.emplace(&inst, next_id++);
}
}
const auto value_string = [&](Value value) {
if (value.IsEmpty()) {
return std::string("<null>");
}
if (const auto* inst = value.TryInstruction(); inst != nullptr) {
return fmt::format("%{}", ids.at(inst));
}
switch (value.GetType()) {
case Type::ScalarReg: return fmt::format("s{}", RegIndex(value.ScalarRegister()));
case Type::VectorReg: return fmt::format("v{}", RegIndex(value.VectorRegister()));
case Type::U1: return std::string(value.U1() ? "true" : "false");
case Type::U8: return fmt::format("{}u8", value.U8());
case Type::U16: return fmt::format("{}u16", value.U16());
case Type::U32: return fmt::format("0x{:08x}", value.U32());
case Type::U64: return fmt::format("0x{:016x}", value.U64());
case Type::F16: return fmt::format("f16(0x{:04x})", value.F16Bits());
case Type::F32: return fmt::format("{}f", value.F32Value());
default: return fmt::format("<{}>", TypeName(value.GetType()));
}
};
std::string text =
fmt::format("mode={}\n", program.dispatcher_fallback ? "dispatcher" : "structured");
for (size_t block_index = 0; block_index < program.blocks.size(); block_index++) {
text += fmt::format("Block ${} pc=0x{:08x}..0x{:08x}\n", block_index,
program.block_info[block_index].start_pc,
program.block_info[block_index].end_pc);
for (const auto& inst: *program.blocks[block_index]) {
const auto type = inst.GetType();
if (type != Type::Void) {
text +=
fmt::format(" %{:<5} = {}", ids.at(&inst), ValueOpcodeName(inst.GetOpcode()));
} else {
text += fmt::format(" {}", ValueOpcodeName(inst.GetOpcode()));
}
for (size_t index = 0; index < inst.NumArgs(); index++) {
text += index == 0 ? " " : ", ";
if (inst.GetOpcode() == ValueOpcode::Phi) {
const auto predecessor =
std::ranges::find(program.blocks, inst.PhiBlock(index));
text += fmt::format("[{}, ${}]", value_string(inst.Arg(index)),
std::distance(program.blocks.begin(), predecessor));
} else {
text += value_string(inst.Arg(index));
}
}
text +=
fmt::format(" ({}; uses={})\n", TypeName(Value(const_cast<Inst*>(&inst)).GetType()),
inst.UseCount());
}
}
return text;
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -0,0 +1,64 @@
#pragma once
#include "graphics/shader/recompiler/ir/Block.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include <memory>
#include <string>
#include <vector>
namespace Libs::Graphics::ShaderRecompiler::IR {
struct ValueBlockInfo {
uint32_t id = 0;
uint32_t start_pc = 0;
uint32_t end_pc = 0;
CFG::Terminator terminator;
Value condition;
Value indirect_target;
};
struct DescriptorSource {
std::array<Value, 8> dwords {};
uint32_t dword_count = 0;
bool operator==(const DescriptorSource& other) const = default;
};
struct SrtRead {
Value value;
uint32_t flat_offset = 0;
uint32_t use_pc = 0;
bool operator==(const SrtRead& other) const = default;
};
struct ValueProgram {
ValueProgram() = default;
~ValueProgram();
ValueProgram(const ValueProgram&) = delete;
ValueProgram& operator=(const ValueProgram&) = delete;
ValueProgram(ValueProgram&&) = default;
ValueProgram& operator=(ValueProgram&&) = default;
std::vector<std::unique_ptr<Block>> block_storage;
BlockList blocks;
std::vector<ValueBlockInfo> block_info;
// Decoded MIMG/VMEM metadata carries details such as RDNA2 NSA address registers and
// storage-image swizzles. Typed memory instructions carry a dense index into these shader-local
// tables until those fields are consumed by emission.
std::vector<MemoryInfo> memory_info;
std::vector<ExportInfo> export_info;
std::vector<DescriptorSource> descriptor_sources;
std::vector<SrtRead> srt_reads;
std::vector<Value> dynamic_reads;
bool dispatcher_fallback = false;
};
bool ValidateValueProgram(const ValueProgram& program, bool require_ssa, std::string* error);
bool EquivalentValue(const ValueProgram& program, Value left, Value right);
Value ResolveInvariantPhi(const ValueProgram& program, Value value);
std::string ValueProgramToString(const ValueProgram& program);
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -0,0 +1,71 @@
#pragma once
#include "graphics/shader/recompiler/ir/Type.h"
#include <array>
#include <cstddef>
#include <string_view>
namespace Libs::Graphics::ShaderRecompiler::IR {
enum class Opcode {
#define IR_OPCODE(name, category, lowering, ...) name,
#include "graphics/shader/recompiler/ir/opcodes/ShaderIROpcodes.inc"
#undef IR_OPCODE
Count,
};
enum class OpcodeClass { General, Compare, Compare64 };
enum class LoweringClass {
Control,
Move,
Lane,
State,
Memory,
Attribute,
IntegerCompare,
Integer16Compare,
FloatCompare,
Conversion,
Integer16,
PackedInteger16,
PackedFloat16,
Float16,
Float,
U64Mask,
SimpleInteger,
ComposedInteger,
ExtendedInteger,
};
struct OpcodeInfo {
std::string_view name;
OpcodeClass opcode_class = OpcodeClass::General;
LoweringClass lowering_class = LoweringClass::Control;
};
inline constexpr OpcodeInfo OPCODE_INFO[] = {
#define IR_OPCODE(name, category, lowering, ...) \
{#name, OpcodeClass::category, LoweringClass::lowering},
#include "graphics/shader/recompiler/ir/opcodes/ShaderIROpcodes.inc"
#undef IR_OPCODE
};
static_assert(std::size(OPCODE_INFO) == static_cast<size_t>(Opcode::Count));
constexpr const OpcodeInfo& GetOpcodeInfo(Opcode opcode) {
return OPCODE_INFO[static_cast<size_t>(opcode)];
}
constexpr std::string_view OpcodeName(Opcode opcode) {
return GetOpcodeInfo(opcode).name;
}
constexpr bool IsCompareOpcode(Opcode opcode) {
return GetOpcodeInfo(opcode).opcode_class != OpcodeClass::General;
}
constexpr bool IsCompare64Opcode(Opcode opcode) {
return GetOpcodeInfo(opcode).opcode_class == OpcodeClass::Compare64;
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -103,6 +103,7 @@ constexpr LowerMap LOWER_OPS[] = {
{Decoder::Opcode::VCvtFlrI32F32, Opcode::ConvertFloorF32ToI32},
{Decoder::Opcode::VCvtOffF32I4, Opcode::ConvertI4ToOffsetF32},
{Decoder::Opcode::VRcpF32, Opcode::RcpF32},
{Decoder::Opcode::VRcpIflagF32, Opcode::RcpIflagF32},
{Decoder::Opcode::VFractF32, Opcode::FractF32},
{Decoder::Opcode::VTruncF32, Opcode::TruncF32},
{Decoder::Opcode::VCeilF32, Opcode::CeilF32},
@@ -0,0 +1,343 @@
// Semantic operations produced by RDNA2 instruction lowering. The typed frontend consumes this
// baseline set before optimization and resource planning; it must not grow without decoder support.
IR_OPCODE(ControlNop, General, Control, Void)
IR_OPCODE(Waitcnt, General, Control, Void)
IR_OPCODE(Barrier, General, Control, Void)
IR_OPCODE(Sendmsg, General, Control, Void)
IR_OPCODE(TtraceData, General, Control, Void)
IR_OPCODE(InstPrefetch, General, Control, Void)
IR_OPCODE(MoveU32, General, Move)
IR_OPCODE(MoveF32Bits, General, Move)
IR_OPCODE(MoveRelDestU32, General, Lane)
IR_OPCODE(MoveRelSourceU32, General, Lane)
IR_OPCODE(MoveU64, General, Move)
IR_OPCODE(WqmB64, General, Move)
IR_OPCODE(SaveexecB32, General, State)
IR_OPCODE(SaveexecB64, General, State)
IR_OPCODE(ReadFirstLaneU32, General, Lane)
IR_OPCODE(ReadLaneU32, General, Lane)
IR_OPCODE(WriteLaneU32, General, Lane)
IR_OPCODE(Permlane16B32, General, Lane)
IR_OPCODE(Permlanex16B32, General, Lane)
IR_OPCODE(AbsI32, General, SimpleInteger)
IR_OPCODE(IAddU32, General, SimpleInteger)
IR_OPCODE(IAddCarryU32, General, State)
IR_OPCODE(ISubBorrowU32, General, State)
IR_OPCODE(ScalarAddCarryU32, General, State)
IR_OPCODE(ScalarSubBorrowU32, General, State)
IR_OPCODE(ScalarSubBorrowCarryU32, General, State)
IR_OPCODE(ScalarSignedAddOverflowI32, General, State)
IR_OPCODE(ScalarSignedSubOverflowI32, General, State)
IR_OPCODE(ScalarShiftLeftAddCarryU32, General, State)
IR_OPCODE(ISubU32, General, SimpleInteger)
IR_OPCODE(IMulU32, General, SimpleInteger)
IR_OPCODE(UMulHighU32, General, SimpleInteger)
IR_OPCODE(SMulHighI32, General, SimpleInteger)
IR_OPCODE(IMadI24U32, General, ExtendedInteger)
IR_OPCODE(UMadU24U32, General, ExtendedInteger)
IR_OPCODE(UMadU64U32, General, ExtendedInteger)
IR_OPCODE(SadU32, General, ExtendedInteger)
IR_OPCODE(IAdd3U32, General, ExtendedInteger)
IR_OPCODE(IMulI24U32, General, ExtendedInteger)
IR_OPCODE(UMulU24U32, General, ExtendedInteger)
IR_OPCODE(IMinI32, General, SimpleInteger)
IR_OPCODE(IMaxI32, General, SimpleInteger)
IR_OPCODE(IMin3I32, General, SimpleInteger)
IR_OPCODE(IMax3I32, General, SimpleInteger)
IR_OPCODE(IMed3I32, General, SimpleInteger)
IR_OPCODE(UMinU32, General, SimpleInteger)
IR_OPCODE(UMaxU32, General, SimpleInteger)
IR_OPCODE(UMin3U32, General, SimpleInteger)
IR_OPCODE(UMax3U32, General, SimpleInteger)
IR_OPCODE(UMed3U32, General, SimpleInteger)
IR_OPCODE(BitwiseAndU32, General, SimpleInteger)
IR_OPCODE(BitwiseAndU64, General, U64Mask)
IR_OPCODE(BitwiseAndNotU32, General, ComposedInteger)
IR_OPCODE(BitwiseAndNotU64, General, U64Mask)
IR_OPCODE(BitwiseOrU32, General, SimpleInteger)
IR_OPCODE(BitwiseOrU64, General, U64Mask)
IR_OPCODE(BitwiseOrNotU32, General, ComposedInteger)
IR_OPCODE(BitwiseOrNotU64, General, U64Mask)
IR_OPCODE(BitwiseAndOrU32, General, ComposedInteger)
IR_OPCODE(BitwiseOr3U32, General, ComposedInteger)
IR_OPCODE(BitwiseXorU32, General, SimpleInteger)
IR_OPCODE(BitwiseXorU64, General, U64Mask)
IR_OPCODE(BitwiseXor3U32, General, ComposedInteger)
IR_OPCODE(BitwiseNandU32, General, ComposedInteger)
IR_OPCODE(BitwiseNandU64, General, U64Mask)
IR_OPCODE(BitwiseNorU32, General, ComposedInteger)
IR_OPCODE(BitwiseNorU64, General, U64Mask)
IR_OPCODE(BitwiseXnorU32, General, ComposedInteger)
IR_OPCODE(BitwiseXnorU64, General, U64Mask)
IR_OPCODE(BitwiseNotU32, General, SimpleInteger)
IR_OPCODE(BitwiseNotU64, General, U64Mask)
IR_OPCODE(BitClearU32, General, ExtendedInteger)
IR_OPCODE(BitSetU32, General, ExtendedInteger)
IR_OPCODE(BitReverseU32, General, SimpleInteger)
IR_OPCODE(BitCountU32, General, SimpleInteger)
IR_OPCODE(BitCountU64, General, SimpleInteger)
IR_OPCODE(BitReplicateB64B32, General, ExtendedInteger)
IR_OPCODE(BitCountAddU32, General, ExtendedInteger)
IR_OPCODE(MaskedBitCountLowU32, General, ExtendedInteger)
IR_OPCODE(MaskedBitCountHighU32, General, ExtendedInteger)
IR_OPCODE(FindLsbU32, General, SimpleInteger)
IR_OPCODE(FindMsbFromHighU32, General, ComposedInteger)
IR_OPCODE(FindMsbFromHighU64, General, ComposedInteger)
IR_OPCODE(BitFieldMaskU32, General, ExtendedInteger)
IR_OPCODE(BitFieldMaskU64, General, ExtendedInteger)
IR_OPCODE(BitFieldExtractU32, General, ExtendedInteger)
IR_OPCODE(BitFieldExtractU64, General, ExtendedInteger)
IR_OPCODE(BitFieldExtract3U32, General, ExtendedInteger)
IR_OPCODE(BitFieldExtract3I32, General, ExtendedInteger)
IR_OPCODE(BitFieldInsertSelectU32, General, ExtendedInteger)
IR_OPCODE(BitCompare0B32, General, ExtendedInteger)
IR_OPCODE(BitCompare1B32, General, ExtendedInteger)
IR_OPCODE(AlignBitU32, General, ExtendedInteger)
IR_OPCODE(ShiftLeftAddU32, General, ExtendedInteger)
IR_OPCODE(AddShiftLeftU32, General, ExtendedInteger)
IR_OPCODE(XorAddU32, General, ExtendedInteger)
IR_OPCODE(ShiftLeftOrU32, General, ExtendedInteger)
IR_OPCODE(ShiftLeftLogicalU32, General, SimpleInteger)
IR_OPCODE(ShiftLeftLogicalU64, General, SimpleInteger)
IR_OPCODE(ShiftLeftLogicalU16, General, Integer16)
IR_OPCODE(ShiftRightLogicalU32, General, SimpleInteger)
IR_OPCODE(ShiftRightLogicalU64, General, SimpleInteger)
IR_OPCODE(ShiftRightLogicalU16, General, Integer16)
IR_OPCODE(ShiftRightArithmeticI32, General, SimpleInteger)
IR_OPCODE(ShiftRightArithmeticI16, General, Integer16)
IR_OPCODE(SelectU32, General, ExtendedInteger, U32, U1, U32, U32)
IR_OPCODE(SelectMaskU32, General, ExtendedInteger)
IR_OPCODE(SelectF32Bits, General, ExtendedInteger)
IR_OPCODE(SelectMaskF32Bits, General, ExtendedInteger)
IR_OPCODE(SelectU64, General, ExtendedInteger)
IR_OPCODE(PackLowLowU16, General, ExtendedInteger)
IR_OPCODE(PackLowHighU16, General, ExtendedInteger)
IR_OPCODE(PackHighHighU16, General, ExtendedInteger)
IR_OPCODE(CompareFalse, Compare, IntegerCompare)
IR_OPCODE(CompareTrue, Compare, IntegerCompare)
IR_OPCODE(CompareEqU32, Compare, IntegerCompare)
IR_OPCODE(CompareNeU32, Compare, IntegerCompare)
IR_OPCODE(CompareGtU32, Compare, IntegerCompare)
IR_OPCODE(CompareGeU32, Compare, IntegerCompare)
IR_OPCODE(CompareLtU32, Compare, IntegerCompare)
IR_OPCODE(CompareLeU32, Compare, IntegerCompare)
IR_OPCODE(CompareEqU64, Compare64, IntegerCompare)
IR_OPCODE(CompareNeU64, Compare64, IntegerCompare)
IR_OPCODE(CompareMaskEqU32, Compare, IntegerCompare)
IR_OPCODE(CompareMaskNeU32, Compare, IntegerCompare)
IR_OPCODE(CompareMaskGtU32, Compare, IntegerCompare)
IR_OPCODE(CompareMaskGeU32, Compare, IntegerCompare)
IR_OPCODE(CompareMaskLtU32, Compare, IntegerCompare)
IR_OPCODE(CompareMaskLeU32, Compare, IntegerCompare)
IR_OPCODE(CompareEqI32, Compare, IntegerCompare)
IR_OPCODE(CompareNeI32, Compare, IntegerCompare)
IR_OPCODE(CompareGtI32, Compare, IntegerCompare)
IR_OPCODE(CompareGeI32, Compare, IntegerCompare)
IR_OPCODE(CompareLtI32, Compare, IntegerCompare)
IR_OPCODE(CompareLeI32, Compare, IntegerCompare)
IR_OPCODE(CompareEqI16, Compare, Integer16Compare)
IR_OPCODE(CompareNeI16, Compare, Integer16Compare)
IR_OPCODE(CompareGtI16, Compare, Integer16Compare)
IR_OPCODE(CompareGeI16, Compare, Integer16Compare)
IR_OPCODE(CompareLtI16, Compare, Integer16Compare)
IR_OPCODE(CompareLeI16, Compare, Integer16Compare)
IR_OPCODE(CompareMaskEqI32, Compare, IntegerCompare)
IR_OPCODE(CompareMaskNeI32, Compare, IntegerCompare)
IR_OPCODE(CompareMaskGtI32, Compare, IntegerCompare)
IR_OPCODE(CompareMaskGeI32, Compare, IntegerCompare)
IR_OPCODE(CompareMaskLtI32, Compare, IntegerCompare)
IR_OPCODE(CompareMaskLeI32, Compare, IntegerCompare)
IR_OPCODE(CompareEqU16, Compare, Integer16Compare)
IR_OPCODE(CompareNeU16, Compare, Integer16Compare)
IR_OPCODE(CompareGtU16, Compare, Integer16Compare)
IR_OPCODE(CompareGeU16, Compare, Integer16Compare)
IR_OPCODE(CompareLtU16, Compare, Integer16Compare)
IR_OPCODE(CompareLeU16, Compare, Integer16Compare)
IR_OPCODE(CompareEqF32, Compare, FloatCompare)
IR_OPCODE(CompareNeF32, Compare, FloatCompare)
IR_OPCODE(CompareGtF32, Compare, FloatCompare)
IR_OPCODE(CompareGeF32, Compare, FloatCompare)
IR_OPCODE(CompareLtF32, Compare, FloatCompare)
IR_OPCODE(CompareLeF32, Compare, FloatCompare)
IR_OPCODE(CompareOrderedF32, Compare, FloatCompare)
IR_OPCODE(CompareUnorderedF32, Compare, FloatCompare)
IR_OPCODE(CompareUnordEqF32, Compare, FloatCompare)
IR_OPCODE(CompareUnordNeF32, Compare, FloatCompare)
IR_OPCODE(CompareUnordGtF32, Compare, FloatCompare)
IR_OPCODE(CompareUnordGeF32, Compare, FloatCompare)
IR_OPCODE(CompareUnordLtF32, Compare, FloatCompare)
IR_OPCODE(CompareUnordLeF32, Compare, FloatCompare)
IR_OPCODE(CompareClassF32, Compare, FloatCompare)
IR_OPCODE(CompareEqF16, Compare, FloatCompare)
IR_OPCODE(CompareNeF16, Compare, FloatCompare)
IR_OPCODE(CompareGtF16, Compare, FloatCompare)
IR_OPCODE(CompareGeF16, Compare, FloatCompare)
IR_OPCODE(CompareLtF16, Compare, FloatCompare)
IR_OPCODE(CompareLeF16, Compare, FloatCompare)
IR_OPCODE(CompareUnordNeF16, Compare, FloatCompare)
IR_OPCODE(CompareMaskEqF16, Compare, FloatCompare)
IR_OPCODE(CompareMaskNeF16, Compare, FloatCompare)
IR_OPCODE(CompareMaskGtF16, Compare, FloatCompare)
IR_OPCODE(CompareMaskGeF16, Compare, FloatCompare)
IR_OPCODE(CompareMaskLtF16, Compare, FloatCompare)
IR_OPCODE(CompareMaskLeF16, Compare, FloatCompare)
IR_OPCODE(CompareMaskUnordNeF16, Compare, FloatCompare)
IR_OPCODE(CompareMaskUnordGeF16, Compare, FloatCompare)
IR_OPCODE(CompareMaskEqF32, Compare, FloatCompare)
IR_OPCODE(CompareMaskNeF32, Compare, FloatCompare)
IR_OPCODE(CompareMaskGtF32, Compare, FloatCompare)
IR_OPCODE(CompareMaskGeF32, Compare, FloatCompare)
IR_OPCODE(CompareMaskLtF32, Compare, FloatCompare)
IR_OPCODE(CompareMaskLeF32, Compare, FloatCompare)
IR_OPCODE(CompareMaskUnordEqF32, Compare, FloatCompare)
IR_OPCODE(CompareMaskUnordNeF32, Compare, FloatCompare)
IR_OPCODE(CompareMaskUnordGtF32, Compare, FloatCompare)
IR_OPCODE(CompareMaskUnordGeF32, Compare, FloatCompare)
IR_OPCODE(CompareMaskUnordLtF32, Compare, FloatCompare)
IR_OPCODE(CompareMaskUnordLeF32, Compare, FloatCompare)
IR_OPCODE(ConvertByteU32ToF32, General, Conversion)
IR_OPCODE(ConvertU32ToF32, General, Conversion)
IR_OPCODE(ConvertI32ToF32, General, Conversion)
IR_OPCODE(ConvertF32ToU32, General, Conversion)
IR_OPCODE(ConvertF32ToI32, General, Conversion)
IR_OPCODE(ConvertF32ToF16, General, Conversion)
IR_OPCODE(ConvertF16ToF32, General, Conversion)
IR_OPCODE(ConvertU16ToF16, General, Conversion)
IR_OPCODE(ConvertF16ToU16, General, Conversion)
IR_OPCODE(ConvertI16ToF16, General, Conversion)
IR_OPCODE(ConvertF16ToI16, General, Conversion)
IR_OPCODE(ConvertRoundPlusInfF32ToI32, General, Conversion)
IR_OPCODE(ConvertFloorF32ToI32, General, Conversion)
IR_OPCODE(ConvertI4ToOffsetF32, General, Conversion)
IR_OPCODE(LdexpF32, General, Float)
IR_OPCODE(PackF32ToF16Rtz, General, Conversion)
IR_OPCODE(PackSnorm2x16F32, General, Conversion)
IR_OPCODE(PackUnorm2x16F32, General, Conversion)
IR_OPCODE(PackU16U32, General, ExtendedInteger)
IR_OPCODE(PackU8F32, General, Conversion)
IR_OPCODE(PackB32F16, General, Conversion)
IR_OPCODE(PackedMadI16, General, PackedInteger16)
IR_OPCODE(PackedMulLoU16, General, PackedInteger16)
IR_OPCODE(PackedAddI16, General, PackedInteger16)
IR_OPCODE(PackedSubI16, General, PackedInteger16)
IR_OPCODE(PackedLshlrevB16, General, PackedInteger16)
IR_OPCODE(PackedLshrrevB16, General, PackedInteger16)
IR_OPCODE(PackedAshrrevI16, General, PackedInteger16)
IR_OPCODE(PackedMaxI16, General, PackedInteger16)
IR_OPCODE(PackedMinI16, General, PackedInteger16)
IR_OPCODE(PackedMadU16, General, PackedInteger16)
IR_OPCODE(PackedAddU16, General, PackedInteger16)
IR_OPCODE(PackedSubU16, General, PackedInteger16)
IR_OPCODE(PackedMaxU16, General, PackedInteger16)
IR_OPCODE(PackedMinU16, General, PackedInteger16)
IR_OPCODE(PackedAddF16, General, PackedFloat16)
IR_OPCODE(PackedMulF16, General, PackedFloat16)
IR_OPCODE(PackedMinF16, General, PackedFloat16)
IR_OPCODE(PackedMaxF16, General, PackedFloat16)
IR_OPCODE(PackedFmaF16, General, PackedFloat16)
IR_OPCODE(AddF16, General, Float16)
IR_OPCODE(SubF16, General, Float16)
IR_OPCODE(MulF16, General, Float16)
IR_OPCODE(MinF16, General, Float16)
IR_OPCODE(MaxF16, General, Float16)
IR_OPCODE(FmaF16, General, Float16)
IR_OPCODE(MadMixF16, General, Float16)
IR_OPCODE(IAddU16, General, Integer16)
IR_OPCODE(ISubI16, General, Integer16)
IR_OPCODE(IMinI16, General, Integer16)
IR_OPCODE(IMaxI16, General, Integer16)
IR_OPCODE(UMinU16, General, Integer16)
IR_OPCODE(UMaxU16, General, Integer16)
IR_OPCODE(RcpF32, General, Float)
IR_OPCODE(RcpIflagF32, General, Float)
IR_OPCODE(FractF32, General, Float)
IR_OPCODE(TruncF32, General, Float)
IR_OPCODE(CeilF32, General, Float)
IR_OPCODE(RoundEvenF32, General, Float)
IR_OPCODE(FloorF32, General, Float)
IR_OPCODE(Exp2F32, General, Float)
IR_OPCODE(Log2F32, General, Float)
IR_OPCODE(InverseSqrtF32, General, Float)
IR_OPCODE(SqrtF32, General, Float)
IR_OPCODE(RcpF16, General, Float16)
IR_OPCODE(SqrtF16, General, Float16)
IR_OPCODE(InverseSqrtF16, General, Float16)
IR_OPCODE(Log2F16, General, Float16)
IR_OPCODE(Exp2F16, General, Float16)
IR_OPCODE(FloorF16, General, Float16)
IR_OPCODE(CeilF16, General, Float16)
IR_OPCODE(TruncF16, General, Float16)
IR_OPCODE(RoundEvenF16, General, Float16)
IR_OPCODE(SinF32, General, Float)
IR_OPCODE(CosF32, General, Float)
IR_OPCODE(CubeIdF32, General, Float)
IR_OPCODE(CubeScF32, General, Float)
IR_OPCODE(CubeTcF32, General, Float)
IR_OPCODE(CubeMaF32, General, Float)
IR_OPCODE(FAddF32, General, Float)
IR_OPCODE(FSubF32, General, Float)
IR_OPCODE(FMulF32, General, Float)
IR_OPCODE(FMinF32, General, Float)
IR_OPCODE(FMaxF32, General, Float)
IR_OPCODE(FMadF32, General, Float)
IR_OPCODE(Dot2AccF32F16, General, Float)
IR_OPCODE(FMin3F32, General, Float)
IR_OPCODE(FMax3F32, General, Float)
IR_OPCODE(FMed3F32, General, Float)
IR_OPCODE(Min3F16, General, Float16)
IR_OPCODE(Max3F16, General, Float16)
IR_OPCODE(Med3F16, General, Float16)
IR_OPCODE(LoadSrtDword, General, Memory)
IR_OPCODE(SLoadDword, General, Memory)
IR_OPCODE(SBufferLoadDword, General, Memory)
IR_OPCODE(BufferLoadUbyte, General, Memory)
IR_OPCODE(BufferLoadSbyte, General, Memory)
IR_OPCODE(BufferLoadUshort, General, Memory)
IR_OPCODE(BufferLoadSshort, General, Memory)
IR_OPCODE(BufferLoadDword, General, Memory)
IR_OPCODE(BufferStoreByte, General, Memory)
IR_OPCODE(BufferStoreShort, General, Memory)
IR_OPCODE(BufferStoreDword, General, Memory)
IR_OPCODE(AtomicSwapU32, General, Memory)
IR_OPCODE(AtomicAddU32, General, Memory)
IR_OPCODE(AtomicSubU32, General, Memory)
IR_OPCODE(AtomicSMinI32, General, Memory)
IR_OPCODE(AtomicUMinU32, General, Memory)
IR_OPCODE(AtomicSMaxI32, General, Memory)
IR_OPCODE(AtomicUMaxU32, General, Memory)
IR_OPCODE(AtomicAndU32, General, Memory)
IR_OPCODE(AtomicOrU32, General, Memory)
IR_OPCODE(AtomicXorU32, General, Memory)
IR_OPCODE(AtomicFMinF32, General, Memory)
IR_OPCODE(AtomicFMaxF32, General, Memory)
IR_OPCODE(FlatLoadUbyte, General, Memory)
IR_OPCODE(FlatLoadSbyte, General, Memory)
IR_OPCODE(FlatLoadUshort, General, Memory)
IR_OPCODE(FlatLoadSshort, General, Memory)
IR_OPCODE(FlatLoadDword, General, Memory)
IR_OPCODE(FlatStoreByte, General, Memory)
IR_OPCODE(FlatStoreShort, General, Memory)
IR_OPCODE(FlatStoreDword, General, Memory)
IR_OPCODE(DsReadUbyte, General, Memory)
IR_OPCODE(DsReadSbyte, General, Memory)
IR_OPCODE(DsReadUshort, General, Memory)
IR_OPCODE(DsReadSshort, General, Memory)
IR_OPCODE(DsReadB32, General, Memory)
IR_OPCODE(DsWriteByte, General, Memory)
IR_OPCODE(DsWriteShort, General, Memory)
IR_OPCODE(DsWriteB32, General, Memory)
IR_OPCODE(DsMinF32, General, Memory)
IR_OPCODE(DsMaxF32, General, Memory)
IR_OPCODE(DsSwizzleB32, General, Memory)
IR_OPCODE(DsConsume, General, Memory)
IR_OPCODE(DsAppend, General, Memory)
IR_OPCODE(DsWriteAddtidB32, General, Memory)
IR_OPCODE(DsReadAddtidB32, General, Memory)
IR_OPCODE(ImageGetResinfo, General, Memory)
IR_OPCODE(ImageGetLod, General, Memory)
IR_OPCODE(ImageLoad, General, Memory)
IR_OPCODE(ImageStore, General, Memory)
IR_OPCODE(ImageSample, General, Memory)
IR_OPCODE(ImageGather4, General, Memory)
IR_OPCODE(LoadInputF32, General, Attribute)
IR_OPCODE(Export, General, Attribute)
@@ -0,0 +1,144 @@
#include "graphics/shader/recompiler/ir/opcodes/ValueOpcodes.h"
#include <array>
namespace Libs::Graphics::ShaderRecompiler::IR {
namespace {
struct OpcodeMeta {
std::string_view name;
Type type = Type::Void;
std::array<Type, 16> args = {};
size_t num_args = 0;
};
template <typename... Args>
consteval OpcodeMeta MakeMeta(std::string_view name, Type type, Args... args) {
static_assert(sizeof...(Args) <= 16);
OpcodeMeta meta {.name = name, .type = type, .num_args = sizeof...(Args)};
meta.args.fill(Type::Void);
size_t index = 0;
((meta.args[index++] = args), ...);
return meta;
}
constexpr Type Void = Type::Void;
constexpr Type Opaque = Type::Opaque;
constexpr Type ScalarReg = Type::ScalarReg;
constexpr Type VectorReg = Type::VectorReg;
constexpr Type U1 = Type::U1;
constexpr Type U8 = Type::U8;
constexpr Type U16 = Type::U16;
constexpr Type U32 = Type::U32;
constexpr Type U64 = Type::U64;
constexpr Type F16 = Type::F16;
constexpr Type F32 = Type::F32;
constexpr Type U32x2 = Type::U32x2;
constexpr Type U32x4 = Type::U32x4;
constexpr Type F32x2 = Type::F32x2;
constexpr Type SrtResource = Type::SrtResource;
constexpr Type BufferResource = Type::BufferResource;
constexpr Type AddressResource = Type::AddressResource;
constexpr Type ImageResource = Type::ImageResource;
constexpr Type SamplerResource = Type::SamplerResource;
constexpr Type LdsResource = Type::LdsResource;
constexpr Type GdsResource = Type::GdsResource;
constexpr Type ImageAddress = Type::ImageAddress;
constexpr std::array<OpcodeMeta, static_cast<size_t>(ValueOpcode::Count)> MetaTable = {{
#define VALUE_OPCODE(name, ...) MakeMeta(#name __VA_OPT__(, ) __VA_ARGS__),
#include "graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc"
#undef VALUE_OPCODE
}};
} // namespace
Type TypeOf(ValueOpcode opcode) {
return MetaTable[static_cast<size_t>(opcode)].type;
}
size_t NumArgsOf(ValueOpcode opcode) {
return MetaTable[static_cast<size_t>(opcode)].num_args;
}
Type ArgTypeOf(ValueOpcode opcode, size_t index) {
const auto& meta = MetaTable[static_cast<size_t>(opcode)];
return meta.args.at(index);
}
std::string_view ValueOpcodeName(ValueOpcode opcode) {
return MetaTable[static_cast<size_t>(opcode)].name;
}
bool HasSideEffects(ValueOpcode opcode) {
switch (opcode) {
case ValueOpcode::Reference:
case ValueOpcode::ReferenceU32:
case ValueOpcode::Prologue:
case ValueOpcode::Epilogue:
case ValueOpcode::StoreAddressU8:
case ValueOpcode::StoreAddressU16:
case ValueOpcode::StoreAddressU32:
case ValueOpcode::StoreBufferU8:
case ValueOpcode::StoreBufferU16:
case ValueOpcode::StoreBufferU32:
case ValueOpcode::WriteSharedU8:
case ValueOpcode::WriteSharedU16:
case ValueOpcode::WriteSharedU32:
case ValueOpcode::WriteGdsU8:
case ValueOpcode::WriteGdsU16:
case ValueOpcode::WriteGdsU32:
case ValueOpcode::SharedAtomicFMin32:
case ValueOpcode::SharedAtomicFMax32:
case ValueOpcode::GdsAtomicFMin32:
case ValueOpcode::GdsAtomicFMax32:
case ValueOpcode::SharedAtomicSwap32:
case ValueOpcode::SharedAtomicIAdd32:
case ValueOpcode::SharedAtomicISub32:
case ValueOpcode::SharedAtomicSMin32:
case ValueOpcode::SharedAtomicUMin32:
case ValueOpcode::SharedAtomicSMax32:
case ValueOpcode::SharedAtomicUMax32:
case ValueOpcode::SharedAtomicAnd32:
case ValueOpcode::SharedAtomicOr32:
case ValueOpcode::SharedAtomicXor32:
case ValueOpcode::GdsAtomicSwap32:
case ValueOpcode::GdsAtomicIAdd32:
case ValueOpcode::GdsAtomicISub32:
case ValueOpcode::GdsAtomicSMin32:
case ValueOpcode::GdsAtomicUMin32:
case ValueOpcode::GdsAtomicSMax32:
case ValueOpcode::GdsAtomicUMax32:
case ValueOpcode::GdsAtomicAnd32:
case ValueOpcode::GdsAtomicOr32:
case ValueOpcode::GdsAtomicXor32:
case ValueOpcode::BufferAtomicSwap32:
case ValueOpcode::BufferAtomicIAdd32:
case ValueOpcode::BufferAtomicISub32:
case ValueOpcode::BufferAtomicSMin32:
case ValueOpcode::BufferAtomicUMin32:
case ValueOpcode::BufferAtomicSMax32:
case ValueOpcode::BufferAtomicUMax32:
case ValueOpcode::BufferAtomicAnd32:
case ValueOpcode::BufferAtomicOr32:
case ValueOpcode::BufferAtomicXor32:
case ValueOpcode::BufferAtomicFMin32:
case ValueOpcode::BufferAtomicFMax32:
case ValueOpcode::ImageAtomicIAdd32:
case ValueOpcode::ImageAtomicUMin32:
case ValueOpcode::ImageAtomicUMax32:
case ValueOpcode::ImageAtomicAnd32:
case ValueOpcode::ImageAtomicOr32:
case ValueOpcode::ImageAtomicXor32:
case ValueOpcode::DataAppend:
case ValueOpcode::DataConsume:
case ValueOpcode::GdsDataAppend:
case ValueOpcode::GdsDataConsume:
case ValueOpcode::ImageWrite:
case ValueOpcode::SetAttribute:
case ValueOpcode::Barrier: return true;
default: return false;
}
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -0,0 +1,57 @@
#pragma once
#include "graphics/shader/recompiler/ir/Type.h"
#include <cstddef>
#include <cstdint>
#include <string_view>
#include <type_traits>
namespace Libs::Graphics::ShaderRecompiler::IR {
enum class ValueOpcode {
#define VALUE_OPCODE(name, ...) name,
#include "graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc"
#undef VALUE_OPCODE
Count,
};
struct DppMoveFlags {
uint16_t control = 0;
uint8_t row_mask = 0xf;
uint8_t bank_mask = 0xf;
bool fetch_inactive = false;
bool bound_control = false;
};
static_assert(sizeof(DppMoveFlags) <= sizeof(uint64_t));
static_assert(std::is_trivially_copyable_v<DppMoveFlags>);
struct PermlaneFlags {
bool x16 = false;
bool fetch_inactive = false;
bool bound_control = false;
};
static_assert(sizeof(PermlaneFlags) <= sizeof(uint64_t));
static_assert(std::is_trivially_copyable_v<PermlaneFlags>);
struct MemoryFlags {
uint32_t index = 0;
uint32_t pc = 0;
};
static_assert(sizeof(MemoryFlags) == sizeof(uint64_t));
static_assert(std::is_trivially_copyable_v<MemoryFlags>);
struct ExportFlags {
uint32_t index = 0;
uint32_t pc = 0;
};
static_assert(sizeof(ExportFlags) == sizeof(uint64_t));
static_assert(std::is_trivially_copyable_v<ExportFlags>);
[[nodiscard]] Type TypeOf(ValueOpcode opcode);
[[nodiscard]] Type ArgTypeOf(ValueOpcode opcode, size_t index);
[[nodiscard]] size_t NumArgsOf(ValueOpcode opcode);
[[nodiscard]] bool HasSideEffects(ValueOpcode opcode);
[[nodiscard]] std::string_view ValueOpcodeName(ValueOpcode opcode);
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -0,0 +1,299 @@
// operation, result, arguments...
VALUE_OPCODE(Phi, Opaque)
VALUE_OPCODE(Identity, Opaque, Opaque)
VALUE_OPCODE(Void, Void)
VALUE_OPCODE(ConditionRef, U1, U1)
VALUE_OPCODE(Reference, Void, U1)
VALUE_OPCODE(ReferenceU32, Void, U32)
// Translation and register-state pseudo operations.
VALUE_OPCODE(Prologue, Void)
VALUE_OPCODE(Epilogue, Void)
VALUE_OPCODE(GetUserData, U32, ScalarReg)
VALUE_OPCODE(GetShaderBase, U64)
VALUE_OPCODE(GetBuiltin, U32, U32, U32)
VALUE_OPCODE(GetThreadBitScalarRegister, U1, ScalarReg)
VALUE_OPCODE(SetThreadBitScalarRegister, Void, ScalarReg, U1)
VALUE_OPCODE(GetScalarRegister, U32, ScalarReg)
VALUE_OPCODE(SetScalarRegister, Void, ScalarReg, U32)
VALUE_OPCODE(GetVectorRegister, U32, VectorReg)
VALUE_OPCODE(SetVectorRegister, Void, VectorReg, U32)
VALUE_OPCODE(GetGotoVariable, U1, U32)
VALUE_OPCODE(SetGotoVariable, Void, U32, U1)
VALUE_OPCODE(GetScc, U1)
VALUE_OPCODE(SetScc, Void, U1)
VALUE_OPCODE(GetExec, U1)
VALUE_OPCODE(SetExec, Void, U1)
VALUE_OPCODE(GetExecLo, U32)
VALUE_OPCODE(SetExecLo, Void, U32)
VALUE_OPCODE(GetExecHi, U32)
VALUE_OPCODE(SetExecHi, Void, U32)
VALUE_OPCODE(GetVcc, U1)
VALUE_OPCODE(SetVcc, Void, U1)
VALUE_OPCODE(GetVccLo, U32)
VALUE_OPCODE(SetVccLo, Void, U32)
VALUE_OPCODE(GetVccHi, U32)
VALUE_OPCODE(SetVccHi, Void, U32)
VALUE_OPCODE(GetM0, U32)
VALUE_OPCODE(SetM0, Void, U32)
VALUE_OPCODE(UndefU1, U1)
VALUE_OPCODE(UndefU8, U8)
VALUE_OPCODE(UndefU16, U16)
VALUE_OPCODE(UndefU32, U32)
VALUE_OPCODE(UndefU64, U64)
// Value construction and bit-preserving conversion.
VALUE_OPCODE(BitCastU16F16, U16, F16)
VALUE_OPCODE(BitCastF16U16, F16, U16)
VALUE_OPCODE(BitCastU32F32, U32, F32)
VALUE_OPCODE(BitCastF32U32, F32, U32)
VALUE_OPCODE(ConvertU16U32, U16, U32)
VALUE_OPCODE(ConvertU32U16, U32, U16)
VALUE_OPCODE(ConvertU8U32, U8, U32)
VALUE_OPCODE(ConvertU32U8, U32, U8)
VALUE_OPCODE(ConvertF32F16, F32, F16)
VALUE_OPCODE(ConvertF16F32, F16, F32)
VALUE_OPCODE(ConvertS32F32, U32, F32)
VALUE_OPCODE(ConvertU32F32, U32, F32)
VALUE_OPCODE(ConvertF32S32, F32, U32)
VALUE_OPCODE(ConvertF32U32, F32, U32)
VALUE_OPCODE(CompositeConstructU32x2, U32x2, U32, U32)
VALUE_OPCODE(CompositeConstructF32x2, F32x2, F32, F32)
VALUE_OPCODE(CompositeConstructU32x4, U32x4, U32, U32, U32, U32)
VALUE_OPCODE(CompositeExtractU32x2, U32, U32x2, U32)
VALUE_OPCODE(CompositeExtractU32x4, U32, U32x4, U32)
VALUE_OPCODE(PackUint2x32, U64, U32x2)
VALUE_OPCODE(UnpackUint2x32, U32x2, U64)
VALUE_OPCODE(PackHalf2x16, U32, F32x2)
VALUE_OPCODE(PackSnorm2x16, U32, F32x2)
VALUE_OPCODE(PackUnorm2x16, U32, F32x2)
VALUE_OPCODE(PackFloat2x16Rtz, U32, F32, F32)
// Source/destination modifiers made explicit by translation.
VALUE_OPCODE(FPAbs32, F32, F32)
VALUE_OPCODE(FPNeg32, F32, F32)
VALUE_OPCODE(FPSaturate32, F32, F32)
VALUE_OPCODE(BitFieldInsert, U32, U32, U32, U32, U32)
VALUE_OPCODE(BitFieldUExtract, U32, U32, U32, U32)
VALUE_OPCODE(BitFieldSExtract, U32, U32, U32, U32)
VALUE_OPCODE(DppMoveU32, U32, U32, U1)
VALUE_OPCODE(DppUpdateU32, U32, U32, U32, U1)
VALUE_OPCODE(WqmMask, U1, U1)
// Core value operations used to expand architectural side effects.
VALUE_OPCODE(SelectU1, U1, U1, U1, U1)
VALUE_OPCODE(SelectF32, F32, U1, F32, F32)
VALUE_OPCODE(IAdd32, U32, U32, U32)
VALUE_OPCODE(IAdd64, U64, U64, U64)
VALUE_OPCODE(IAddCarry32, U32x2, U32, U32)
VALUE_OPCODE(ISub32, U32, U32, U32)
VALUE_OPCODE(ISub64, U64, U64, U64)
VALUE_OPCODE(IMul32, U32, U32, U32)
VALUE_OPCODE(IMul64, U64, U64, U64)
VALUE_OPCODE(UDiv32, U32, U32, U32)
VALUE_OPCODE(SMulHi, U32, U32, U32)
VALUE_OPCODE(UMulHi, U32, U32, U32)
VALUE_OPCODE(IAbs32, U32, U32)
VALUE_OPCODE(ShiftLeftLogical32, U32, U32, U32)
VALUE_OPCODE(ShiftLeftLogical64, U64, U64, U32)
VALUE_OPCODE(ShiftRightLogical32, U32, U32, U32)
VALUE_OPCODE(ShiftRightLogical64, U64, U64, U32)
VALUE_OPCODE(ShiftRightArithmetic32, U32, U32, U32)
VALUE_OPCODE(ShiftRightArithmetic64, U64, U64, U32)
VALUE_OPCODE(BitwiseAnd32, U32, U32, U32)
VALUE_OPCODE(BitwiseAnd64, U64, U64, U64)
VALUE_OPCODE(BitwiseOr32, U32, U32, U32)
VALUE_OPCODE(BitwiseOr64, U64, U64, U64)
VALUE_OPCODE(BitwiseXor32, U32, U32, U32)
VALUE_OPCODE(BitwiseNot32, U32, U32)
VALUE_OPCODE(BitReverse32, U32, U32)
VALUE_OPCODE(BitCount32, U32, U32)
VALUE_OPCODE(BitCount64, U32, U64)
VALUE_OPCODE(FindUMsb32, U32, U32)
VALUE_OPCODE(FindUMsb64, U32, U64)
VALUE_OPCODE(FindSMsb32, U32, U32)
VALUE_OPCODE(FindILsb32, U32, U32)
VALUE_OPCODE(SMin32, U32, U32, U32)
VALUE_OPCODE(UMin32, U32, U32, U32)
VALUE_OPCODE(SMax32, U32, U32, U32)
VALUE_OPCODE(UMax32, U32, U32, U32)
VALUE_OPCODE(SMinTri32, U32, U32, U32, U32)
VALUE_OPCODE(UMinTri32, U32, U32, U32, U32)
VALUE_OPCODE(SMaxTri32, U32, U32, U32, U32)
VALUE_OPCODE(UMaxTri32, U32, U32, U32, U32)
VALUE_OPCODE(SMedTri32, U32, U32, U32, U32)
VALUE_OPCODE(UMedTri32, U32, U32, U32, U32)
VALUE_OPCODE(SLessThan32, U1, U32, U32)
VALUE_OPCODE(SLessThan64, U1, U64, U64)
VALUE_OPCODE(ULessThan32, U1, U32, U32)
VALUE_OPCODE(ULessThan64, U1, U64, U64)
VALUE_OPCODE(IEqual32, U1, U32, U32)
VALUE_OPCODE(IEqual64, U1, U64, U64)
VALUE_OPCODE(SLessThanEqual32, U1, U32, U32)
VALUE_OPCODE(SLessThanEqual64, U1, U64, U64)
VALUE_OPCODE(ULessThanEqual32, U1, U32, U32)
VALUE_OPCODE(ULessThanEqual64, U1, U64, U64)
VALUE_OPCODE(SGreaterThan32, U1, U32, U32)
VALUE_OPCODE(SGreaterThan64, U1, U64, U64)
VALUE_OPCODE(UGreaterThan32, U1, U32, U32)
VALUE_OPCODE(UGreaterThan64, U1, U64, U64)
VALUE_OPCODE(INotEqual32, U1, U32, U32)
VALUE_OPCODE(INotEqual64, U1, U64, U64)
VALUE_OPCODE(SGreaterThanEqual32, U1, U32, U32)
VALUE_OPCODE(SGreaterThanEqual64, U1, U64, U64)
VALUE_OPCODE(UGreaterThanEqual32, U1, U32, U32)
VALUE_OPCODE(UGreaterThanEqual64, U1, U64, U64)
VALUE_OPCODE(LogicalOr, U1, U1, U1)
VALUE_OPCODE(LogicalAnd, U1, U1, U1)
VALUE_OPCODE(LogicalXor, U1, U1, U1)
VALUE_OPCODE(LogicalNot, U1, U1)
VALUE_OPCODE(FPOrdEqual32, U1, F32, F32)
VALUE_OPCODE(FPUnordEqual32, U1, F32, F32)
VALUE_OPCODE(FPOrdNotEqual32, U1, F32, F32)
VALUE_OPCODE(FPUnordNotEqual32, U1, F32, F32)
VALUE_OPCODE(FPOrdLessThan32, U1, F32, F32)
VALUE_OPCODE(FPUnordLessThan32, U1, F32, F32)
VALUE_OPCODE(FPOrdGreaterThan32, U1, F32, F32)
VALUE_OPCODE(FPUnordGreaterThan32, U1, F32, F32)
VALUE_OPCODE(FPOrdLessThanEqual32, U1, F32, F32)
VALUE_OPCODE(FPUnordLessThanEqual32, U1, F32, F32)
VALUE_OPCODE(FPOrdGreaterThanEqual32, U1, F32, F32)
VALUE_OPCODE(FPUnordGreaterThanEqual32, U1, F32, F32)
VALUE_OPCODE(FPIsNan32, U1, F32)
VALUE_OPCODE(FPCmpClass32, U1, F32, U32)
VALUE_OPCODE(FPAdd32, F32, F32, F32)
VALUE_OPCODE(FPSub32, F32, F32, F32)
VALUE_OPCODE(FPFma32, F32, F32, F32, F32)
VALUE_OPCODE(FPMul32, F32, F32, F32)
VALUE_OPCODE(FPMin32, F32, F32, F32)
VALUE_OPCODE(FPMax32, F32, F32, F32)
VALUE_OPCODE(FPMinTri32, F32, F32, F32, F32)
VALUE_OPCODE(FPMaxTri32, F32, F32, F32, F32)
VALUE_OPCODE(FPMedTri32, F32, F32, F32, F32)
VALUE_OPCODE(FPRecip32, F32, F32)
VALUE_OPCODE(FPRecipIFlag32, F32, F32)
VALUE_OPCODE(FPRecipSqrt32, F32, F32)
VALUE_OPCODE(FPSqrt, F32, F32)
VALUE_OPCODE(FPSin, F32, F32)
VALUE_OPCODE(FPCos, F32, F32)
VALUE_OPCODE(FPExp2, F32, F32)
VALUE_OPCODE(FPLog2, F32, F32)
VALUE_OPCODE(FPLdexp, F32, F32, U32)
VALUE_OPCODE(FPRoundEven32, F32, F32)
VALUE_OPCODE(FPFloor32, F32, F32)
VALUE_OPCODE(FPCeil32, F32, F32)
VALUE_OPCODE(FPTrunc32, F32, F32)
VALUE_OPCODE(FPFract32, F32, F32)
VALUE_OPCODE(LaneId, U32)
VALUE_OPCODE(Ballot, U32x4, U1)
VALUE_OPCODE(ReadFirstLane, U32, U32, U1)
VALUE_OPCODE(ReadLane, U32, U32, U32)
VALUE_OPCODE(WriteLane, U32, U32, U32, U32)
VALUE_OPCODE(Permlane16U32, U32, U32, U32, U32, U1)
// Typed shader resources and memory operations. Descriptor state is represented by SSA values;
// dense bindings are assigned by resource tracking.
VALUE_OPCODE(GetSrtResource, SrtResource)
VALUE_OPCODE(GetBufferResource, BufferResource, U32, U32, U32, U32)
VALUE_OPCODE(GetAddressResource, AddressResource, U32, U32)
VALUE_OPCODE(GetImageResource, ImageResource, U32, U32, U32, U32, U32, U32, U32, U32)
VALUE_OPCODE(GetSamplerResource, SamplerResource, U32, U32, U32, U32)
VALUE_OPCODE(GetLdsResource, LdsResource)
VALUE_OPCODE(GetGdsResource, GdsResource)
VALUE_OPCODE(MakeImageAddress, ImageAddress, U32, U32, U32, U32, U32, U32, U32, U32, U32, U32, U32,
U32, U32)
VALUE_OPCODE(ReadConst, U32, SrtResource, U32)
VALUE_OPCODE(ReadConstBuffer, U32, BufferResource, U32)
VALUE_OPCODE(LoadAddressU8, U8, AddressResource, U32, U32, U1)
VALUE_OPCODE(LoadAddressU16, U16, AddressResource, U32, U32, U1)
VALUE_OPCODE(LoadAddressU32, U32, AddressResource, U32, U32, U1)
VALUE_OPCODE(StoreAddressU8, Void, AddressResource, U32, U32, U8, U1)
VALUE_OPCODE(StoreAddressU16, Void, AddressResource, U32, U32, U16, U1)
VALUE_OPCODE(StoreAddressU32, Void, AddressResource, U32, U32, U32, U1)
VALUE_OPCODE(LoadBufferU8, U8, BufferResource, U32, U32, U32, U1)
VALUE_OPCODE(LoadBufferU16, U16, BufferResource, U32, U32, U32, U1)
VALUE_OPCODE(LoadBufferU32, U32, BufferResource, U32, U32, U32, U1)
VALUE_OPCODE(StoreBufferU8, Void, BufferResource, U32, U32, U32, U8, U1)
VALUE_OPCODE(StoreBufferU16, Void, BufferResource, U32, U32, U32, U16, U1)
VALUE_OPCODE(StoreBufferU32, Void, BufferResource, U32, U32, U32, U32, U1)
VALUE_OPCODE(BufferAtomicSwap32, U32, BufferResource, U32, U32, U32, U32, U1)
VALUE_OPCODE(BufferAtomicIAdd32, U32, BufferResource, U32, U32, U32, U32, U1)
VALUE_OPCODE(BufferAtomicISub32, U32, BufferResource, U32, U32, U32, U32, U1)
VALUE_OPCODE(BufferAtomicSMin32, U32, BufferResource, U32, U32, U32, U32, U1)
VALUE_OPCODE(BufferAtomicUMin32, U32, BufferResource, U32, U32, U32, U32, U1)
VALUE_OPCODE(BufferAtomicSMax32, U32, BufferResource, U32, U32, U32, U32, U1)
VALUE_OPCODE(BufferAtomicUMax32, U32, BufferResource, U32, U32, U32, U32, U1)
VALUE_OPCODE(BufferAtomicAnd32, U32, BufferResource, U32, U32, U32, U32, U1)
VALUE_OPCODE(BufferAtomicOr32, U32, BufferResource, U32, U32, U32, U32, U1)
VALUE_OPCODE(BufferAtomicXor32, U32, BufferResource, U32, U32, U32, U32, U1)
VALUE_OPCODE(BufferAtomicFMin32, U32, BufferResource, U32, U32, U32, U32, U1)
VALUE_OPCODE(BufferAtomicFMax32, U32, BufferResource, U32, U32, U32, U32, U1)
VALUE_OPCODE(LoadSharedU8, U8, LdsResource, U32, U1)
VALUE_OPCODE(LoadSharedU16, U16, LdsResource, U32, U1)
VALUE_OPCODE(LoadSharedU32, U32, LdsResource, U32, U1)
VALUE_OPCODE(WriteSharedU8, Void, LdsResource, U32, U8, U1)
VALUE_OPCODE(WriteSharedU16, Void, LdsResource, U32, U16, U1)
VALUE_OPCODE(WriteSharedU32, Void, LdsResource, U32, U32, U1)
VALUE_OPCODE(LoadGdsU8, U8, GdsResource, U32, U1)
VALUE_OPCODE(LoadGdsU16, U16, GdsResource, U32, U1)
VALUE_OPCODE(LoadGdsU32, U32, GdsResource, U32, U1)
VALUE_OPCODE(WriteGdsU8, Void, GdsResource, U32, U8, U1)
VALUE_OPCODE(WriteGdsU16, Void, GdsResource, U32, U16, U1)
VALUE_OPCODE(WriteGdsU32, Void, GdsResource, U32, U32, U1)
VALUE_OPCODE(SharedAtomicFMin32, Void, LdsResource, U32, U32, U32, U1)
VALUE_OPCODE(SharedAtomicFMax32, Void, LdsResource, U32, U32, U32, U1)
VALUE_OPCODE(GdsAtomicFMin32, Void, GdsResource, U32, U32, U32, U1)
VALUE_OPCODE(GdsAtomicFMax32, Void, GdsResource, U32, U32, U32, U1)
VALUE_OPCODE(SharedAtomicSwap32, U32, LdsResource, U32, U32, U1)
VALUE_OPCODE(SharedAtomicIAdd32, U32, LdsResource, U32, U32, U1)
VALUE_OPCODE(SharedAtomicISub32, U32, LdsResource, U32, U32, U1)
VALUE_OPCODE(SharedAtomicSMin32, U32, LdsResource, U32, U32, U1)
VALUE_OPCODE(SharedAtomicUMin32, U32, LdsResource, U32, U32, U1)
VALUE_OPCODE(SharedAtomicSMax32, U32, LdsResource, U32, U32, U1)
VALUE_OPCODE(SharedAtomicUMax32, U32, LdsResource, U32, U32, U1)
VALUE_OPCODE(SharedAtomicAnd32, U32, LdsResource, U32, U32, U1)
VALUE_OPCODE(SharedAtomicOr32, U32, LdsResource, U32, U32, U1)
VALUE_OPCODE(SharedAtomicXor32, U32, LdsResource, U32, U32, U1)
VALUE_OPCODE(GdsAtomicSwap32, U32, GdsResource, U32, U32, U1)
VALUE_OPCODE(GdsAtomicIAdd32, U32, GdsResource, U32, U32, U1)
VALUE_OPCODE(GdsAtomicISub32, U32, GdsResource, U32, U32, U1)
VALUE_OPCODE(GdsAtomicSMin32, U32, GdsResource, U32, U32, U1)
VALUE_OPCODE(GdsAtomicUMin32, U32, GdsResource, U32, U32, U1)
VALUE_OPCODE(GdsAtomicSMax32, U32, GdsResource, U32, U32, U1)
VALUE_OPCODE(GdsAtomicUMax32, U32, GdsResource, U32, U32, U1)
VALUE_OPCODE(GdsAtomicAnd32, U32, GdsResource, U32, U32, U1)
VALUE_OPCODE(GdsAtomicOr32, U32, GdsResource, U32, U32, U1)
VALUE_OPCODE(GdsAtomicXor32, U32, GdsResource, U32, U32, U1)
VALUE_OPCODE(DataAppend, U32, LdsResource, U32, U1, U32, U32)
VALUE_OPCODE(DataConsume, U32, LdsResource, U32, U1, U32, U32)
VALUE_OPCODE(GdsDataAppend, U32, GdsResource, U32, U1, U32, U32)
VALUE_OPCODE(GdsDataConsume, U32, GdsResource, U32, U1, U32, U32)
VALUE_OPCODE(SwizzleU32, U32, U32, U32, U1)
VALUE_OPCODE(ImageQueryDimensions, U32x4, ImageResource, ImageAddress)
VALUE_OPCODE(ImageQueryLod, U32x4, ImageResource, SamplerResource, ImageAddress)
VALUE_OPCODE(ImageRead, U32x4, ImageResource, ImageAddress, U1)
VALUE_OPCODE(ImageWrite, Void, ImageResource, ImageAddress, U32x4, U1)
VALUE_OPCODE(ImageSampleRaw, U32x4, ImageResource, SamplerResource, ImageAddress)
VALUE_OPCODE(ImageGatherRaw, U32x4, ImageResource, SamplerResource, ImageAddress)
VALUE_OPCODE(ImageAtomicIAdd32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicUMin32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicUMax32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicAnd32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicOr32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicXor32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(GetAttribute, U32, U32, U32)
VALUE_OPCODE(SetAttribute, Void, U32x4, U1)
VALUE_OPCODE(ControlNop, Void)
VALUE_OPCODE(Waitcnt, Void)
VALUE_OPCODE(Barrier, Void)
VALUE_OPCODE(Sendmsg, Void)
VALUE_OPCODE(TtraceData, Void)
VALUE_OPCODE(InstPrefetch, Void)
VALUE_OPCODE(SelectU32, U32, U1, U32, U32)
@@ -1,6 +1,6 @@
#include "graphics/shader/recompiler/ir/BindingLayout.h"
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
#include "graphics/shader/recompiler/ir/ScalarProvenance.h"
#include "graphics/shader/recompiler/ir/ValueProgram.h"
#include <algorithm>
#include <array>
@@ -88,105 +88,21 @@ bool ImageBinding(const ImageResource& image, DescriptorBindingKind& result) {
}
}
bool CollectValue(const ScalarProvenance& provenance, uint32_t id, std::vector<uint8_t>& visited,
std::set<uint32_t>& registers) {
if (id <= ScalarProvenance::Unknown) {
return true;
}
if (id >= provenance.values.size()) {
return false;
}
if (visited[id] != 0) {
return true;
}
visited[id] = 1;
const auto& value = provenance.values[id];
if (value.op == ScalarValueOp::UserData) {
registers.insert(value.imm);
return true;
}
if (value.op == ScalarValueOp::Phi) {
for (const auto arg: value.phi_args) {
if (!CollectValue(provenance, arg, visited, registers)) {
return false;
}
}
return true;
}
for (uint32_t i = 0; i < ScalarValueArgCount(value.op); i++) {
if (!CollectValue(provenance, value.args[i], visited, registers)) {
return false;
}
}
return true;
}
bool CollectSource(const Program& program, uint32_t source, bool allow_unknown,
std::vector<uint8_t>& visited, std::set<uint32_t>& registers) {
if (allow_unknown && source == ScalarProvenance::Unknown) {
return true;
}
if (source < 2u || source - 2u >= program.provenance.descriptors.size()) {
return false;
}
const auto& descriptor = program.provenance.descriptors[source - 2u];
for (uint32_t i = 0; i < descriptor.dword_count; i++) {
if (!CollectValue(program.provenance, descriptor.dwords[i], visited, registers)) {
return false;
}
}
return true;
}
bool CollectUserData(const Program& program, std::vector<uint32_t>& result) {
std::set<uint32_t> registers;
std::vector<uint8_t> visited(program.provenance.values.size());
for (const auto& buffer: program.info.buffers) {
if (!CollectSource(program, buffer.source, false, visited, registers)) {
return false;
}
}
for (const auto& image: program.info.images) {
if (!CollectSource(program, image.source, false, visited, registers)) {
return false;
}
}
for (const auto& sampler: program.info.samplers) {
if (!CollectSource(program, sampler.source, false, visited, registers)) {
return false;
}
}
for (const auto& address: program.info.addresses) {
if (!CollectSource(program, address.source, true, visited, registers)) {
return false;
}
}
for (const auto& read: program.srt.reads) {
if (!CollectValue(program.provenance, read.value, visited, registers)) {
return false;
}
}
for (const auto read: program.srt.dynamic_reads) {
if (!CollectValue(program.provenance, read, visited, registers)) {
return false;
}
}
for (const auto source: program.srt.dynamic_sources) {
if (!CollectSource(program, source, true, visited, registers)) {
return false;
}
}
for (const auto& block: program.blocks) {
for (const auto& inst: block.instructions) {
if (inst.op == Opcode::SLoadDword && inst.memory.kind == ResourceKind::ScalarBuffer &&
!CollectValue(program.provenance, inst.scalar_value, visited, registers)) {
std::set<uint32_t> registers;
for (const auto* block: program.values->blocks) {
for (const auto& inst: *block) {
if (inst.GetOpcode() != ValueOpcode::GetUserData || !inst.HasUses()) {
continue;
}
if (inst.Arg(0).GetType() != Type::ScalarReg) {
return false;
}
for (uint32_t i = 0; i < inst.src_count; i++) {
if (!CollectValue(program.provenance, inst.scalar_sources[i], visited, registers)) {
return false;
}
const auto index = RegIndex(inst.Arg(0).ScalarRegister());
if (index >= NumScalarRegs) {
return false;
}
registers.insert(index);
}
}
result.assign(registers.begin(), registers.end());
@@ -200,11 +116,16 @@ void AddBinding(BindingLayout& layout, DescriptorBindingKind kind,
}
bool UsesGds(const Program& program) {
for (const auto& block: program.blocks) {
for (const auto& inst: block.instructions) {
if (inst.memory.kind == ResourceKind::Gds) {
for (const auto* block: program.values->blocks) {
for (const auto& inst: *block) {
if (inst.GetOpcode() == ValueOpcode::GetGdsResource && inst.HasUses()) {
return true;
}
for (size_t index = 0; index < inst.NumArgs(); index++) {
if (inst.Arg(index).GetType() == Type::GdsResource) {
return true;
}
}
}
}
return false;
@@ -232,13 +153,19 @@ bool AllocateBindings(Program& program, const BindingLayoutOptions& options, std
}
return false;
}
if (program.values == nullptr) {
if (error != nullptr) {
*error = "typed value program is not available";
}
return false;
}
BindingLayout next;
next.descriptor_set = options.descriptor_set;
next.push_constant_offset = options.push_constant_offset;
if (!CollectUserData(program, next.user_data_registers)) {
if (error != nullptr) {
*error = "shader plan contains an invalid scalar provenance reference";
*error = "typed shader contains an invalid user-data register";
}
return false;
}
@@ -294,7 +221,7 @@ bool AllocateBindings(Program& program, const BindingLayoutOptions& options, std
}
AddBinding(next, DescriptorBindingKind::AddressMemory, std::move(resources));
}
if (!program.srt.reads.empty()) {
if (!program.values->srt_reads.empty()) {
AddBinding(next, DescriptorBindingKind::FlattenedSrt);
}
@@ -0,0 +1,612 @@
#include "graphics/shader/recompiler/ir/passes/ConstantPropagation.h"
#include <algorithm>
#include <bit>
#include <cstdint>
namespace Libs::Graphics::ShaderRecompiler::IR {
namespace {
Value Arg(const Inst& inst, size_t index) {
return inst.Arg(index).Resolve();
}
bool IsImmediate(Value value, Type type) {
return value.IsImmediate() && value.GetType() == type;
}
void Replace(Inst& inst, Value value) {
inst.ReplaceUsesWith(value.Resolve());
}
template <typename Function>
bool FoldU32(Inst& inst, Function function) {
const auto lhs = Arg(inst, 0);
const auto rhs = Arg(inst, 1);
if (!IsImmediate(lhs, Type::U32) || !IsImmediate(rhs, Type::U32)) {
return false;
}
Replace(inst, Value(static_cast<uint32_t>(function(lhs.U32(), rhs.U32()))));
return true;
}
template <typename Function>
bool FoldU64(Inst& inst, Function function) {
const auto lhs = Arg(inst, 0);
const auto rhs = Arg(inst, 1);
if (!IsImmediate(lhs, Type::U64) || !IsImmediate(rhs, Type::U64)) {
return false;
}
Replace(inst, Value(static_cast<uint64_t>(function(lhs.U64(), rhs.U64()))));
return true;
}
template <typename Function>
bool FoldU64Shift(Inst& inst, Function function) {
const auto value = Arg(inst, 0);
const auto shift = Arg(inst, 1);
if (!IsImmediate(value, Type::U64) || !IsImmediate(shift, Type::U32)) {
return false;
}
Replace(inst, Value(static_cast<uint64_t>(function(value.U64(), shift.U32()))));
return true;
}
template <typename Function>
bool FoldU32Compare(Inst& inst, Function function) {
const auto lhs = Arg(inst, 0);
const auto rhs = Arg(inst, 1);
if (!IsImmediate(lhs, Type::U32) || !IsImmediate(rhs, Type::U32)) {
return false;
}
Replace(inst, Value(function(lhs.U32(), rhs.U32())));
return true;
}
template <typename Function>
bool FoldU64Compare(Inst& inst, Function function) {
const auto lhs = Arg(inst, 0);
const auto rhs = Arg(inst, 1);
if (!IsImmediate(lhs, Type::U64) || !IsImmediate(rhs, Type::U64)) {
return false;
}
Replace(inst, Value(function(lhs.U64(), rhs.U64())));
return true;
}
template <typename Function>
bool FoldLogical(Inst& inst, Function function) {
const auto lhs = Arg(inst, 0);
const auto rhs = Arg(inst, 1);
if (!IsImmediate(lhs, Type::U1) || !IsImmediate(rhs, Type::U1)) {
return false;
}
Replace(inst, Value(function(lhs.U1(), rhs.U1())));
return true;
}
bool ReplaceBinaryIdentity(Inst& inst, Type type, uint64_t identity) {
const auto lhs = Arg(inst, 0);
const auto rhs = Arg(inst, 1);
if (IsImmediate(lhs, type) &&
(type == Type::U32 ? lhs.U32() == identity : lhs.U64() == identity)) {
Replace(inst, rhs);
return true;
}
if (IsImmediate(rhs, type) &&
(type == Type::U32 ? rhs.U32() == identity : rhs.U64() == identity)) {
Replace(inst, lhs);
return true;
}
return false;
}
bool FoldSelect(Inst& inst) {
const auto condition = Arg(inst, 0);
const auto true_value = Arg(inst, 1);
const auto false_value = Arg(inst, 2);
if (IsImmediate(condition, Type::U1)) {
Replace(inst, condition.U1() ? true_value : false_value);
return true;
}
if (true_value == false_value) {
Replace(inst, true_value);
return true;
}
return false;
}
bool FoldPhi(Inst& inst) {
Value same;
for (size_t index = 0; index < inst.NumArgs(); index++) {
const auto value = Arg(inst, index);
if (value.TryInstruction() == &inst) {
continue;
}
if (same.IsEmpty()) {
same = value;
} else if (same != value) {
return false;
}
}
if (same.IsEmpty()) {
return false;
}
Replace(inst, same);
return true;
}
bool FoldBitCast(Inst& inst, ValueOpcode reverse) {
const auto value = Arg(inst, 0);
if (IsImmediate(value, Type::F32) && inst.GetOpcode() == ValueOpcode::BitCastU32F32) {
Replace(inst, Value(std::bit_cast<uint32_t>(value.F32Value())));
return true;
}
if (IsImmediate(value, Type::U32) && inst.GetOpcode() == ValueOpcode::BitCastF32U32) {
Replace(inst, Value::F32(std::bit_cast<float>(value.U32())));
return true;
}
if (IsImmediate(value, Type::F16) && inst.GetOpcode() == ValueOpcode::BitCastU16F16) {
Replace(inst, Value(value.F16Bits()));
return true;
}
if (IsImmediate(value, Type::U16) && inst.GetOpcode() == ValueOpcode::BitCastF16U16) {
Replace(inst, Value::F16(value.U16()));
return true;
}
if (auto* producer = value.TryInstruction();
producer != nullptr && producer->GetOpcode() == reverse) {
Replace(inst, producer->Arg(0));
return true;
}
return false;
}
bool FoldCompositeExtract(Inst& inst, ValueOpcode construct, size_t components) {
const auto composite = Arg(inst, 0);
const auto index = Arg(inst, 1);
if (!IsImmediate(index, Type::U32) || index.U32() >= components) {
return false;
}
const auto component = index.U32();
const auto* producer = composite.TryInstruction();
if (producer != nullptr && producer->GetOpcode() == construct) {
Replace(inst, producer->Arg(component));
return true;
}
if (component < 2u && producer != nullptr &&
producer->GetOpcode() == ValueOpcode::IAddCarry32) {
const auto lhs = producer->Arg(0).Resolve();
const auto rhs = producer->Arg(1).Resolve();
if (IsImmediate(lhs, Type::U32) && IsImmediate(rhs, Type::U32)) {
const auto sum = static_cast<uint64_t>(lhs.U32()) + rhs.U32();
Replace(inst, Value(component == 0u ? static_cast<uint32_t>(sum)
: static_cast<uint32_t>(sum >> 32u)));
return true;
}
}
if (component < 2u && producer != nullptr &&
producer->GetOpcode() == ValueOpcode::UnpackUint2x32) {
const auto packed = producer->Arg(0).Resolve();
if (IsImmediate(packed, Type::U64)) {
Replace(inst, Value(static_cast<uint32_t>(packed.U64() >> (component * 32u))));
return true;
}
}
return false;
}
void FoldInstruction(Inst& inst) {
switch (inst.GetOpcode()) {
case ValueOpcode::Phi: FoldPhi(inst); return;
case ValueOpcode::SelectU1:
case ValueOpcode::SelectU32:
case ValueOpcode::SelectF32: FoldSelect(inst); return;
case ValueOpcode::BitFieldInsert: {
const auto base = Arg(inst, 0);
const auto insert = Arg(inst, 1);
const auto offset = Arg(inst, 2);
const auto count = Arg(inst, 3);
if (IsImmediate(base, Type::U32) && IsImmediate(insert, Type::U32) &&
IsImmediate(offset, Type::U32) && IsImmediate(count, Type::U32) &&
offset.U32() <= 32u && count.U32() <= 32u - offset.U32()) {
if (count.U32() == 0u) {
Replace(inst, base);
return;
}
const auto mask = count.U32() == 32u
? UINT32_MAX
: ((uint32_t {1} << count.U32()) - 1u) << offset.U32();
Replace(inst,
Value((base.U32() & ~mask) | ((insert.U32() << offset.U32()) & mask)));
}
return;
}
case ValueOpcode::BitFieldUExtract:
case ValueOpcode::BitFieldSExtract: {
const auto value = Arg(inst, 0);
const auto offset = Arg(inst, 1);
const auto count = Arg(inst, 2);
if (!IsImmediate(value, Type::U32) || !IsImmediate(offset, Type::U32) ||
!IsImmediate(count, Type::U32) || offset.U32() > 32u ||
count.U32() > 32u - offset.U32()) {
return;
}
if (count.U32() == 0u) {
Replace(inst, Value(0u));
} else if (inst.GetOpcode() == ValueOpcode::BitFieldUExtract) {
const auto mask =
count.U32() == 32u ? UINT32_MAX : (uint32_t {1} << count.U32()) - 1u;
Replace(inst, Value((value.U32() >> offset.U32()) & mask));
} else {
const auto left = 32u - offset.U32() - count.U32();
const auto bits = value.U32() << left;
Replace(inst, Value(static_cast<uint32_t>(std::bit_cast<int32_t>(bits) >>
(left + offset.U32()))));
}
return;
}
case ValueOpcode::BitCastU16F16: FoldBitCast(inst, ValueOpcode::BitCastF16U16); return;
case ValueOpcode::BitCastF16U16: FoldBitCast(inst, ValueOpcode::BitCastU16F16); return;
case ValueOpcode::BitCastU32F32: FoldBitCast(inst, ValueOpcode::BitCastF32U32); return;
case ValueOpcode::BitCastF32U32: FoldBitCast(inst, ValueOpcode::BitCastU32F32); return;
case ValueOpcode::ConvertU16U32: {
const auto value = Arg(inst, 0);
if (IsImmediate(value, Type::U32)) {
Replace(inst, Value(static_cast<uint16_t>(value.U32())));
}
return;
}
case ValueOpcode::ConvertU32U16: {
const auto value = Arg(inst, 0);
if (IsImmediate(value, Type::U16)) {
Replace(inst, Value(static_cast<uint32_t>(value.U16())));
} else if (auto* producer = value.TryInstruction();
producer != nullptr && producer->GetOpcode() == ValueOpcode::ConvertU16U32) {
Replace(inst, producer->Arg(0));
}
return;
}
case ValueOpcode::ConvertU8U32: {
const auto value = Arg(inst, 0);
if (IsImmediate(value, Type::U32)) {
Replace(inst, Value(static_cast<uint8_t>(value.U32())));
}
return;
}
case ValueOpcode::ConvertU32U8: {
const auto value = Arg(inst, 0);
if (IsImmediate(value, Type::U8)) {
Replace(inst, Value(static_cast<uint32_t>(value.U8())));
} else if (auto* producer = value.TryInstruction();
producer != nullptr && producer->GetOpcode() == ValueOpcode::ConvertU8U32) {
Replace(inst, producer->Arg(0));
}
return;
}
case ValueOpcode::CompositeExtractU32x2:
FoldCompositeExtract(inst, ValueOpcode::CompositeConstructU32x2, 2);
return;
case ValueOpcode::CompositeExtractU32x4:
FoldCompositeExtract(inst, ValueOpcode::CompositeConstructU32x4, 4);
return;
case ValueOpcode::PackUint2x32: {
const auto value = Arg(inst, 0);
const auto* producer = value.TryInstruction();
if (producer != nullptr && producer->GetOpcode() == ValueOpcode::UnpackUint2x32) {
Replace(inst, producer->Arg(0));
} else if (producer != nullptr &&
producer->GetOpcode() == ValueOpcode::CompositeConstructU32x2) {
const auto low = producer->Arg(0).Resolve();
const auto high = producer->Arg(1).Resolve();
if (IsImmediate(low, Type::U32) && IsImmediate(high, Type::U32)) {
Replace(inst, Value(static_cast<uint64_t>(low.U32()) |
(static_cast<uint64_t>(high.U32()) << 32u)));
}
}
return;
}
case ValueOpcode::UnpackUint2x32: {
const auto value = Arg(inst, 0);
const auto* producer = value.TryInstruction();
if (producer != nullptr && producer->GetOpcode() == ValueOpcode::PackUint2x32) {
Replace(inst, producer->Arg(0));
}
return;
}
case ValueOpcode::IAdd32:
if (!FoldU32(inst, [](uint32_t a, uint32_t b) { return a + b; })) {
ReplaceBinaryIdentity(inst, Type::U32, 0u);
}
return;
case ValueOpcode::IAdd64:
if (!FoldU64(inst, [](uint64_t a, uint64_t b) { return a + b; })) {
ReplaceBinaryIdentity(inst, Type::U64, 0u);
}
return;
case ValueOpcode::ISub32: {
if (FoldU32(inst, [](uint32_t a, uint32_t b) { return a - b; })) {
return;
}
const auto rhs = Arg(inst, 1);
if (IsImmediate(rhs, Type::U32) && rhs.U32() == 0u) {
Replace(inst, Arg(inst, 0));
}
return;
}
case ValueOpcode::ISub64: {
if (FoldU64(inst, [](uint64_t a, uint64_t b) { return a - b; })) {
return;
}
const auto rhs = Arg(inst, 1);
if (IsImmediate(rhs, Type::U64) && rhs.U64() == 0u) {
Replace(inst, Arg(inst, 0));
}
return;
}
case ValueOpcode::IMul32:
if (!FoldU32(inst, [](uint32_t a, uint32_t b) { return a * b; })) {
ReplaceBinaryIdentity(inst, Type::U32, 1u);
}
return;
case ValueOpcode::IMul64:
if (!FoldU64(inst, [](uint64_t a, uint64_t b) { return a * b; })) {
ReplaceBinaryIdentity(inst, Type::U64, 1u);
}
return;
case ValueOpcode::UDiv32: {
const auto rhs = Arg(inst, 1);
if (IsImmediate(rhs, Type::U32) && rhs.U32() == 1u) {
Replace(inst, Arg(inst, 0));
} else if (IsImmediate(rhs, Type::U32) && rhs.U32() != 0u) {
FoldU32(inst, [](uint32_t a, uint32_t b) { return a / b; });
}
return;
}
case ValueOpcode::SMulHi:
FoldU32(inst, [](uint32_t a, uint32_t b) {
const auto product = static_cast<int64_t>(std::bit_cast<int32_t>(a)) *
static_cast<int64_t>(std::bit_cast<int32_t>(b));
return static_cast<uint32_t>(static_cast<uint64_t>(product) >> 32u);
});
return;
case ValueOpcode::UMulHi:
FoldU32(inst, [](uint32_t a, uint32_t b) {
return static_cast<uint32_t>((static_cast<uint64_t>(a) * b) >> 32u);
});
return;
case ValueOpcode::IAbs32: {
const auto value = Arg(inst, 0);
if (IsImmediate(value, Type::U32)) {
Replace(inst,
Value((value.U32() & 0x80000000u) != 0u ? 0u - value.U32() : value.U32()));
}
return;
}
case ValueOpcode::ShiftLeftLogical32:
if (!FoldU32(inst, [](uint32_t a, uint32_t b) { return a << (b & 31u); })) {
const auto shift = Arg(inst, 1);
if (IsImmediate(shift, Type::U32) && (shift.U32() & 31u) == 0u) {
Replace(inst, Arg(inst, 0));
}
}
return;
case ValueOpcode::ShiftRightLogical32:
if (!FoldU32(inst, [](uint32_t a, uint32_t b) { return a >> (b & 31u); })) {
const auto shift = Arg(inst, 1);
if (IsImmediate(shift, Type::U32) && (shift.U32() & 31u) == 0u) {
Replace(inst, Arg(inst, 0));
}
}
return;
case ValueOpcode::ShiftRightArithmetic32:
FoldU32(inst, [](uint32_t a, uint32_t b) {
return static_cast<uint32_t>(std::bit_cast<int32_t>(a) >> (b & 31u));
});
return;
case ValueOpcode::ShiftLeftLogical64:
if (!FoldU64Shift(inst, [](uint64_t a, uint32_t b) { return a << (b & 63u); })) {
const auto shift = Arg(inst, 1);
if (IsImmediate(shift, Type::U32) && (shift.U32() & 63u) == 0u) {
Replace(inst, Arg(inst, 0));
}
}
return;
case ValueOpcode::ShiftRightLogical64:
if (!FoldU64Shift(inst, [](uint64_t a, uint32_t b) { return a >> (b & 63u); })) {
const auto shift = Arg(inst, 1);
if (IsImmediate(shift, Type::U32) && (shift.U32() & 63u) == 0u) {
Replace(inst, Arg(inst, 0));
}
}
return;
case ValueOpcode::ShiftRightArithmetic64:
FoldU64Shift(inst, [](uint64_t a, uint32_t b) {
return static_cast<uint64_t>(std::bit_cast<int64_t>(a) >> (b & 63u));
});
return;
case ValueOpcode::BitwiseAnd32:
if (!FoldU32(inst, [](uint32_t a, uint32_t b) { return a & b; })) {
ReplaceBinaryIdentity(inst, Type::U32, 0xffffffffu);
}
return;
case ValueOpcode::BitwiseAnd64:
if (!FoldU64(inst, [](uint64_t a, uint64_t b) { return a & b; })) {
ReplaceBinaryIdentity(inst, Type::U64, UINT64_MAX);
}
return;
case ValueOpcode::BitwiseOr32:
case ValueOpcode::BitwiseXor32:
if (!FoldU32(inst, [opcode = inst.GetOpcode()](uint32_t a, uint32_t b) {
return opcode == ValueOpcode::BitwiseOr32 ? a | b : a ^ b;
})) {
ReplaceBinaryIdentity(inst, Type::U32, 0u);
}
return;
case ValueOpcode::BitwiseOr64:
if (!FoldU64(inst, [](uint64_t a, uint64_t b) { return a | b; })) {
ReplaceBinaryIdentity(inst, Type::U64, 0u);
}
return;
case ValueOpcode::BitwiseNot32: {
const auto value = Arg(inst, 0);
if (IsImmediate(value, Type::U32)) {
Replace(inst, Value(~value.U32()));
} else if (auto* producer = value.TryInstruction();
producer != nullptr && producer->GetOpcode() == ValueOpcode::BitwiseNot32) {
Replace(inst, producer->Arg(0));
}
return;
}
case ValueOpcode::BitCount32: {
const auto value = Arg(inst, 0);
if (IsImmediate(value, Type::U32)) {
Replace(inst, Value(static_cast<uint32_t>(std::popcount(value.U32()))));
}
return;
}
case ValueOpcode::BitCount64: {
const auto value = Arg(inst, 0);
if (IsImmediate(value, Type::U64)) {
Replace(inst, Value(static_cast<uint32_t>(std::popcount(value.U64()))));
}
return;
}
case ValueOpcode::SMin32:
case ValueOpcode::SMax32:
FoldU32(inst, [opcode = inst.GetOpcode()](uint32_t a, uint32_t b) {
const auto lhs = std::bit_cast<int32_t>(a);
const auto rhs = std::bit_cast<int32_t>(b);
return opcode == ValueOpcode::SMin32 ? (lhs < rhs ? a : b) : (lhs > rhs ? a : b);
});
return;
case ValueOpcode::UMin32:
FoldU32(inst, [](uint32_t a, uint32_t b) { return std::min(a, b); });
return;
case ValueOpcode::UMax32:
FoldU32(inst, [](uint32_t a, uint32_t b) { return std::max(a, b); });
return;
case ValueOpcode::IEqual32:
FoldU32Compare(inst, [](uint32_t a, uint32_t b) { return a == b; });
return;
case ValueOpcode::INotEqual32:
FoldU32Compare(inst, [](uint32_t a, uint32_t b) { return a != b; });
return;
case ValueOpcode::ULessThan32:
FoldU32Compare(inst, [](uint32_t a, uint32_t b) { return a < b; });
return;
case ValueOpcode::ULessThanEqual32:
FoldU32Compare(inst, [](uint32_t a, uint32_t b) { return a <= b; });
return;
case ValueOpcode::UGreaterThan32:
FoldU32Compare(inst, [](uint32_t a, uint32_t b) { return a > b; });
return;
case ValueOpcode::UGreaterThanEqual32:
FoldU32Compare(inst, [](uint32_t a, uint32_t b) { return a >= b; });
return;
case ValueOpcode::SLessThan32:
case ValueOpcode::SLessThanEqual32:
case ValueOpcode::SGreaterThan32:
case ValueOpcode::SGreaterThanEqual32:
FoldU32Compare(inst, [opcode = inst.GetOpcode()](uint32_t a, uint32_t b) {
const auto lhs = std::bit_cast<int32_t>(a);
const auto rhs = std::bit_cast<int32_t>(b);
switch (opcode) {
case ValueOpcode::SLessThan32: return lhs < rhs;
case ValueOpcode::SLessThanEqual32: return lhs <= rhs;
case ValueOpcode::SGreaterThan32: return lhs > rhs;
default: return lhs >= rhs;
}
});
return;
case ValueOpcode::IEqual64:
FoldU64Compare(inst, [](uint64_t a, uint64_t b) { return a == b; });
return;
case ValueOpcode::INotEqual64:
FoldU64Compare(inst, [](uint64_t a, uint64_t b) { return a != b; });
return;
case ValueOpcode::ULessThan64:
FoldU64Compare(inst, [](uint64_t a, uint64_t b) { return a < b; });
return;
case ValueOpcode::ULessThanEqual64:
FoldU64Compare(inst, [](uint64_t a, uint64_t b) { return a <= b; });
return;
case ValueOpcode::UGreaterThan64:
FoldU64Compare(inst, [](uint64_t a, uint64_t b) { return a > b; });
return;
case ValueOpcode::UGreaterThanEqual64:
FoldU64Compare(inst, [](uint64_t a, uint64_t b) { return a >= b; });
return;
case ValueOpcode::SLessThan64:
case ValueOpcode::SLessThanEqual64:
case ValueOpcode::SGreaterThan64:
case ValueOpcode::SGreaterThanEqual64:
FoldU64Compare(inst, [opcode = inst.GetOpcode()](uint64_t a, uint64_t b) {
const auto lhs = std::bit_cast<int64_t>(a);
const auto rhs = std::bit_cast<int64_t>(b);
switch (opcode) {
case ValueOpcode::SLessThan64: return lhs < rhs;
case ValueOpcode::SLessThanEqual64: return lhs <= rhs;
case ValueOpcode::SGreaterThan64: return lhs > rhs;
default: return lhs >= rhs;
}
});
return;
case ValueOpcode::LogicalAnd:
if (!FoldLogical(inst, [](bool a, bool b) { return a && b; })) {
const auto lhs = Arg(inst, 0);
const auto rhs = Arg(inst, 1);
if (IsImmediate(lhs, Type::U1)) {
Replace(inst, lhs.U1() ? rhs : lhs);
} else if (IsImmediate(rhs, Type::U1)) {
Replace(inst, rhs.U1() ? lhs : rhs);
}
}
return;
case ValueOpcode::LogicalOr:
if (!FoldLogical(inst, [](bool a, bool b) { return a || b; })) {
const auto lhs = Arg(inst, 0);
const auto rhs = Arg(inst, 1);
if (IsImmediate(lhs, Type::U1)) {
Replace(inst, lhs.U1() ? lhs : rhs);
} else if (IsImmediate(rhs, Type::U1)) {
Replace(inst, rhs.U1() ? rhs : lhs);
}
}
return;
case ValueOpcode::LogicalXor:
if (!FoldLogical(inst, [](bool a, bool b) { return a != b; })) {
const auto lhs = Arg(inst, 0);
const auto rhs = Arg(inst, 1);
if (IsImmediate(lhs, Type::U1) && !lhs.U1()) {
Replace(inst, rhs);
} else if (IsImmediate(rhs, Type::U1) && !rhs.U1()) {
Replace(inst, lhs);
}
}
return;
case ValueOpcode::LogicalNot: {
const auto value = Arg(inst, 0);
if (IsImmediate(value, Type::U1)) {
Replace(inst, Value(!value.U1()));
} else if (auto* producer = value.TryInstruction();
producer != nullptr && producer->GetOpcode() == ValueOpcode::LogicalNot) {
Replace(inst, producer->Arg(0));
}
return;
}
default: return;
}
}
} // namespace
void ConstantPropagationPass(const BlockList& blocks) {
for (auto* block: blocks) {
for (auto& inst: block->Instructions()) {
FoldInstruction(inst);
}
}
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -0,0 +1,9 @@
#pragma once
#include "graphics/shader/recompiler/ir/Block.h"
namespace Libs::Graphics::ShaderRecompiler::IR {
void ConstantPropagationPass(const BlockList& blocks);
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -0,0 +1,40 @@
#include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h"
namespace Libs::Graphics::ShaderRecompiler::IR {
void RemoveIdentities(const BlockList& blocks) {
for (auto* block: blocks) {
auto& instructions = block->Instructions();
for (auto inst = instructions.begin(); inst != instructions.end();) {
if (inst->GetOpcode() != ValueOpcode::Identity) {
inst++;
continue;
}
const auto replacement = inst->Arg(0);
inst->ReplaceUsesWith(replacement, false);
inst = instructions.erase(inst);
}
}
}
void EliminateDeadCode(const BlockList& blocks) {
bool changed;
do {
changed = false;
for (auto block = blocks.rbegin(); block != blocks.rend(); block++) {
auto& instructions = (*block)->Instructions();
auto inst = instructions.end();
while (inst != instructions.begin()) {
--inst;
if (inst->HasUses() || inst->MayHaveSideEffects()) {
continue;
}
inst->Invalidate();
inst = instructions.erase(inst);
changed = true;
}
}
} while (changed);
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -0,0 +1,10 @@
#pragma once
#include "graphics/shader/recompiler/ir/Block.h"
namespace Libs::Graphics::ShaderRecompiler::IR {
void RemoveIdentities(const BlockList& blocks);
void EliminateDeadCode(const BlockList& blocks);
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -0,0 +1,141 @@
#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 = &phi;
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);
}
} // namespace
ReadLaneStats EliminateReadLane(ValueProgram& 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
@@ -0,0 +1,13 @@
#pragma once
#include "graphics/shader/recompiler/ir/ValueProgram.h"
namespace Libs::Graphics::ShaderRecompiler::IR {
struct ReadLaneStats {
uint32_t rewritten_reads = 0;
};
[[nodiscard]] ReadLaneStats EliminateReadLane(ValueProgram& program, uint32_t wave_size);
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -1,6 +1,7 @@
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/shader/recompiler/ir/ValueProgram.h"
#include "graphics/shader/shaderBindings.h"
#include <algorithm>
@@ -81,11 +82,29 @@ bool DecodeBufferDescriptor(const DescriptorValue& descriptor, ShaderBufferResou
uint64_t AddressSpecialization(const AddressResource& resource,
const ResourceSnapshot::Address& snapshot) {
return resource.kind == ResourceKind::Flat || resource.source == ScalarProvenance::Unknown
return resource.kind == ResourceKind::Flat || resource.unbased
? snapshot.binding_base
: snapshot.guest_base - snapshot.binding_base;
}
bool IsImageOp(ValueOpcode op) {
switch (op) {
case ValueOpcode::ImageQueryDimensions:
case ValueOpcode::ImageQueryLod:
case ValueOpcode::ImageRead:
case ValueOpcode::ImageWrite:
case ValueOpcode::ImageSampleRaw:
case ValueOpcode::ImageGatherRaw:
case ValueOpcode::ImageAtomicIAdd32:
case ValueOpcode::ImageAtomicUMin32:
case ValueOpcode::ImageAtomicUMax32:
case ValueOpcode::ImageAtomicAnd32:
case ValueOpcode::ImageAtomicOr32:
case ValueOpcode::ImageAtomicXor32: return true;
default: return false;
}
}
} // namespace
bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& snapshot,
@@ -105,7 +124,8 @@ bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& sn
}
return false;
}
if (snapshot.flattened_srt.size() != program.srt.reads.size()) {
if (program.values == nullptr ||
snapshot.flattened_srt.size() != program.values->srt_reads.size()) {
if (error != nullptr) {
*error = "flattened SRT snapshot does not match the shader plan";
}
@@ -280,7 +300,7 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
requests.push_back({sampler.source, sampler.first_use_pc});
}
for (const auto& address: program.info.addresses) {
if (address.source != ScalarProvenance::Unknown) {
if (!address.unbased) {
requests.push_back({address.source, address.first_use_pc});
}
}
@@ -311,7 +331,7 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
next.samplers.assign(cursor, cursor + program.info.samplers.size());
cursor += program.info.samplers.size();
for (const auto& address: program.info.addresses) {
if (address.source != ScalarProvenance::Unknown) {
if (!address.unbased) {
const auto value = *cursor++;
auto base = (static_cast<uint64_t>(value.dwords[0]) |
static_cast<uint64_t>(value.dwords[1]) << 32u) &
@@ -430,38 +450,42 @@ bool SpecializeResources(Program& program, const ResourceSnapshot& snapshot, std
}
}
}
struct ImagePatch {
std::reference_wrapper<Instruction> inst;
ResourceKind kind;
Decoder::ImageDimension dimension;
bool cube;
};
std::vector<ImagePatch> patches;
for (auto& block: program.blocks) {
for (auto& inst: block.instructions) {
if (inst.memory.kind != ResourceKind::Image &&
inst.memory.kind != ResourceKind::ImageUint &&
inst.memory.kind != ResourceKind::StorageImage &&
inst.memory.kind != ResourceKind::StorageImageUint) {
if (program.values == nullptr) {
if (error != nullptr) {
*error = "typed SSA is not ready";
}
return false;
}
auto memory_info = program.values->memory_info;
for (const auto* block: program.values->blocks) {
for (const auto& inst: *block) {
if (!IsImageOp(inst.GetOpcode())) {
continue;
}
if (inst.memory.resource >= next.images.size()) {
const auto index = inst.Flags<MemoryFlags>().index;
if (index >= memory_info.size()) {
if (error != nullptr) {
*error = fmt::format("image instruction at pc 0x{:08x} has invalid resource {}",
inst.pc, inst.memory.resource);
*error = fmt::format("typed image instruction has invalid memory metadata {}",
index);
}
return false;
}
const auto& image = next.images[inst.memory.resource];
patches.push_back({std::ref(inst), image.kind, image.dimension, image.cube});
auto& memory = memory_info[index];
if (memory.resource >= next.images.size()) {
if (error != nullptr) {
*error = fmt::format("typed image instruction has invalid resource {}",
memory.resource);
}
return false;
}
const auto& image = next.images[memory.resource];
memory.kind = image.kind;
memory.image_dimension = image.dimension;
memory.image_cube = image.cube;
}
}
program.info = std::move(next);
for (const auto& patch: patches) {
patch.inst.get().memory.kind = patch.kind;
patch.inst.get().memory.image_dimension = patch.dimension;
patch.inst.get().memory.image_cube = patch.cube;
}
program.info = std::move(next);
program.values->memory_info = std::move(memory_info);
return true;
}
@@ -1,7 +1,7 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_RESOURCEMATERIALIZATION_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_RESOURCEMATERIALIZATION_H_
#include "graphics/shader/recompiler/ir/SrtWalker.h"
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
namespace Libs::Graphics::ShaderRecompiler::IR {
@@ -0,0 +1,677 @@
#include "graphics/shader/recompiler/ir/passes/ResourceTracking.h"
#include "graphics/shader/recompiler/ir/ValueProgram.h"
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
#include <algorithm>
#include <fmt/format.h>
#include <unordered_set>
#include <utility>
namespace Libs::Graphics::ShaderRecompiler::IR {
namespace {
constexpr uint32_t SamplerBorderClampMask = (1u << 2u) | (1u << 5u) | (1u << 8u);
const char* StageName(ShaderType stage) {
switch (stage) {
case ShaderType::Vertex: return "vertex";
case ShaderType::Pixel: return "pixel";
case ShaderType::Fetch: return "fetch";
case ShaderType::Compute: return "compute";
default: return "unknown";
}
}
bool IsBufferAtomic(ValueOpcode op) {
switch (op) {
case ValueOpcode::BufferAtomicSwap32:
case ValueOpcode::BufferAtomicIAdd32:
case ValueOpcode::BufferAtomicISub32:
case ValueOpcode::BufferAtomicSMin32:
case ValueOpcode::BufferAtomicUMin32:
case ValueOpcode::BufferAtomicSMax32:
case ValueOpcode::BufferAtomicUMax32:
case ValueOpcode::BufferAtomicAnd32:
case ValueOpcode::BufferAtomicOr32:
case ValueOpcode::BufferAtomicXor32:
case ValueOpcode::BufferAtomicFMin32:
case ValueOpcode::BufferAtomicFMax32: return true;
default: return false;
}
}
bool IsBufferStore(ValueOpcode op) {
switch (op) {
case ValueOpcode::StoreBufferU8:
case ValueOpcode::StoreBufferU16:
case ValueOpcode::StoreBufferU32: return true;
default: return IsBufferAtomic(op);
}
}
bool IsBuffer(ValueOpcode op) {
switch (op) {
case ValueOpcode::ReadConstBuffer:
case ValueOpcode::LoadBufferU8:
case ValueOpcode::LoadBufferU16:
case ValueOpcode::LoadBufferU32:
case ValueOpcode::StoreBufferU8:
case ValueOpcode::StoreBufferU16:
case ValueOpcode::StoreBufferU32: return true;
default: return IsBufferAtomic(op);
}
}
bool IsAddressStore(ValueOpcode op) {
switch (op) {
case ValueOpcode::StoreAddressU8:
case ValueOpcode::StoreAddressU16:
case ValueOpcode::StoreAddressU32: return true;
default: return false;
}
}
bool IsAddress(ValueOpcode op) {
switch (op) {
case ValueOpcode::LoadAddressU8:
case ValueOpcode::LoadAddressU16:
case ValueOpcode::LoadAddressU32:
case ValueOpcode::StoreAddressU8:
case ValueOpcode::StoreAddressU16:
case ValueOpcode::StoreAddressU32: return true;
default: return false;
}
}
bool IsImageAtomic(ValueOpcode op) {
switch (op) {
case ValueOpcode::ImageAtomicIAdd32:
case ValueOpcode::ImageAtomicUMin32:
case ValueOpcode::ImageAtomicUMax32:
case ValueOpcode::ImageAtomicAnd32:
case ValueOpcode::ImageAtomicOr32:
case ValueOpcode::ImageAtomicXor32: return true;
default: return false;
}
}
bool IsImageStore(ValueOpcode op) {
return op == ValueOpcode::ImageWrite || IsImageAtomic(op);
}
bool IsImage(ValueOpcode op) {
switch (op) {
case ValueOpcode::ImageQueryDimensions:
case ValueOpcode::ImageQueryLod:
case ValueOpcode::ImageRead:
case ValueOpcode::ImageWrite:
case ValueOpcode::ImageSampleRaw:
case ValueOpcode::ImageGatherRaw: return true;
default: return IsImageAtomic(op);
}
}
bool NeedsSampler(ValueOpcode op) {
return op == ValueOpcode::ImageQueryLod || op == ValueOpcode::ImageSampleRaw ||
op == ValueOpcode::ImageGatherRaw;
}
uint32_t ByteExtent(const MemoryInfo& memory) {
const auto bytes = std::max((memory.data_bits + 7u) / 8u, 1u);
const auto count = std::max(memory.data_dwords, 1u);
const auto end = static_cast<uint64_t>(memory.offset) + static_cast<uint64_t>(bytes) * count;
return end > UINT32_MAX ? UINT32_MAX : static_cast<uint32_t>(end);
}
ImageMipMode MipMode(const MemoryInfo& memory) {
const bool storage =
memory.kind == ResourceKind::StorageImage || memory.kind == ResourceKind::StorageImageUint;
return storage && memory.image_has_mip ? ImageMipMode::DynamicStorage : ImageMipMode::None;
}
class Tracker {
public:
Tracker(Program& program, ValueProgram& values)
: m_program(program), m_values(values), m_info(program.info) {
m_info.buffers.clear();
m_info.addresses.clear();
m_info.images.clear();
m_info.samplers.clear();
m_info.sampled_pairs.clear();
}
bool Run(std::string* error) {
if (m_program.resource_tracking_complete) {
return Fail(0, error, "resources already tracked");
}
if (!m_program.srt_plan_complete) {
return Fail(0, error, "SRT plan is not ready");
}
for (auto* block: m_values.blocks) {
for (auto& inst: *block) {
if (!Collect(inst, error)) {
return false;
}
}
}
LinkImageAliases();
for (const auto& patch: m_handle_patches) {
patch.handle->SetFlags<uint32_t>(patch.resource);
}
for (const auto& patch: m_memory_patches) {
auto& memory = m_values.memory_info[patch.index];
memory.resource = patch.resource;
if (patch.has_sampler) {
memory.sampler = patch.sampler;
}
}
m_values.descriptor_sources = std::move(m_sources);
m_program.info = std::move(m_info);
m_program.resource_tracking_complete = true;
return true;
}
private:
struct HandlePatch {
Inst* handle = nullptr;
uint32_t resource = 0;
};
struct MemoryPatch {
uint32_t index = 0;
uint32_t resource = 0;
uint32_t sampler = 0;
bool has_sampler = false;
};
bool Fail(uint32_t pc, std::string* error, const std::string& reason) const {
if (error != nullptr) {
*error = fmt::format("shader resource tracking: hash=0x{:016x} stage={} pc=0x{:08x} {}",
m_program.shader_hash, StageName(m_program.stage), pc, reason);
}
return false;
}
struct AddressPart {
Value value;
bool rooted = false;
};
AddressPart FindAddressPart(Value value, std::unordered_set<const Inst*>& visiting) const {
value = value.Resolve();
if (value.IsImmediate()) {
return {value, false};
}
const auto* inst = value.TryInstruction();
if (inst == nullptr || !visiting.insert(inst).second) {
return {};
}
const auto finish = [&](AddressPart part) {
visiting.erase(inst);
return part;
};
std::string reason;
if (ValidateRuntimeValue(m_program, value, reason)) {
return finish({value, true});
}
const auto merge = [&](AddressPart left, AddressPart right, bool require_both) {
if (left.value.IsEmpty() || right.value.IsEmpty()) {
return require_both ? AddressPart {} : (left.value.IsEmpty() ? right : left);
}
return EquivalentValue(m_values, left.value, right.value)
? AddressPart {left.value, left.rooted || right.rooted}
: AddressPart {};
};
switch (inst->GetOpcode()) {
case ValueOpcode::SelectU32:
return finish(merge(FindAddressPart(inst->Arg(1), visiting),
FindAddressPart(inst->Arg(2), visiting), true));
case ValueOpcode::Phi: {
const auto invariant = ResolveInvariantPhi(m_values, value);
return finish(invariant.IsEmpty() ? AddressPart {}
: FindAddressPart(invariant, visiting));
}
case ValueOpcode::IAdd32:
case ValueOpcode::ISub32: {
const auto left = FindAddressPart(inst->Arg(0), visiting);
const auto right = FindAddressPart(inst->Arg(1), visiting);
if (left.rooted == right.rooted ||
(inst->GetOpcode() == ValueOpcode::ISub32 && right.rooted)) {
return finish({});
}
return finish(left.rooted ? left : right);
}
case ValueOpcode::CompositeExtractU32x2: {
const auto* source = inst->Arg(0).ResolveInstruction();
if (source == nullptr || source->GetOpcode() != ValueOpcode::IAddCarry32) {
return finish({});
}
const auto left = FindAddressPart(source->Arg(0), visiting);
const auto right = FindAddressPart(source->Arg(1), visiting);
if (left.rooted == right.rooted) {
return finish({});
}
return finish(left.rooted ? left : right);
}
default: return finish({});
}
}
bool MakeFlatAddressSource(const Inst& handle, DescriptorSource& descriptor) const {
std::unordered_set<const Inst*> visiting;
auto low = FindAddressPart(handle.Arg(0), visiting);
visiting.clear();
auto high = FindAddressPart(handle.Arg(1), visiting);
if ((!low.rooted && !high.rooted) || low.value.IsEmpty() || high.value.IsEmpty()) {
return false;
}
descriptor.dword_count = 2;
descriptor.dwords[0] = low.value;
descriptor.dwords[1] = high.value;
return true;
}
bool MakeSource(const Inst& handle, uint32_t width, bool sampler, DescriptorSource& descriptor,
uint32_t pc, std::string* error) const {
if (handle.NumArgs() != width) {
return Fail(pc, error,
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] = handle.Arg(i).Resolve();
}
const auto dword0 = descriptor.dwords[0].Resolve();
if (sampler && dword0.IsImmediate() && dword0.GetType() == Type::U32 &&
(dword0.U32() & SamplerBorderClampMask) == 0) {
// Border color and its table index are unused unless a clamp axis selects border mode.
descriptor.dwords[3] = Value(0u);
}
return true;
}
bool ValidateSource(const DescriptorSource& descriptor, std::string& reason,
uint32_t& bad_dword) const {
for (uint32_t i = 0; i < descriptor.dword_count; i++) {
bad_dword = i;
if (descriptor.dwords[i].Resolve().GetType() != Type::U32) {
reason = "is not U32";
return false;
}
if (!ValidateRuntimeValue(m_program, descriptor.dwords[i], reason)) {
return false;
}
}
return true;
}
uint32_t InternSource(const DescriptorSource& descriptor) {
for (uint32_t candidate = 0; candidate < m_sources.size(); candidate++) {
const auto& current = m_sources[candidate];
if (current.dword_count != descriptor.dword_count) {
continue;
}
bool same = true;
for (uint32_t i = 0; i < descriptor.dword_count; i++) {
same = same && EquivalentValue(m_values, current.dwords[i], descriptor.dwords[i]);
}
if (same) {
return candidate;
}
}
m_sources.push_back(descriptor);
return static_cast<uint32_t>(m_sources.size() - 1);
}
bool GetHandle(Value value, ValueOpcode expected, uint32_t width, uint32_t pc, Inst*& handle,
uint32_t& source, std::string* error, bool sampler = false) {
handle = value.Resolve().TryInstruction();
if (handle == nullptr || handle->GetOpcode() != expected) {
return Fail(pc, error,
fmt::format("memory operation requires {}", ValueOpcodeName(expected)));
}
DescriptorSource descriptor;
if (!MakeSource(*handle, width, sampler, descriptor, pc, error)) {
return false;
}
std::string reason;
uint32_t bad_dword = 0;
if (!ValidateSource(descriptor, reason, bad_dword)) {
return Fail(
pc, error,
fmt::format("{} dword {} {}", ValueOpcodeName(expected), bad_dword, reason));
}
source = InternSource(descriptor);
return true;
}
bool GetAddressHandle(const Inst& memory_inst, Value value, uint32_t pc, Inst*& handle,
uint32_t& source, bool& unbased, std::string* error) {
handle = value.Resolve().TryInstruction();
if (handle == nullptr || handle->GetOpcode() != ValueOpcode::GetAddressResource) {
return Fail(pc, error, "address operation requires GetAddressResource");
}
if (handle->NumArgs() != 2) {
return Fail(pc, error, "GetAddressResource must have two address dwords");
}
DescriptorSource descriptor;
const auto& memory = m_values.memory_info[memory_inst.Flags<MemoryFlags>().index];
if (memory.address_is_full) {
if (memory.kind != ResourceKind::Flat || !MakeFlatAddressSource(*handle, descriptor)) {
unbased = true;
source = UINT32_MAX;
return true;
}
} else if (!MakeSource(*handle, 2, false, descriptor, pc, error)) {
return false;
}
std::string reason;
uint32_t bad_dword = 0;
if (!ValidateSource(descriptor, reason, bad_dword)) {
if (memory.address_is_full) {
unbased = true;
source = UINT32_MAX;
return true;
}
return Fail(
pc, error,
fmt::format("scalar memory base dword {} is unresolved: {}", bad_dword, reason));
}
unbased = false;
source = InternSource(descriptor);
return true;
}
uint32_t AddBuffer(uint32_t source, const MemoryInfo& memory, ValueOpcode op, uint32_t pc) {
for (uint32_t i = 0; i < m_info.buffers.size(); i++) {
if (m_info.buffers[i].source == source) {
Merge(m_info.buffers[i], memory, op, pc);
return i;
}
}
if (m_info.buffers.size() >= ShaderInfo::MaxBuffers) {
return UINT32_MAX;
}
BufferResource resource;
resource.source = source;
resource.first_use_pc = pc;
Merge(resource, memory, op, pc);
m_info.buffers.push_back(resource);
return static_cast<uint32_t>(m_info.buffers.size() - 1);
}
static void Merge(BufferResource& resource, const MemoryInfo& memory, ValueOpcode op,
uint32_t pc) {
const bool atomic = IsBufferAtomic(op);
const bool write = IsBufferStore(op);
resource.first_use_pc = std::min(resource.first_use_pc, pc);
resource.max_byte_extent = std::max(resource.max_byte_extent, ByteExtent(memory));
resource.read = resource.read || !write || atomic;
resource.written = resource.written || write;
resource.atomic = resource.atomic || atomic;
resource.formatted = resource.formatted || memory.formatted;
resource.scalar = resource.scalar || op == ValueOpcode::ReadConstBuffer ||
memory.kind == ResourceKind::ScalarBuffer;
}
uint32_t AddImage(uint32_t source, const MemoryInfo& memory, ValueOpcode op, uint32_t pc) {
const auto mip = MipMode(memory);
const bool depth = (memory.image_sample_flags & Decoder::ImageSampleFlagCompare) != 0;
for (uint32_t i = 0; i < m_info.images.size(); i++) {
auto& image = m_info.images[i];
if (image.source == source && image.kind == memory.kind &&
image.dimension == memory.image_dimension && image.mip_mode == mip &&
image.depth_compare == depth) {
Merge(image, op, pc);
return i;
}
}
if (m_info.images.size() >= ShaderInfo::MaxImages) {
return UINT32_MAX;
}
ImageResource image;
image.source = source;
image.first_use_pc = pc;
image.kind = memory.kind;
image.dimension = memory.image_dimension;
image.mip_mode = mip;
image.depth_compare = depth;
Merge(image, op, pc);
m_info.images.push_back(image);
return static_cast<uint32_t>(m_info.images.size() - 1);
}
static void Merge(ImageResource& image, ValueOpcode op, uint32_t pc) {
const bool atomic = IsImageAtomic(op);
const bool write = IsImageStore(op);
image.first_use_pc = std::min(image.first_use_pc, pc);
image.read = image.read || !write || atomic;
image.written = image.written || write;
image.atomic = image.atomic || atomic;
}
uint32_t AddSampler(uint32_t source, uint32_t pc) {
for (uint32_t i = 0; i < m_info.samplers.size(); i++) {
if (m_info.samplers[i].source == source) {
m_info.samplers[i].first_use_pc = std::min(m_info.samplers[i].first_use_pc, pc);
return i;
}
}
if (m_info.samplers.size() >= ShaderInfo::MaxSamplers) {
return UINT32_MAX;
}
m_info.samplers.push_back({source, pc});
return static_cast<uint32_t>(m_info.samplers.size() - 1);
}
uint32_t AddAddress(uint32_t source, bool unbased, const MemoryInfo& memory, ValueOpcode op,
uint32_t pc) {
auto immediate = static_cast<int32_t>(memory.offset);
if (memory.kind == ResourceKind::ScalarBuffer) {
immediate = static_cast<int32_t>(static_cast<uint32_t>(immediate) & ~3u);
}
const auto min_offset = unbased ? 0 : std::min(immediate, 0);
for (uint32_t i = 0; i < m_info.addresses.size(); i++) {
auto& address = m_info.addresses[i];
if (address.source == source && address.unbased == unbased &&
address.kind == memory.kind) {
address.first_use_pc = std::min(address.first_use_pc, pc);
address.min_offset = std::min(address.min_offset, min_offset);
address.read = address.read || !IsAddressStore(op);
address.written = address.written || IsAddressStore(op);
return i;
}
}
if (m_info.addresses.size() >= ShaderInfo::MaxAddresses) {
return UINT32_MAX;
}
AddressResource address;
address.source = source;
address.first_use_pc = pc;
address.kind = memory.kind;
address.min_offset = min_offset;
address.unbased = unbased;
address.read = !IsAddressStore(op);
address.written = IsAddressStore(op);
m_info.addresses.push_back(address);
return static_cast<uint32_t>(m_info.addresses.size() - 1);
}
bool AddSampledPair(uint32_t image, uint32_t sampler, uint32_t pc, std::string* error) {
for (auto& pair: m_info.sampled_pairs) {
if (pair.image == image && pair.sampler == sampler) {
pair.first_use_pc = std::min(pair.first_use_pc, pc);
return true;
}
}
if (m_info.sampled_pairs.size() >= ShaderInfo::MaxSampledPairs) {
return Fail(pc, error, "sampled image/sampler pair limit exceeded");
}
m_info.sampled_pairs.push_back({image, sampler, pc});
return true;
}
bool AddHandlePatch(Inst* handle, uint32_t resource, uint32_t pc, std::string* error) {
for (const auto& patch: m_handle_patches) {
if (patch.handle == handle) {
return patch.resource == resource ||
Fail(pc, error,
fmt::format("{} is reused with incompatible resource classes",
ValueOpcodeName(handle->GetOpcode())));
}
}
m_handle_patches.push_back({handle, resource});
return true;
}
bool AddMemoryPatch(uint32_t index, uint32_t resource, uint32_t sampler, bool has_sampler,
uint32_t pc, std::string* error) {
for (auto& patch: m_memory_patches) {
if (patch.index != index) {
continue;
}
if (patch.resource != resource ||
(has_sampler && patch.has_sampler && patch.sampler != sampler)) {
return Fail(pc, error, "memory metadata is reused with incompatible resources");
}
if (has_sampler) {
patch.sampler = sampler;
patch.has_sampler = true;
}
return true;
}
m_memory_patches.push_back({index, resource, sampler, has_sampler});
return true;
}
bool Collect(Inst& inst, std::string* error) {
const auto op = inst.GetOpcode();
if (!IsBuffer(op) && !IsAddress(op) && !IsImage(op)) {
return true;
}
const auto flags = inst.Flags<MemoryFlags>();
if (flags.index >= m_values.memory_info.size()) {
return Fail(flags.pc, error,
fmt::format("memory metadata index {} is out of range", flags.index));
}
if (inst.NumArgs() == 0) {
return Fail(flags.pc, error, "memory operation has no resource handle");
}
const auto& memory = m_values.memory_info[flags.index];
if (memory.planning_only) {
return true;
}
Inst* handle = nullptr;
uint32_t source = 0;
uint32_t resource = 0;
if (IsBuffer(op)) {
if (!GetHandle(inst.Arg(0), ValueOpcode::GetBufferResource, 4, flags.pc, handle, source,
error)) {
return false;
}
resource = AddBuffer(source, memory, op, flags.pc);
if (resource == UINT32_MAX) {
return Fail(flags.pc, error, "buffer resource limit exceeded");
}
return AddHandlePatch(handle, resource, flags.pc, error) &&
AddMemoryPatch(flags.index, resource, 0, false, flags.pc, error);
}
if (IsAddress(op)) {
bool unbased = false;
if (!GetAddressHandle(inst, inst.Arg(0), flags.pc, handle, source, unbased, error)) {
return false;
}
resource = AddAddress(source, unbased, memory, op, flags.pc);
if (resource == UINT32_MAX) {
return Fail(flags.pc, error, "address resource limit exceeded");
}
return AddHandlePatch(handle, resource, flags.pc, error) &&
AddMemoryPatch(flags.index, resource, 0, false, flags.pc, error);
}
if (!GetHandle(inst.Arg(0), ValueOpcode::GetImageResource, 8, flags.pc, handle, source,
error)) {
return false;
}
resource = AddImage(source, memory, op, flags.pc);
if (resource == UINT32_MAX) {
return Fail(flags.pc, error, "image resource limit exceeded");
}
if (!AddHandlePatch(handle, resource, flags.pc, error)) {
return false;
}
uint32_t sampler = 0;
if (NeedsSampler(op)) {
if (inst.NumArgs() < 2) {
return Fail(flags.pc, error, "sampled image operation has no sampler handle");
}
Inst* sampler_handle = nullptr;
uint32_t sampler_source = 0;
if (!GetHandle(inst.Arg(1), ValueOpcode::GetSamplerResource, 4, flags.pc,
sampler_handle, sampler_source, error, true)) {
return false;
}
sampler = AddSampler(sampler_source, flags.pc);
if (sampler == UINT32_MAX) {
return Fail(flags.pc, error, "sampler resource limit exceeded");
}
if (!AddHandlePatch(sampler_handle, sampler, flags.pc, error) ||
!AddSampledPair(resource, sampler, flags.pc, error)) {
return false;
}
}
return AddMemoryPatch(flags.index, resource, sampler, NeedsSampler(op), flags.pc, error);
}
const DescriptorSource* Source(uint32_t source) const {
return source < m_sources.size() ? &m_sources[source] : nullptr;
}
void LinkImageAliases() {
for (auto& buffer: m_info.buffers) {
const auto* buffer_source = Source(buffer.source);
if (buffer_source == nullptr || buffer_source->dword_count != 4) {
continue;
}
for (uint32_t image = 0; image < m_info.images.size(); image++) {
const auto* image_source = Source(m_info.images[image].source);
if (image_source == nullptr || image_source->dword_count != 8) {
continue;
}
bool alias = true;
for (uint32_t dword = 0; dword < 4; dword++) {
alias = alias && EquivalentValue(m_values, buffer_source->dwords[dword],
image_source->dwords[dword]);
}
if (alias) {
buffer.image_alias = image;
break;
}
}
}
}
Program& m_program;
ValueProgram& m_values;
ShaderInfo m_info;
std::vector<DescriptorSource> m_sources;
std::vector<HandlePatch> m_handle_patches;
std::vector<MemoryPatch> m_memory_patches;
};
} // namespace
bool TrackResources(Program& program, std::string* error) {
if (program.values == nullptr) {
if (error != nullptr) {
*error = "shader resource tracking requires typed IR";
}
return false;
}
return Tracker(program, *program.values).Run(error);
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -5,8 +5,8 @@
namespace Libs::Graphics::ShaderRecompiler::IR {
// Collects immutable resource topology, then replaces descriptor operands with dense indices.
// On failure the program is left unpatched.
// Collects immutable resource topology from typed SSA handles, interns their resolved dwords in
// descriptor_sources, then writes dense indices to handle flags and MemoryInfo.
bool TrackResources(Program& program, std::string* error);
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -0,0 +1,318 @@
#include "graphics/shader/recompiler/ir/passes/ShaderInfoCollection.h"
#include "graphics/shader/recompiler/ir/ValueProgram.h"
#include <algorithm>
#include <fmt/format.h>
namespace Libs::Graphics::ShaderRecompiler::IR {
namespace {
void AddInput(ShaderInfo& info, StageInputKind kind, uint32_t location, uint32_t components,
std::string name) {
const auto input = std::find_if(info.inputs.begin(), info.inputs.end(), [=](const auto& value) {
return value.kind == kind && value.location == location;
});
if (input == info.inputs.end()) {
info.inputs.push_back({kind, location, components, std::move(name)});
} else {
input->component_count = std::max(input->component_count, components);
}
}
bool HasOutput(const ShaderInfo& info, StageOutputKind kind, uint32_t index) {
return std::any_of(info.outputs.begin(), info.outputs.end(), [=](const auto& output) {
return output.kind == kind && output.index == index;
});
}
void AddOutput(ShaderInfo& info, StageOutputKind kind, uint32_t index, uint32_t location,
std::string name) {
if (!HasOutput(info, kind, index)) {
info.outputs.push_back({kind, index, location, std::move(name)});
}
}
bool ValidateOptions(const Program& program, const ShaderInfoOptions& options, std::string* error) {
auto Fail = [&](const char* message) {
if (error != nullptr) {
*error = message;
}
return false;
};
switch (program.stage) {
case ShaderType::Vertex:
if (options.vertex == nullptr) {
return Fail("vertex shader info requires vertex metadata");
}
if (options.vertex->resources_num < 0 ||
options.vertex->resources_num > ShaderVertexInputInfo::RES_MAX) {
return Fail("vertex resource count is out of range");
}
return true;
case ShaderType::Pixel:
if (options.pixel == nullptr) {
return Fail("pixel shader info requires pixel metadata");
}
return options.pixel->input_num <= std::size(options.pixel->interpolator_settings) ||
Fail("pixel input count is out of range");
case ShaderType::Compute:
if (options.compute == nullptr) {
return Fail("compute shader info requires compute metadata");
}
return (options.compute->thread_ids_num >= 0 && options.compute->thread_ids_num <= 3) ||
Fail("compute thread ID count is out of range");
default: return Fail("unsupported shader stage for info collection");
}
}
bool ValidateValueReferences(const Program& program, const ShaderInfoOptions& options,
std::string* error) {
const auto Fail = [&](const char* message) {
if (error != nullptr) {
*error = message;
}
return false;
};
for (const auto* block: program.values->blocks) {
for (const auto& inst: *block) {
switch (inst.GetOpcode()) {
case ValueOpcode::GetAttribute: {
if (!inst.Arg(0).IsImmediate() || inst.Arg(0).GetType() != Type::U32 ||
!inst.Arg(1).IsImmediate() || inst.Arg(1).GetType() != Type::U32) {
return Fail("typed attribute reference is not constant");
}
if (program.stage == ShaderType::Vertex &&
(inst.Arg(1).U32() >= 4u || options.vertex == nullptr ||
inst.Arg(0).U32() >=
static_cast<uint32_t>(options.vertex->resources_num))) {
return Fail("vertex input reference is out of range");
}
break;
}
case ValueOpcode::GetBuiltin: {
if (!inst.Arg(0).IsImmediate() || inst.Arg(0).GetType() != Type::U32 ||
!inst.Arg(1).IsImmediate() || inst.Arg(1).GetType() != Type::U32) {
return Fail("typed builtin reference is not constant");
}
const auto kind = static_cast<StageInputKind>(inst.Arg(0).U32());
const auto component = inst.Arg(1).U32();
switch (kind) {
case StageInputKind::VertexIndex:
case StageInputKind::InstanceIndex:
case StageInputKind::FrontFacing:
case StageInputKind::LocalInvocationIndex:
if (component != 0u) {
return Fail("typed scalar builtin component is out of range");
}
break;
case StageInputKind::FragCoord:
if (component >= 4u) {
return Fail("typed fragment-coordinate component is out of range");
}
break;
case StageInputKind::WorkgroupId:
case StageInputKind::LocalInvocationId:
case StageInputKind::GlobalInvocationId:
if (component >= 3u) {
return Fail("typed invocation builtin component is out of range");
}
break;
case StageInputKind::Parameter:
default: return Fail("typed builtin kind is invalid");
}
break;
}
case ValueOpcode::SetAttribute:
if (inst.Flags<ExportFlags>().index >= program.values->export_info.size()) {
return Fail("typed export metadata index is out of range");
}
break;
default: break;
}
}
}
return true;
}
void CollectVertexInputs(const Program& program, const ShaderVertexInputInfo* vertex,
ShaderInfo& info) {
AddInput(info, StageInputKind::VertexIndex, 0, 1, "gl_VertexIndex");
AddInput(info, StageInputKind::InstanceIndex, 0, 1, "gl_InstanceIndex");
if (vertex == nullptr) {
return;
}
uint32_t used_components[ShaderVertexInputInfo::RES_MAX] = {};
for (const auto* block: program.values->blocks) {
for (const auto& inst: *block) {
if (inst.GetOpcode() == ValueOpcode::GetAttribute) {
const auto attr = inst.Arg(0).U32();
const auto chan = inst.Arg(1).U32();
used_components[attr] = std::max(used_components[attr], chan + 1u);
}
}
}
for (uint32_t attr = 0; attr < static_cast<uint32_t>(vertex->resources_num) &&
attr < ShaderVertexInputInfo::RES_MAX;
attr++) {
if (used_components[attr] != 0) {
AddInput(info, StageInputKind::Parameter, attr, used_components[attr],
fmt::format("in_attr_{}", attr));
}
}
}
void CollectPixelInputs(const ShaderPixelInputInfo* pixel, ShaderInfo& info) {
if (pixel == nullptr) {
return;
}
if (pixel->HasPositionInput()) {
AddInput(info, StageInputKind::FragCoord, 0, 4, "gl_FragCoord");
}
if (pixel->ps_front_face) {
AddInput(info, StageInputKind::FrontFacing, 0, 1, "gl_FrontFacing");
}
for (uint32_t input = 0; input < pixel->input_num; input++) {
AddInput(info, StageInputKind::Parameter, input, 4, fmt::format("in_param_{}", input));
}
}
void CollectComputeInputs(const ShaderComputeInputInfo* compute, ShaderInfo& info) {
if (compute != nullptr) {
if (compute->group_id[0] || compute->group_id[1] || compute->group_id[2]) {
AddInput(info, StageInputKind::WorkgroupId, 0, 3, "gl_WorkGroupID");
}
if (compute->thread_ids_num > 0) {
AddInput(info, StageInputKind::LocalInvocationId, 0, 3, "gl_LocalInvocationID");
}
if (compute->thread_ids_num > 0 || compute->tg_size_en) {
AddInput(info, StageInputKind::LocalInvocationIndex, 0, 1, "gl_LocalInvocationIndex");
}
if (compute->dispatch_thread_dimensions) {
AddInput(info, StageInputKind::GlobalInvocationId, 0, 3, "gl_GlobalInvocationID");
}
}
}
void CollectBuiltinInputs(const Program& program, ShaderInfo& info) {
for (const auto* block: program.values->blocks) {
for (const auto& inst: *block) {
if (inst.GetOpcode() != ValueOpcode::GetBuiltin) {
continue;
}
const auto kind = static_cast<StageInputKind>(inst.Arg(0).U32());
switch (kind) {
case StageInputKind::VertexIndex:
AddInput(info, kind, 0, 1, "gl_VertexIndex");
break;
case StageInputKind::InstanceIndex:
AddInput(info, kind, 0, 1, "gl_InstanceIndex");
break;
case StageInputKind::FragCoord: AddInput(info, kind, 0, 4, "gl_FragCoord"); break;
case StageInputKind::FrontFacing:
AddInput(info, kind, 0, 1, "gl_FrontFacing");
break;
case StageInputKind::WorkgroupId:
AddInput(info, kind, 0, 3, "gl_WorkGroupID");
break;
case StageInputKind::LocalInvocationId:
AddInput(info, kind, 0, 3, "gl_LocalInvocationID");
break;
case StageInputKind::LocalInvocationIndex:
AddInput(info, kind, 0, 1, "gl_LocalInvocationIndex");
break;
case StageInputKind::GlobalInvocationId:
AddInput(info, kind, 0, 3, "gl_GlobalInvocationID");
break;
case StageInputKind::Parameter: break;
}
}
}
}
void CollectOutputs(const Program& program, const ShaderPixelInputInfo* pixel, ShaderInfo& info) {
for (const auto* block: program.values->blocks) {
for (const auto& inst: *block) {
if (inst.GetOpcode() != ValueOpcode::SetAttribute) {
continue;
}
const auto& export_info = program.values->export_info[inst.Flags<ExportFlags>().index];
if (export_info.kind == ExportTargetKind::MrtZ) {
if (pixel != nullptr && (export_info.en & 0x1u) != 0 &&
pixel->ps_depth_export_enable) {
AddOutput(info, StageOutputKind::Depth, 0, 0, "gl_FragDepth");
}
if (pixel != nullptr && (export_info.en & 0x4u) != 0 &&
pixel->ps_sample_mask_export_enable) {
AddOutput(info, StageOutputKind::SampleMask, 0, 0, "gl_SampleMask");
}
continue;
}
if (export_info.en == 0) {
continue;
}
switch (export_info.kind) {
case ExportTargetKind::Position:
AddOutput(info, StageOutputKind::Position, export_info.index, 0,
"out_position");
break;
case ExportTargetKind::Parameter:
AddOutput(info, StageOutputKind::Parameter, export_info.index,
export_info.index, fmt::format("out_param_{}", export_info.index));
break;
case ExportTargetKind::Mrt:
AddOutput(info, StageOutputKind::Mrt, export_info.index, export_info.index,
fmt::format("out_mrt_{}", export_info.index));
break;
default: break;
}
}
}
}
} // namespace
bool CollectShaderInfo(Program& program, const ShaderInfoOptions& options, std::string* error) {
if (!program.resource_tracking_complete || program.shader_info_complete) {
if (error != nullptr) {
*error = !program.resource_tracking_complete ? "shader resources were not tracked"
: "shader info already collected";
}
return false;
}
if (program.values == nullptr) {
if (error != nullptr) {
*error = "typed value program is not available";
}
return false;
}
if (!ValidateOptions(program, options, error)) {
return false;
}
if (!ValidateValueReferences(program, options, error)) {
return false;
}
auto next = program.info;
next.inputs.clear();
next.outputs.clear();
next.has_bitwise_xor = std::any_of(
program.values->blocks.begin(), program.values->blocks.end(), [](const auto* block) {
return std::any_of(block->begin(), block->end(), [](const auto& inst) {
return inst.GetOpcode() == ValueOpcode::BitwiseXor32;
});
});
switch (program.stage) {
case ShaderType::Vertex: CollectVertexInputs(program, options.vertex, next); break;
case ShaderType::Pixel: CollectPixelInputs(options.pixel, next); break;
case ShaderType::Compute: CollectComputeInputs(options.compute, next); break;
default: return false;
}
CollectBuiltinInputs(program, next);
CollectOutputs(program, options.pixel, next);
program.info = std::move(next);
program.shader_info_complete = true;
return true;
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -0,0 +1,951 @@
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
#include "graphics/shader/recompiler/ir/ValueProgram.h"
#include <algorithm>
#include <bit>
#include <cstring>
#include <fmt/format.h>
#include <unordered_map>
#include <unordered_set>
namespace Libs::Graphics::ShaderRecompiler::IR {
namespace {
constexpr uint64_t AddressMask = 0x0000ffffffffffffull;
const char* StageName(ShaderType stage) {
switch (stage) {
case ShaderType::Vertex: return "vertex";
case ShaderType::Pixel: return "pixel";
case ShaderType::Fetch: return "fetch";
case ShaderType::Compute: return "compute";
default: return "unknown";
}
}
std::string Diagnostic(const Program& program, uint32_t pc, const std::string& message) {
return fmt::format("shader SRT: hash=0x{:016x} stage={} pc=0x{:08x} {}", program.shader_hash,
StageName(program.stage), pc, message);
}
bool AddSignedAddress(uint64_t base, int64_t offset, uint64_t& result) {
if (base > AddressMask) {
return false;
}
if (offset < 0) {
const auto magnitude = uint64_t {0} - static_cast<uint64_t>(offset);
if (magnitude > base) {
return false;
}
result = base - magnitude;
return true;
}
const auto magnitude = static_cast<uint64_t>(offset);
if (magnitude > AddressMask - base) {
return false;
}
result = base + magnitude;
return true;
}
bool IsRawRead(const ValueProgram& values, const Inst& inst) {
if (inst.GetOpcode() != ValueOpcode::LoadAddressU32 &&
inst.GetOpcode() != ValueOpcode::ReadConstBuffer) {
return false;
}
const auto index = inst.Flags<MemoryFlags>().index;
return index < values.memory_info.size() &&
values.memory_info[index].kind == ResourceKind::ScalarBuffer;
}
bool IsDescriptorHandle(ValueOpcode opcode) {
switch (opcode) {
case ValueOpcode::GetBufferResource:
case ValueOpcode::GetAddressResource:
case ValueOpcode::GetImageResource:
case ValueOpcode::GetSamplerResource: return true;
default: return false;
}
}
bool IsRuntimeIntegerOp(ValueOpcode op) {
switch (op) {
case ValueOpcode::CompositeConstructU32x2:
case ValueOpcode::CompositeExtractU32x2:
case ValueOpcode::PackUint2x32:
case ValueOpcode::UnpackUint2x32:
case ValueOpcode::BitFieldInsert:
case ValueOpcode::BitFieldUExtract:
case ValueOpcode::BitFieldSExtract:
case ValueOpcode::IAdd32:
case ValueOpcode::IAdd64:
case ValueOpcode::IAddCarry32:
case ValueOpcode::ISub32:
case ValueOpcode::ISub64:
case ValueOpcode::IMul32:
case ValueOpcode::IMul64:
case ValueOpcode::ShiftLeftLogical32:
case ValueOpcode::ShiftLeftLogical64:
case ValueOpcode::ShiftRightLogical32:
case ValueOpcode::ShiftRightLogical64:
case ValueOpcode::ShiftRightArithmetic32:
case ValueOpcode::ShiftRightArithmetic64:
case ValueOpcode::BitwiseAnd32:
case ValueOpcode::BitwiseAnd64:
case ValueOpcode::BitwiseOr32:
case ValueOpcode::BitwiseOr64:
case ValueOpcode::BitwiseXor32:
case ValueOpcode::BitwiseNot32:
case ValueOpcode::SelectU1:
case ValueOpcode::SelectU32:
case ValueOpcode::ULessThan32:
case ValueOpcode::IEqual32:
case ValueOpcode::UGreaterThan32:
case ValueOpcode::INotEqual32:
case ValueOpcode::LogicalOr:
case ValueOpcode::LogicalAnd:
case ValueOpcode::LogicalXor:
case ValueOpcode::LogicalNot: return true;
default: return false;
}
}
class RuntimeValidator {
public:
RuntimeValidator(const Program& program, const ValueProgram& values)
: m_program(program), m_values(values) {}
bool Run(Value value, std::string& reason) { return Validate(value, reason); }
private:
bool Validate(Value value, std::string& reason) {
value = value.Resolve();
const auto* inst = value.TryInstruction();
if (inst == nullptr) {
switch (value.GetType()) {
case Type::U1:
case Type::U8:
case Type::U16:
case Type::U32:
case Type::U64: return true;
default:
reason = fmt::format("contains a non-integer immediate of type {}",
TypeName(value.GetType()));
return false;
}
}
if (!m_visiting.insert(inst).second) {
reason = fmt::format("contains a cyclic {} value", ValueOpcodeName(inst->GetOpcode()));
return false;
}
const auto finish = [&](bool valid) {
m_visiting.erase(inst);
return valid;
};
const auto op = inst->GetOpcode();
if (op == ValueOpcode::UndefU1 || op == ValueOpcode::UndefU8 ||
op == ValueOpcode::UndefU16 || op == ValueOpcode::UndefU32 ||
op == ValueOpcode::UndefU64 || op == ValueOpcode::Void) {
reason = fmt::format("contains {}", ValueOpcodeName(op));
return finish(false);
}
if (op == ValueOpcode::GetUserData) {
if (inst->NumArgs() != 1 || inst->Arg(0).GetType() != Type::ScalarReg) {
reason = "contains a malformed GetUserData";
return finish(false);
}
const auto reg = RegIndex(inst->Arg(0).ScalarRegister());
if (reg < m_program.user_data_base ||
reg - m_program.user_data_base >= m_program.user_data_count) {
reason = fmt::format("references unavailable user SGPR {}", reg);
return finish(false);
}
return finish(true);
}
if (op == ValueOpcode::GetShaderBase) {
if (inst->NumArgs() != 0) {
reason = "contains a malformed GetShaderBase";
return finish(false);
}
return finish(true);
}
if (op == ValueOpcode::Phi) {
const auto invariant = ResolveInvariantPhi(m_values, value);
if (invariant.IsEmpty()) {
reason = "contains a control-dependent phi";
return finish(false);
}
return finish(Validate(invariant, reason));
}
if (op == ValueOpcode::GetSrtResource) {
if (inst->NumArgs() != 0) {
reason = "contains a malformed GetSrtResource";
return finish(false);
}
return finish(true);
}
if (op == ValueOpcode::ReadConst) {
const auto slot = inst->NumArgs() == 2 ? inst->Arg(1).Resolve() : Value {};
if (inst->NumArgs() != 2 || inst->Arg(0).Resolve().TryInstruction() == nullptr ||
inst->Arg(0).Resolve().TryInstruction()->GetOpcode() !=
ValueOpcode::GetSrtResource ||
!slot.IsImmediate() || slot.GetType() != Type::U32 ||
slot.U32() >= m_values.srt_reads.size()) {
reason = "contains a malformed flattened SRT read";
return finish(false);
}
} else if (op == ValueOpcode::LoadAddressU32 || op == ValueOpcode::ReadConstBuffer) {
const auto expected = op == ValueOpcode::LoadAddressU32
? ValueOpcode::GetAddressResource
: ValueOpcode::GetBufferResource;
const auto* handle = inst->NumArgs() != 0 ? inst->Arg(0).ResolveInstruction() : nullptr;
if (!IsRawRead(m_values, *inst) || handle == nullptr ||
handle->GetOpcode() != expected) {
reason = fmt::format("contains a non-scalar {}", ValueOpcodeName(op));
return finish(false);
}
} else if (op == ValueOpcode::PackUint2x32) {
const auto* pair = inst->NumArgs() == 1 ? inst->Arg(0).ResolveInstruction() : nullptr;
if (pair == nullptr || pair->GetOpcode() != ValueOpcode::CompositeConstructU32x2) {
reason = "contains an unsupported packed runtime value";
return finish(false);
}
} else if (op == ValueOpcode::CompositeExtractU32x2) {
const auto* source = inst->NumArgs() == 2 ? inst->Arg(0).ResolveInstruction() : nullptr;
const auto index = inst->NumArgs() == 2 ? inst->Arg(1).Resolve() : Value {};
if (source == nullptr || !index.IsImmediate() || index.GetType() != Type::U32 ||
index.U32() >= 2u ||
(source->GetOpcode() != ValueOpcode::CompositeConstructU32x2 &&
source->GetOpcode() != ValueOpcode::UnpackUint2x32 &&
source->GetOpcode() != ValueOpcode::IAddCarry32)) {
reason = "contains an unsupported composite runtime source";
return finish(false);
}
}
if (op == ValueOpcode::GetBufferResource || op == ValueOpcode::GetImageResource ||
op == ValueOpcode::GetSamplerResource || op == ValueOpcode::GetAddressResource) {
const size_t expected = op == ValueOpcode::GetBufferResource ? 4u
: op == ValueOpcode::GetImageResource ? 8u
: op == ValueOpcode::GetSamplerResource ? 4u
: 2u;
if (inst->NumArgs() != expected) {
reason = fmt::format("contains a malformed {}", ValueOpcodeName(op));
return finish(false);
}
} else if (op != ValueOpcode::ReadConst && op != ValueOpcode::ReadConstBuffer &&
op != ValueOpcode::LoadAddressU32 && !IsRuntimeIntegerOp(op)) {
reason =
fmt::format("contains unsupported or control-dependent {}", ValueOpcodeName(op));
return finish(false);
}
for (size_t index = 0; index < inst->NumArgs(); index++) {
if (!Validate(inst->Arg(index), reason)) {
return finish(false);
}
}
return finish(true);
}
const Program& m_program;
const ValueProgram& m_values;
std::unordered_set<const Inst*> m_visiting;
};
class PlanBuilder {
public:
PlanBuilder(Program& program, ValueProgram& values): m_program(program), m_values(values) {}
bool Run(std::string* error) {
m_values.srt_reads.clear();
m_values.dynamic_reads.clear();
for (auto* block: m_values.blocks) {
for (auto& inst: *block) {
if (IsDescriptorHandle(inst.GetOpcode())) {
for (size_t index = 0; index < inst.NumArgs(); index++) {
if (!Collect(inst.Arg(index), 0, error)) {
return false;
}
}
}
}
}
for (auto* block: m_values.blocks) {
for (auto& inst: *block) {
std::string reason;
if (inst.GetOpcode() == ValueOpcode::LoadAddressU32 && IsRawRead(m_values, inst) &&
inst.Arg(1).Resolve().IsImmediate() &&
ValidateRuntimeValue(m_program, Value(&inst), reason) &&
!Collect(Value(&inst), inst.Flags<MemoryFlags>().pc, error)) {
return false;
}
}
}
PatchReads();
return true;
}
private:
struct Patch {
Inst* inst = nullptr;
uint32_t slot = 0;
bool keep = false;
};
bool Fail(uint32_t pc, std::string* error, const std::string& message) const {
if (error != nullptr) {
*error = Diagnostic(m_program, pc, message);
}
return false;
}
bool Collect(Value value, uint32_t use_pc, std::string* error) {
value = value.Resolve();
if (value.IsImmediate()) {
return true;
}
auto* inst = value.TryInstruction();
if (inst == nullptr) {
return Fail(use_pc, error, "invalid typed planning value");
}
if (std::ranges::find(m_visiting, inst) != m_visiting.end()) {
return inst->GetOpcode() == ValueOpcode::Phi;
}
if (std::ranges::find(m_visited, inst) != m_visited.end()) {
return true;
}
m_visiting.push_back(inst);
for (size_t index = 0; index < inst->NumArgs(); index++) {
if (!Collect(inst->Arg(index), use_pc, error)) {
return false;
}
}
m_visiting.pop_back();
m_visited.push_back(inst);
if (!IsRawRead(m_values, *inst)) {
return true;
}
const auto offset = inst->Arg(1).Resolve();
if (!offset.IsImmediate() || offset.GetType() != Type::U32) {
if (std::ranges::find(m_values.dynamic_reads, value) == m_values.dynamic_reads.end()) {
m_values.dynamic_reads.push_back(value);
}
return true;
}
use_pc = inst->Flags<MemoryFlags>().pc;
for (uint32_t slot = 0; slot < m_values.srt_reads.size(); slot++) {
if (EquivalentValue(m_values, value, m_values.srt_reads[slot].value)) {
m_patches.push_back({inst, slot, false});
return true;
}
}
const auto slot = static_cast<uint32_t>(m_values.srt_reads.size());
m_values.srt_reads.push_back({value, slot, use_pc});
m_patches.push_back({inst, slot, true});
return true;
}
void PatchReads() {
for (const auto& patch: m_patches) {
auto* block = patch.inst->Parent();
auto& list = block->Instructions();
auto where =
std::ranges::find_if(list, [&](const Inst& inst) { return &inst == patch.inst; });
const auto resource =
Value(&*block->PrependNewInst(where, ValueOpcode::GetSrtResource));
const auto flat = Value(&*block->PrependNewInst(where, ValueOpcode::ReadConst,
{resource, Value(patch.slot)}));
const auto uses = patch.inst->Uses();
for (const auto& use: uses) {
use.user->SetArg(use.operand, flat);
}
for (auto& info: m_values.block_info) {
if (info.condition.Resolve() == Value(patch.inst)) {
info.condition = flat;
}
if (info.indirect_target.Resolve() == Value(patch.inst)) {
info.indirect_target = flat;
}
}
if (patch.keep) {
const auto memory = patch.inst->Flags<MemoryFlags>().index;
if (memory < m_values.memory_info.size()) {
m_values.memory_info[memory].planning_only = true;
}
block->AppendNewInst(ValueOpcode::ReferenceU32, {Value(patch.inst)});
}
}
}
Program& m_program;
ValueProgram& m_values;
std::vector<Inst*> m_visiting;
std::vector<Inst*> m_visited;
std::vector<Patch> m_patches;
};
class Evaluator {
public:
Evaluator(const Program& program, const SrtRuntime& runtime)
: m_program(program), m_values(*program.values), m_runtime(runtime) {}
void SetUsePc(uint32_t pc) { m_use_pc = pc; }
bool Evaluate(Value value, uint32_t& result, std::string* error) {
uint64_t wide = 0;
if (!EvaluateWide(value, wide, error)) {
return false;
}
result = static_cast<uint32_t>(wide);
return true;
}
private:
bool Fail(std::string* error, const std::string& message) const {
if (error != nullptr) {
*error = Diagnostic(m_program, m_use_pc, message);
}
return false;
}
bool EvaluateWide(Value value, uint64_t& result, std::string* error) {
value = value.Resolve();
if (value.IsImmediate()) {
switch (value.GetType()) {
case Type::U1: result = value.U1(); return true;
case Type::U8: result = value.U8(); return true;
case Type::U16: result = value.U16(); return true;
case Type::U32: result = value.U32(); return true;
case Type::U64: result = value.U64(); return true;
default: return Fail(error, "non-integer immediate in runtime expression");
}
}
auto* inst = value.TryInstruction();
if (inst == nullptr) {
return Fail(error, "invalid typed runtime value");
}
if (const auto found = m_cache.find(inst); found != m_cache.end()) {
result = found->second;
return true;
}
if (std::ranges::find(m_visiting, inst) != m_visiting.end()) {
return Fail(error, "cyclic typed runtime value");
}
m_visiting.push_back(inst);
uint64_t out = 0;
if (!EvaluateInst(*inst, out, error)) {
return false;
}
m_visiting.pop_back();
m_cache.emplace(inst, out);
result = out;
return true;
}
bool Arg(const Inst& inst, size_t index, uint64_t& result, std::string* error) {
return EvaluateWide(inst.Arg(index), result, error);
}
bool EvaluatePhi(const Inst& inst, uint64_t& result, std::string* error) {
const auto value = ResolveInvariantPhi(m_values, Value(const_cast<Inst*>(&inst)));
return !value.IsEmpty() ? EvaluateWide(value, result, error)
: Fail(error, "typed phi has runtime-dependent values");
}
bool EvaluateExtract(const Inst& inst, uint64_t& result, std::string* error) {
const auto index = inst.Arg(1).Resolve();
if (!index.IsImmediate() || index.GetType() != Type::U32) {
return Fail(error, "dynamic composite extract in runtime expression");
}
const auto component = index.U32();
const auto* source = inst.Arg(0).ResolveInstruction();
if (source == nullptr || component >= 2u) {
return Fail(error, "unsupported composite runtime source");
}
if (source->GetOpcode() == ValueOpcode::CompositeConstructU32x2) {
return EvaluateWide(source->Arg(component), result, error);
}
if (source->GetOpcode() == ValueOpcode::UnpackUint2x32) {
uint64_t packed = 0;
if (!Arg(*source, 0, packed, error)) {
return false;
}
result = static_cast<uint32_t>(packed >> (component * 32u));
return true;
}
if (source->GetOpcode() == ValueOpcode::IAddCarry32) {
uint64_t lhs = 0;
uint64_t rhs = 0;
if (!Arg(*source, 0, lhs, error) || !Arg(*source, 1, rhs, error)) {
return false;
}
const auto sum =
static_cast<uint64_t>(static_cast<uint32_t>(lhs)) + static_cast<uint32_t>(rhs);
result =
component == 0u ? static_cast<uint32_t>(sum) : static_cast<uint32_t>(sum >> 32u);
return true;
}
return Fail(error, "unsupported composite runtime source");
}
bool EvaluateRawRead(const Inst& inst, uint64_t& result, std::string* error) {
const auto flags = inst.Flags<MemoryFlags>();
if (flags.index >= m_values.memory_info.size()) {
return Fail(error, "raw scalar read has invalid metadata");
}
const auto& mem = m_values.memory_info[flags.index];
const auto* handle = inst.Arg(0).ResolveInstruction();
if (handle == nullptr) {
return Fail(error, "raw scalar read has no descriptor handle");
}
uint64_t low = 0;
uint64_t high = 0;
uint64_t offset = 0;
if (!Arg(*handle, 0, low, error) || !Arg(*handle, 1, high, error) ||
!Arg(inst, 1, offset, error)) {
return false;
}
const auto base = ((high << 32u) | static_cast<uint32_t>(low)) & AddressMask;
const auto immediate = static_cast<int64_t>(static_cast<int32_t>(mem.offset));
uint64_t address = 0;
if (inst.GetOpcode() == ValueOpcode::ReadConstBuffer) {
uint64_t records = 0;
uint64_t word3 = 0;
if (handle->NumArgs() != 4u || !Arg(*handle, 2, records, error) ||
!Arg(*handle, 3, word3, error)) {
return Fail(error, "constant-buffer descriptor has invalid width");
}
if (immediate < 0) {
return Fail(error, "constant-buffer read has a negative immediate offset");
}
const auto byte_offset =
static_cast<uint64_t>(immediate) + static_cast<uint32_t>(offset);
const auto aligned = byte_offset & ~uint64_t {3};
const auto stride = (static_cast<uint32_t>(high) >> 16u) & 0x3fffu;
const auto size = stride == 0u
? static_cast<uint64_t>(static_cast<uint32_t>(records))
: static_cast<uint64_t>(stride) * static_cast<uint32_t>(records);
if (aligned > size || size - aligned < sizeof(uint32_t)) {
return Fail(
error, fmt::format("constant-buffer offset {} exceeds size {}", aligned, size));
}
address = ((base & ~uint64_t {3}) + byte_offset) & ~uint64_t {3};
} else {
const auto relative = (immediate & ~int64_t {3}) +
static_cast<int64_t>(static_cast<uint32_t>(offset) & ~3u);
if (!AddSignedAddress(base & ~uint64_t {3}, relative, address)) {
return Fail(error, "raw scalar read is outside the 48-bit address space");
}
}
uint32_t word = 0;
if (m_runtime.read_memory != nullptr) {
if (!m_runtime.read_memory(m_runtime.userdata, address, &word)) {
return Fail(error, fmt::format("constant read failed at 0x{:016x}", address));
}
} else {
std::memcpy(&word, reinterpret_cast<const void*>(address), sizeof(word));
}
result = word;
return true;
}
bool EvaluateInst(const Inst& inst, uint64_t& result, std::string* error) {
uint64_t a = 0;
uint64_t b = 0;
uint64_t c = 0;
const auto binary = [&]() { return Arg(inst, 0, a, error) && Arg(inst, 1, b, error); };
const auto ternary = [&]() {
return Arg(inst, 0, a, error) && Arg(inst, 1, b, error) && Arg(inst, 2, c, error);
};
switch (inst.GetOpcode()) {
case ValueOpcode::GetUserData: {
const auto reg = RegIndex(inst.Arg(0).ScalarRegister());
if (reg < m_program.user_data_base ||
reg - m_program.user_data_base >= m_runtime.user_data.size()) {
return Fail(error, fmt::format("user SGPR {} is unavailable", reg));
}
result = m_runtime.user_data[reg - m_program.user_data_base];
return true;
}
case ValueOpcode::GetShaderBase: result = m_runtime.shader_base; return true;
case ValueOpcode::Phi: return EvaluatePhi(inst, result, error);
case ValueOpcode::CompositeExtractU32x2: return EvaluateExtract(inst, result, error);
case ValueOpcode::PackUint2x32: {
const auto* pair = inst.Arg(0).ResolveInstruction();
if (pair == nullptr || pair->GetOpcode() != ValueOpcode::CompositeConstructU32x2 ||
!Arg(*pair, 0, a, error) || !Arg(*pair, 1, b, error)) {
return Fail(error, "unsupported packed runtime value");
}
result = static_cast<uint32_t>(a) |
(static_cast<uint64_t>(static_cast<uint32_t>(b)) << 32u);
return true;
}
case ValueOpcode::ReadConst: {
const auto slot = inst.Arg(1).Resolve();
if (!slot.IsImmediate() || slot.GetType() != Type::U32 ||
slot.U32() >= m_values.srt_reads.size()) {
return Fail(error, "invalid flattened SRT index");
}
return EvaluateWide(m_values.srt_reads[slot.U32()].value, result, error);
}
case ValueOpcode::LoadAddressU32:
case ValueOpcode::ReadConstBuffer:
if (IsRawRead(m_values, inst)) {
return EvaluateRawRead(inst, result, error);
}
break;
case ValueOpcode::IAdd32:
if (binary()) {
result = static_cast<uint32_t>(a + b);
return true;
}
return false;
case ValueOpcode::IAdd64:
if (binary()) {
result = a + b;
return true;
}
return false;
case ValueOpcode::ISub32:
if (binary()) {
result = static_cast<uint32_t>(a - b);
return true;
}
return false;
case ValueOpcode::ISub64:
if (binary()) {
result = a - b;
return true;
}
return false;
case ValueOpcode::IMul32:
if (binary()) {
result = static_cast<uint32_t>(a * b);
return true;
}
return false;
case ValueOpcode::IMul64:
if (binary()) {
result = a * b;
return true;
}
return false;
case ValueOpcode::BitwiseAnd32:
if (binary()) {
result = static_cast<uint32_t>(a & b);
return true;
}
return false;
case ValueOpcode::BitwiseAnd64:
if (binary()) {
result = a & b;
return true;
}
return false;
case ValueOpcode::BitwiseOr32:
if (binary()) {
result = static_cast<uint32_t>(a | b);
return true;
}
return false;
case ValueOpcode::BitwiseOr64:
if (binary()) {
result = a | b;
return true;
}
return false;
case ValueOpcode::BitwiseXor32:
if (binary()) {
result = static_cast<uint32_t>(a ^ b);
return true;
}
return false;
case ValueOpcode::BitwiseNot32:
if (Arg(inst, 0, a, error)) {
result = ~static_cast<uint32_t>(a);
return true;
}
return false;
case ValueOpcode::ShiftLeftLogical32:
if (binary()) {
result = static_cast<uint32_t>(a) << (b & 31u);
return true;
}
return false;
case ValueOpcode::ShiftLeftLogical64:
if (binary()) {
result = a << (b & 63u);
return true;
}
return false;
case ValueOpcode::ShiftRightLogical32:
if (binary()) {
result = static_cast<uint32_t>(a) >> (b & 31u);
return true;
}
return false;
case ValueOpcode::ShiftRightLogical64:
if (binary()) {
result = a >> (b & 63u);
return true;
}
return false;
case ValueOpcode::ShiftRightArithmetic32:
if (binary()) {
result = static_cast<uint32_t>(
std::bit_cast<int32_t>(static_cast<uint32_t>(a)) >> (b & 31u));
return true;
}
return false;
case ValueOpcode::ShiftRightArithmetic64:
if (binary()) {
result = static_cast<uint64_t>(std::bit_cast<int64_t>(a) >> (b & 63u));
return true;
}
return false;
case ValueOpcode::BitFieldUExtract:
if (ternary()) {
const auto offset = static_cast<uint32_t>(b);
const auto width = static_cast<uint32_t>(c);
if (offset > 32u || width > 32u - offset) {
return Fail(error, "invalid unsigned bit-field range");
}
const auto mask = width == 32u ? UINT32_MAX
: width == 0u ? 0u
: (uint32_t {1} << width) - 1u;
result = width == 0u ? 0u : (static_cast<uint32_t>(a) >> offset) & mask;
return true;
}
return false;
case ValueOpcode::BitFieldSExtract:
if (ternary()) {
const auto offset = static_cast<uint32_t>(b);
const auto width = static_cast<uint32_t>(c);
if (offset > 32u || width > 32u - offset) {
return Fail(error, "invalid signed bit-field range");
}
if (width == 0u) {
result = 0;
return true;
}
const auto mask = width == 32u ? UINT32_MAX : (uint32_t {1} << width) - 1u;
auto bits = (static_cast<uint32_t>(a) >> offset) & mask;
if (width < 32u && (bits & (uint32_t {1} << (width - 1u))) != 0u) {
bits |= ~mask;
}
result = bits;
return true;
}
return false;
case ValueOpcode::BitFieldInsert: {
uint64_t d = 0;
if (!ternary() || !Arg(inst, 3, d, error)) {
return false;
}
const auto offset = static_cast<uint32_t>(c);
const auto width = static_cast<uint32_t>(d);
if (offset > 32u || width > 32u - offset) {
return Fail(error, "invalid inserted bit-field range");
}
if (width == 0u) {
result = static_cast<uint32_t>(a);
return true;
}
const auto mask =
width == 32u ? UINT32_MAX : ((uint32_t {1} << width) - 1u) << offset;
result = (static_cast<uint32_t>(a) & ~mask) |
((static_cast<uint32_t>(b) << offset) & mask);
return true;
}
case ValueOpcode::SelectU32:
case ValueOpcode::SelectU1:
if (ternary()) {
result = a != 0u ? b : c;
return true;
}
return false;
case ValueOpcode::IEqual32:
if (binary()) {
result = static_cast<uint32_t>(a) == static_cast<uint32_t>(b);
return true;
}
return false;
case ValueOpcode::INotEqual32:
if (binary()) {
result = static_cast<uint32_t>(a) != static_cast<uint32_t>(b);
return true;
}
return false;
case ValueOpcode::ULessThan32:
if (binary()) {
result = static_cast<uint32_t>(a) < static_cast<uint32_t>(b);
return true;
}
return false;
case ValueOpcode::UGreaterThan32:
if (binary()) {
result = static_cast<uint32_t>(a) > static_cast<uint32_t>(b);
return true;
}
return false;
case ValueOpcode::LogicalAnd:
if (binary()) {
result = (a != 0u) && (b != 0u);
return true;
}
return false;
case ValueOpcode::LogicalOr:
if (binary()) {
result = (a != 0u) || (b != 0u);
return true;
}
return false;
case ValueOpcode::LogicalXor:
if (binary()) {
result = (a != 0u) != (b != 0u);
return true;
}
return false;
case ValueOpcode::LogicalNot:
if (Arg(inst, 0, a, error)) {
result = a == 0u;
return true;
}
return false;
case ValueOpcode::UndefU1:
case ValueOpcode::UndefU8:
case ValueOpcode::UndefU16:
case ValueOpcode::UndefU32:
case ValueOpcode::UndefU64: return Fail(error, "undefined typed runtime value");
default: break;
}
return Fail(error, fmt::format("unsupported typed runtime opcode {}",
ValueOpcodeName(inst.GetOpcode())));
}
const Program& m_program;
const ValueProgram& m_values;
const SrtRuntime& m_runtime;
uint32_t m_use_pc = 0;
std::unordered_map<const Inst*, uint64_t> m_cache;
std::vector<const Inst*> m_visiting;
};
const DescriptorSource* Source(const Program& program, uint32_t source) {
if (program.values == nullptr || source >= program.values->descriptor_sources.size()) {
return nullptr;
}
return &program.values->descriptor_sources[source];
}
bool EvaluateRuntimeSourcesImpl(const Program& program,
std::span<const DescriptorSourceRequest> requests,
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
std::vector<uint32_t>& flat, bool evaluate_flat,
std::string* error) {
if (program.values == nullptr || !program.srt_plan_complete) {
if (error != nullptr) {
*error = Diagnostic(program, 0, "typed SRT plan is not ready");
}
return false;
}
Evaluator evaluator(program, runtime);
std::vector<DescriptorValue> evaluated;
evaluated.reserve(requests.size());
for (const auto& request: requests) {
const auto* source = Source(program, request.source);
if (source == nullptr) {
if (error != nullptr) {
*error = Diagnostic(program, request.use_pc,
fmt::format("invalid descriptor source {}", request.source));
}
return false;
}
evaluator.SetUsePc(request.use_pc);
DescriptorValue value;
value.dword_count = source->dword_count;
for (uint32_t index = 0; index < source->dword_count; index++) {
if (!evaluator.Evaluate(source->dwords[index], value.dwords[index], error)) {
return false;
}
}
evaluated.push_back(value);
}
std::vector<uint32_t> flattened;
if (evaluate_flat) {
flattened.resize(program.values->srt_reads.size());
for (const auto& read: program.values->srt_reads) {
evaluator.SetUsePc(read.use_pc);
if (read.flat_offset >= flattened.size() ||
!evaluator.Evaluate(read.value, flattened[read.flat_offset], error)) {
return false;
}
}
}
results = std::move(evaluated);
if (evaluate_flat) {
flat = std::move(flattened);
}
return true;
}
} // namespace
bool ValidateRuntimeValue(const Program& program, Value value, std::string& reason) {
if (program.values == nullptr) {
reason = "typed SSA is not ready";
return false;
}
return RuntimeValidator(program, *program.values).Run(value, reason);
}
bool BuildSrtPlan(Program& program, std::string* error) {
if (program.values == nullptr || program.resource_tracking_complete) {
if (error != nullptr) {
*error = program.values == nullptr ? "typed SSA is not ready"
: "cannot rebuild SRT after resource tracking";
}
return false;
}
program.srt_plan_complete = false;
if (!PlanBuilder(program, *program.values).Run(error)) {
return false;
}
program.srt_plan_complete = true;
return true;
}
bool EvaluateDescriptorSource(const Program& program, uint32_t source, uint32_t use_pc,
const SrtRuntime& runtime, DescriptorValue& result,
std::string* error) {
const DescriptorSourceRequest request {source, use_pc};
std::vector<DescriptorValue> results;
if (!EvaluateDescriptorSources(program, std::span {&request, 1}, runtime, results, error)) {
return false;
}
result = results.front();
return true;
}
bool EvaluateDescriptorSources(const Program& program,
std::span<const DescriptorSourceRequest> requests,
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
std::string* error) {
std::vector<uint32_t> ignored;
return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, ignored, false, error);
}
bool EvaluateRuntimeSources(const Program& program,
std::span<const DescriptorSourceRequest> requests,
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
std::vector<uint32_t>& flat, std::string* error) {
return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, flat, true, error);
}
bool WalkSrt(const Program& program, const SrtRuntime& runtime, std::vector<uint32_t>& flat,
std::string* error) {
std::vector<DescriptorValue> ignored;
return EvaluateRuntimeSources(program, {}, runtime, ignored, flat, error);
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -8,6 +8,8 @@
namespace Libs::Graphics::ShaderRecompiler::IR {
class Value;
using SrtMemoryReader = bool (*)(void* userdata, uint64_t address, uint32_t* value);
struct SrtRuntime {
@@ -23,11 +25,10 @@ struct DescriptorSourceRequest {
uint32_t use_pc = 0;
};
bool FoldScalarConstant(const ScalarProvenance& provenance, uint32_t value, uint32_t& result);
// Collects reachable ReadConst values. Immediate offsets receive compact flat-buffer slots;
// dynamic offsets remain explicit and are never assigned a fake slot.
bool BuildSrtPlan(Program& program, std::string* error);
bool ValidateRuntimeValue(const Program& program, Value value, std::string& reason);
bool EvaluateDescriptorSource(const Program& program, uint32_t source, uint32_t use_pc,
const SrtRuntime& runtime, DescriptorValue& result,
@@ -0,0 +1,377 @@
#include "graphics/shader/recompiler/ir/passes/SsaRewrite.h"
#include <algorithm>
#include <map>
#include <unordered_map>
#include <variant>
namespace Libs::Graphics::ShaderRecompiler::IR {
namespace {
struct SccTag {
auto operator<=>(const SccTag&) const = default;
};
struct ThreadBitScalarReg {
ScalarReg reg {};
auto operator<=>(const ThreadBitScalarReg&) const = default;
};
struct ExecTag {
auto operator<=>(const ExecTag&) const = default;
};
struct ExecLoTag {
auto operator<=>(const ExecLoTag&) const = default;
};
struct ExecHiTag {
auto operator<=>(const ExecHiTag&) const = default;
};
struct VccTag {
auto operator<=>(const VccTag&) const = default;
};
struct VccLoTag {
auto operator<=>(const VccLoTag&) const = default;
};
struct VccHiTag {
auto operator<=>(const VccHiTag&) const = default;
};
struct M0Tag {
auto operator<=>(const M0Tag&) const = default;
};
struct GotoVariable {
uint32_t index = 0;
auto operator<=>(const GotoVariable&) const = default;
};
using Variable = std::variant<ScalarReg, ThreadBitScalarReg, VectorReg, GotoVariable, SccTag,
ExecTag, ExecLoTag, ExecHiTag, VccTag, VccLoTag, VccHiTag, M0Tag>;
using ValueMap = std::unordered_map<Block*, Value>;
struct DefTable {
const Value& Get(Block* block, ScalarReg reg) {
if (RegIndex(reg) >= NumScalarRegs) {
EXIT("SSA scalar read index is out of range: %u", RegIndex(reg));
}
return block->ssa_sreg_values[RegIndex(reg)];
}
void Set(Block* block, ScalarReg reg, Value value) {
if (RegIndex(reg) >= NumScalarRegs) {
EXIT("SSA scalar write index is out of range: %u", RegIndex(reg));
}
block->ssa_sreg_values[RegIndex(reg)] = value;
}
const Value& Get(Block* block, ThreadBitScalarReg value) {
EXIT_IF(RegIndex(value.reg) >= NumScalarRegs);
return block->ssa_thread_bit_sreg_values[RegIndex(value.reg)];
}
void Set(Block* block, ThreadBitScalarReg value, Value definition) {
EXIT_IF(RegIndex(value.reg) >= NumScalarRegs);
block->ssa_thread_bit_sreg_values[RegIndex(value.reg)] = definition;
}
const Value& Get(Block* block, VectorReg reg) {
EXIT_IF(RegIndex(reg) >= NumVectorRegs);
return block->ssa_vreg_values[RegIndex(reg)];
}
void Set(Block* block, VectorReg reg, Value value) {
EXIT_IF(RegIndex(reg) >= NumVectorRegs);
block->ssa_vreg_values[RegIndex(reg)] = value;
}
const Value& Get(Block* block, GotoVariable value) {
return goto_variables[value.index][block];
}
void Set(Block* block, GotoVariable value, Value definition) {
goto_variables[value.index][block] = definition;
}
const Value& Get(Block* block, SccTag) { return scc[block]; }
void Set(Block* block, SccTag, Value value) { scc[block] = value; }
const Value& Get(Block* block, ExecTag) { return exec[block]; }
void Set(Block* block, ExecTag, Value value) { exec[block] = value; }
const Value& Get(Block* block, ExecLoTag) { return exec_lo[block]; }
void Set(Block* block, ExecLoTag, Value value) { exec_lo[block] = value; }
const Value& Get(Block* block, ExecHiTag) { return exec_hi[block]; }
void Set(Block* block, ExecHiTag, Value value) { exec_hi[block] = value; }
const Value& Get(Block* block, VccTag) { return vcc[block]; }
void Set(Block* block, VccTag, Value value) { vcc[block] = value; }
const Value& Get(Block* block, VccLoTag) { return vcc_lo[block]; }
void Set(Block* block, VccLoTag, Value value) { vcc_lo[block] = value; }
const Value& Get(Block* block, VccHiTag) { return vcc_hi[block]; }
void Set(Block* block, VccHiTag, Value value) { vcc_hi[block] = value; }
const Value& Get(Block* block, M0Tag) { return m0[block]; }
void Set(Block* block, M0Tag, Value value) { m0[block] = value; }
ValueMap scc;
ValueMap exec;
ValueMap exec_lo;
ValueMap exec_hi;
ValueMap vcc;
ValueMap vcc_lo;
ValueMap vcc_hi;
ValueMap m0;
std::unordered_map<uint32_t, ValueMap> goto_variables;
};
ValueOpcode UndefOpcode(ScalarReg) {
return ValueOpcode::UndefU32;
}
ValueOpcode UndefOpcode(ThreadBitScalarReg) {
return ValueOpcode::UndefU1;
}
ValueOpcode UndefOpcode(VectorReg) {
return ValueOpcode::UndefU32;
}
ValueOpcode UndefOpcode(GotoVariable) {
return ValueOpcode::UndefU1;
}
ValueOpcode UndefOpcode(SccTag) {
return ValueOpcode::UndefU1;
}
ValueOpcode UndefOpcode(ExecTag) {
return ValueOpcode::UndefU1;
}
ValueOpcode UndefOpcode(ExecLoTag) {
return ValueOpcode::UndefU32;
}
ValueOpcode UndefOpcode(ExecHiTag) {
return ValueOpcode::UndefU32;
}
ValueOpcode UndefOpcode(VccTag) {
return ValueOpcode::UndefU1;
}
ValueOpcode UndefOpcode(VccLoTag) {
return ValueOpcode::UndefU32;
}
ValueOpcode UndefOpcode(VccHiTag) {
return ValueOpcode::UndefU32;
}
ValueOpcode UndefOpcode(M0Tag) {
return ValueOpcode::UndefU32;
}
Value InitialValue(ScalarReg) {
return Value(0u);
}
Value InitialValue(ThreadBitScalarReg) {
return Value(false);
}
Value InitialValue(VectorReg) {
return Value(0u);
}
Value InitialValue(GotoVariable) {
return Value(false);
}
Value InitialValue(SccTag) {
return Value(false);
}
Value InitialValue(ExecTag) {
return Value(false);
}
Value InitialValue(ExecLoTag) {
return Value(0u);
}
Value InitialValue(ExecHiTag) {
return Value(0u);
}
Value InitialValue(VccTag) {
return Value(false);
}
Value InitialValue(VccLoTag) {
return Value(0u);
}
Value InitialValue(VccHiTag) {
return Value(0u);
}
Value InitialValue(M0Tag) {
return Value(0u);
}
enum class ReadStep { Start, SetValue, PushPhiArgument };
template <typename T>
struct ReadState {
Block* block = nullptr;
Value result;
Inst* phi = nullptr;
size_t pred = 0;
ReadStep step = ReadStep::Start;
};
class Pass {
public:
template <typename T>
void Write(T variable, Block* block, Value value) {
definitions.Set(block, variable, value);
}
template <typename T>
Value Read(T variable, Block* root) {
std::vector<ReadState<T>> stack {{}, {.block = root}};
const auto prepare_phi = [&]() {
auto& state = stack.back();
const auto predecessors = state.block->ImmPredecessors();
if (state.pred == predecessors.size()) {
const auto result = TryRemoveTrivialPhi(*state.phi, InitialValue(variable));
const auto block = state.block;
stack.pop_back();
stack.back().result = result;
Write(variable, block, result);
return;
}
state.step = ReadStep::PushPhiArgument;
stack.push_back({.block = predecessors[state.pred]});
};
do {
auto& state = stack.back();
auto* block = state.block;
switch (state.step) {
case ReadStep::Start: {
if (const auto& def = definitions.Get(block, variable); !def.IsEmpty()) {
state.result = def;
} else if (!block->IsSsaSealed()) {
auto& phi = *block->PrependNewInst(block->begin(), ValueOpcode::Phi);
phi.SetFlags(TypeOf(UndefOpcode(variable)));
incomplete_phis[block][Variable(variable)] = &phi;
state.result = Value(&phi);
} else if (const auto predecessors = block->ImmPredecessors();
predecessors.size() == 1) {
state.step = ReadStep::SetValue;
stack.push_back({.block = predecessors.front()});
break;
} else {
auto& phi = *block->PrependNewInst(block->begin(), ValueOpcode::Phi);
phi.SetFlags(TypeOf(UndefOpcode(variable)));
Write(variable, block, Value(&phi));
state.phi = &phi;
prepare_phi();
break;
}
[[fallthrough]];
}
case ReadStep::SetValue: {
const auto result = state.result;
Write(variable, block, result);
stack.pop_back();
stack.back().result = result;
break;
}
case ReadStep::PushPhiArgument: {
const auto predecessors = block->ImmPredecessors();
state.phi->AddPhiOperand(predecessors[state.pred], state.result);
state.pred++;
prepare_phi();
break;
}
}
} while (stack.size() > 1);
return stack.back().result;
}
void Seal(Block* block) {
if (const auto found = incomplete_phis.find(block); found != incomplete_phis.end()) {
for (auto& [variable, phi]: found->second) {
std::visit([&](auto value) { AddPhiOperands(value, *phi, block); }, variable);
}
}
block->SsaSeal();
}
private:
template <typename T>
Value AddPhiOperands(T variable, Inst& phi, Block* block) {
for (auto* predecessor: block->ImmPredecessors()) {
phi.AddPhiOperand(predecessor, Read(variable, predecessor));
}
return TryRemoveTrivialPhi(phi, InitialValue(variable));
}
Value TryRemoveTrivialPhi(Inst& phi, Value initial_value) {
Value same;
for (size_t index = 0; index < phi.NumArgs(); index++) {
const auto operand = phi.Arg(index).Resolve();
if (operand == same.Resolve() || operand == Value(&phi)) {
continue;
}
if (!same.IsEmpty()) {
return Value(&phi);
}
same = operand;
}
if (same.IsEmpty()) {
same = initial_value;
}
const auto users = phi.Uses();
phi.ReplaceUsesWith(same);
for (const auto& use: users) {
if (use.user->GetOpcode() == ValueOpcode::Phi) {
TryRemoveTrivialPhi(*use.user, initial_value);
}
}
return same;
}
std::unordered_map<Block*, std::map<Variable, Inst*>> incomplete_phis;
DefTable definitions;
};
void VisitInstruction(Pass& pass, Block* block, Inst& inst) {
switch (inst.GetOpcode()) {
case ValueOpcode::SetScalarRegister:
pass.Write(inst.Arg(0).ScalarRegister(), block, inst.Arg(1));
break;
case ValueOpcode::SetThreadBitScalarRegister:
pass.Write(ThreadBitScalarReg {inst.Arg(0).ScalarRegister()}, block, inst.Arg(1));
break;
case ValueOpcode::SetVectorRegister:
pass.Write(inst.Arg(0).VectorRegister(), block, inst.Arg(1));
break;
case ValueOpcode::SetGotoVariable:
pass.Write(GotoVariable {inst.Arg(0).U32()}, block, inst.Arg(1));
break;
case ValueOpcode::SetScc: pass.Write(SccTag {}, block, inst.Arg(0)); break;
case ValueOpcode::SetExec: pass.Write(ExecTag {}, block, inst.Arg(0)); break;
case ValueOpcode::SetExecLo: pass.Write(ExecLoTag {}, block, inst.Arg(0)); break;
case ValueOpcode::SetExecHi: pass.Write(ExecHiTag {}, block, inst.Arg(0)); break;
case ValueOpcode::SetVcc: pass.Write(VccTag {}, block, inst.Arg(0)); break;
case ValueOpcode::SetVccLo: pass.Write(VccLoTag {}, block, inst.Arg(0)); break;
case ValueOpcode::SetVccHi: pass.Write(VccHiTag {}, block, inst.Arg(0)); break;
case ValueOpcode::SetM0: pass.Write(M0Tag {}, block, inst.Arg(0)); break;
case ValueOpcode::GetScalarRegister:
inst.ReplaceUsesWith(pass.Read(inst.Arg(0).ScalarRegister(), block));
break;
case ValueOpcode::GetThreadBitScalarRegister:
inst.ReplaceUsesWith(
pass.Read(ThreadBitScalarReg {inst.Arg(0).ScalarRegister()}, block));
break;
case ValueOpcode::GetVectorRegister:
inst.ReplaceUsesWith(pass.Read(inst.Arg(0).VectorRegister(), block));
break;
case ValueOpcode::GetGotoVariable:
inst.ReplaceUsesWith(pass.Read(GotoVariable {inst.Arg(0).U32()}, block));
break;
case ValueOpcode::GetScc: inst.ReplaceUsesWith(pass.Read(SccTag {}, block)); break;
case ValueOpcode::GetExec: inst.ReplaceUsesWith(pass.Read(ExecTag {}, block)); break;
case ValueOpcode::GetExecLo: inst.ReplaceUsesWith(pass.Read(ExecLoTag {}, block)); break;
case ValueOpcode::GetExecHi: inst.ReplaceUsesWith(pass.Read(ExecHiTag {}, block)); break;
case ValueOpcode::GetVcc: inst.ReplaceUsesWith(pass.Read(VccTag {}, block)); break;
case ValueOpcode::GetVccLo: inst.ReplaceUsesWith(pass.Read(VccLoTag {}, block)); break;
case ValueOpcode::GetVccHi: inst.ReplaceUsesWith(pass.Read(VccHiTag {}, block)); break;
case ValueOpcode::GetM0: inst.ReplaceUsesWith(pass.Read(M0Tag {}, block)); break;
default: break;
}
}
} // namespace
void RewriteToSsa(const BlockList& blocks) {
Pass pass;
for (auto* block: blocks) {
for (auto& inst: *block) {
VisitInstruction(pass, block, inst);
}
}
for (auto* block: blocks) {
pass.Seal(block);
}
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -0,0 +1,9 @@
#pragma once
#include "graphics/shader/recompiler/ir/Block.h"
namespace Libs::Graphics::ShaderRecompiler::IR {
void RewriteToSsa(const BlockList& blocks);
} // namespace Libs::Graphics::ShaderRecompiler::IR