mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-21 19:36:55 +02:00
f14fb8c9df
Two descriptor chains this title uses could not be resolved: - A descriptor whose address arrives through a phi. Walk the phi to its leaves and accept the result when they agree, or when exactly one is a real address and the rest are null/sentinel. - A descriptor read through ReadFirstLane. This is safe for non-image descriptors, where every lane holds the same handle, so accept it there. Image descriptors keep the old restriction, gated by the dword count.
999 lines
31 KiB
C++
999 lines
31 KiB
C++
#include <atomic>
|
|
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
|
|
|
|
#include "common/assert.h"
|
|
#include "graphics/shader/recompiler/ir/ShaderIR.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;
|
|
}
|
|
|
|
void CollectPhiLeaves(Value value, std::vector<Value>& out) {
|
|
std::vector<Value> pending {value};
|
|
std::unordered_set<const Inst*> seen;
|
|
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 (!seen.insert(inst).second) {
|
|
continue;
|
|
}
|
|
for (size_t index = 0; index < inst->NumArgs(); index++) {
|
|
pending.push_back(inst->Arg(index));
|
|
}
|
|
continue;
|
|
}
|
|
if (current.IsImmediate() && current.TryInstruction() == nullptr &&
|
|
current.GetType() == Type::U32 && current.U32() == 0u) {
|
|
continue;
|
|
}
|
|
out.push_back(current);
|
|
}
|
|
}
|
|
|
|
bool IsRawRead(const Program& values, const Inst& inst) {
|
|
const auto op = inst.GetOpcode();
|
|
if (op != ValueOpcode::LoadAddressU32 && op != ValueOpcode::ReadConstBuffer) {
|
|
return false;
|
|
}
|
|
const auto index = inst.Flags<MemoryFlags>().index;
|
|
if (index >= values.memory_info.size()) {
|
|
return false;
|
|
}
|
|
const auto kind = values.memory_info[index].kind;
|
|
return (op == ValueOpcode::LoadAddressU32 && kind == ResourceKind::ScalarAddress) ||
|
|
(op == ValueOpcode::ReadConstBuffer && 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::CompositeConstructU64:
|
|
case ValueOpcode::CompositeExtractU64:
|
|
case ValueOpcode::CompositeConstructU32x2:
|
|
case ValueOpcode::CompositeExtractU32x2:
|
|
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::UMin32:
|
|
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::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, bool allow_read_first_lane)
|
|
: m_program(program), m_allow_read_first_lane(allow_read_first_lane) {}
|
|
|
|
bool Run(Value value) { return Validate(value); }
|
|
|
|
private:
|
|
bool Validate(Value value) {
|
|
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: return false;
|
|
}
|
|
}
|
|
if (!m_visiting.insert(inst).second) {
|
|
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) {
|
|
return finish(false);
|
|
}
|
|
if (op == ValueOpcode::GetUserData) {
|
|
if (inst->NumArgs() != 1 || inst->Arg(0).GetType() != Type::ScalarReg) {
|
|
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) {
|
|
return finish(false);
|
|
}
|
|
return finish(true);
|
|
}
|
|
if (op == ValueOpcode::GetShaderBase) {
|
|
if (inst->NumArgs() != 0) {
|
|
return finish(false);
|
|
}
|
|
return finish(true);
|
|
}
|
|
if (op == ValueOpcode::Phi) {
|
|
const auto invariant = ResolveInvariantPhi(m_program, value);
|
|
if (invariant.IsEmpty()) {
|
|
return finish(false);
|
|
}
|
|
return finish(Validate(invariant));
|
|
}
|
|
if (op == ValueOpcode::GetSrtResource) {
|
|
if (inst->NumArgs() != 0) {
|
|
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_program.srt_reads.size()) {
|
|
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_program, *inst) || handle == nullptr ||
|
|
handle->GetOpcode() != expected) {
|
|
return finish(false);
|
|
}
|
|
} else if (op == ValueOpcode::CompositeExtractU64) {
|
|
const auto index = inst->NumArgs() == 2 ? inst->Arg(1).Resolve() : Value {};
|
|
if (!index.IsImmediate() || index.GetType() != Type::U32 || index.U32() >= 2u) {
|
|
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::IAddCarry32)) {
|
|
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) {
|
|
return finish(false);
|
|
}
|
|
} else if (op != ValueOpcode::ReadConst && op != ValueOpcode::ReadConstBuffer &&
|
|
op != ValueOpcode::LoadAddressU32 && !IsRuntimeIntegerOp(op) &&
|
|
!(m_allow_read_first_lane && op == ValueOpcode::ReadFirstLane)) {
|
|
return finish(false);
|
|
}
|
|
for (size_t index = 0; index < inst->NumArgs(); index++) {
|
|
if (!Validate(inst->Arg(index))) {
|
|
return finish(false);
|
|
}
|
|
}
|
|
return finish(true);
|
|
}
|
|
|
|
const Program& m_program;
|
|
bool m_allow_read_first_lane = false;
|
|
std::unordered_set<const Inst*> m_visiting;
|
|
};
|
|
|
|
class PlanBuilder {
|
|
public:
|
|
explicit PlanBuilder(Program& program): m_program(program) {}
|
|
|
|
void Run() {
|
|
m_program.srt_reads.clear();
|
|
m_program.dynamic_reads.clear();
|
|
for (auto* block: m_program.blocks) {
|
|
for (auto& inst: *block) {
|
|
const auto op = inst.GetOpcode();
|
|
if (op == ValueOpcode::LoadAddressU32 || op == ValueOpcode::ReadConstBuffer) {
|
|
const auto flags = inst.Flags<MemoryFlags>();
|
|
if (flags.index < m_program.memory_info.size()) {
|
|
const auto kind = m_program.memory_info[flags.index].kind;
|
|
const bool crosswired = (op == ValueOpcode::LoadAddressU32 &&
|
|
kind == ResourceKind::ScalarBuffer) ||
|
|
(op == ValueOpcode::ReadConstBuffer &&
|
|
kind == ResourceKind::ScalarAddress);
|
|
if (crosswired) {
|
|
Fail(flags.pc,
|
|
fmt::format("{} has incompatible scalar memory metadata",
|
|
ValueOpcodeName(op)));
|
|
}
|
|
}
|
|
}
|
|
if (IsDescriptorHandle(inst.GetOpcode())) {
|
|
for (size_t index = 0; index < inst.NumArgs(); index++) {
|
|
Collect(inst.Arg(index), 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (auto* block: m_program.blocks) {
|
|
for (auto& inst: *block) {
|
|
if (inst.GetOpcode() == ValueOpcode::LoadAddressU32 && IsRawRead(m_program, inst) &&
|
|
inst.Arg(1).Resolve().IsImmediate() &&
|
|
ValidateRuntimeValue(m_program, Value(&inst))) {
|
|
Collect(Value(&inst), inst.Flags<MemoryFlags>().pc);
|
|
}
|
|
}
|
|
}
|
|
PatchReads();
|
|
}
|
|
|
|
private:
|
|
struct Patch {
|
|
Inst* inst = nullptr;
|
|
uint32_t slot = 0;
|
|
bool keep = false;
|
|
};
|
|
|
|
[[noreturn]] void Fail(uint32_t pc, const std::string& message) const {
|
|
const auto diagnostic = Diagnostic(m_program, pc, message);
|
|
EXIT("shader SRT planning failed: %s", diagnostic.c_str());
|
|
std::abort();
|
|
}
|
|
|
|
void Collect(Value value, uint32_t use_pc) {
|
|
value = value.Resolve();
|
|
if (value.IsImmediate()) {
|
|
return;
|
|
}
|
|
auto* inst = value.TryInstruction();
|
|
if (inst == nullptr) {
|
|
Fail(use_pc, "invalid typed planning value");
|
|
}
|
|
const auto cycle = std::ranges::find(m_visiting, inst);
|
|
if (cycle != m_visiting.end()) {
|
|
const auto contains_phi = std::any_of(cycle, m_visiting.end(), [](const Inst* value) {
|
|
return value->GetOpcode() == ValueOpcode::Phi;
|
|
});
|
|
if (contains_phi) {
|
|
return;
|
|
}
|
|
Fail(use_pc, fmt::format("cyclic typed planning value {} without a phi",
|
|
ValueOpcodeName(inst->GetOpcode())));
|
|
}
|
|
if (std::ranges::find(m_visited, inst) != m_visited.end()) {
|
|
return;
|
|
}
|
|
m_visiting.push_back(inst);
|
|
for (size_t index = 0; index < inst->NumArgs(); index++) {
|
|
Collect(inst->Arg(index), use_pc);
|
|
}
|
|
m_visiting.pop_back();
|
|
m_visited.push_back(inst);
|
|
if (!IsRawRead(m_program, *inst)) {
|
|
return;
|
|
}
|
|
const auto offset = inst->Arg(1).Resolve();
|
|
if (!offset.IsImmediate() || offset.GetType() != Type::U32) {
|
|
if (std::ranges::find(m_program.dynamic_reads, value) ==
|
|
m_program.dynamic_reads.end()) {
|
|
m_program.dynamic_reads.push_back(value);
|
|
}
|
|
return;
|
|
}
|
|
for (uint32_t slot = 0; slot < m_program.srt_reads.size(); slot++) {
|
|
if (EquivalentValue(m_program, value, m_program.srt_reads[slot].value)) {
|
|
m_patches.push_back({inst, slot, false});
|
|
return;
|
|
}
|
|
}
|
|
const auto slot = static_cast<uint32_t>(m_program.srt_reads.size());
|
|
m_program.srt_reads.push_back({value, slot});
|
|
m_patches.push_back({inst, slot, 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_program.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_program.memory_info.size()) {
|
|
m_program.memory_info[memory].planning_only = true;
|
|
}
|
|
block->AppendNewInst(ValueOpcode::ReferenceU32, {Value(patch.inst)});
|
|
}
|
|
}
|
|
}
|
|
|
|
Program& m_program;
|
|
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,
|
|
std::span<const uint8_t> clean_flat_slots = {}, Evaluator* clean_evaluator = nullptr)
|
|
: m_program(program), m_runtime(runtime), m_clean_flat_slots(clean_flat_slots),
|
|
m_clean_evaluator(clean_evaluator) {}
|
|
|
|
bool Evaluate(Value value, uint32_t& result) {
|
|
uint64_t wide = 0;
|
|
if (!EvaluateWide(value, wide)) {
|
|
return false;
|
|
}
|
|
result = static_cast<uint32_t>(wide);
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
bool EvaluateWide(Value value, uint64_t& result) {
|
|
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 false;
|
|
}
|
|
}
|
|
auto* inst = value.TryInstruction();
|
|
if (inst == nullptr) {
|
|
return false;
|
|
}
|
|
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 false;
|
|
}
|
|
m_visiting.push_back(inst);
|
|
uint64_t out = 0;
|
|
if (!EvaluateInst(*inst, out)) {
|
|
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) {
|
|
return EvaluateWide(inst.Arg(index), result);
|
|
}
|
|
|
|
bool EvaluatePhi(const Inst& inst, uint64_t& result) {
|
|
const auto value = ResolveInvariantPhi(m_program, Value(const_cast<Inst*>(&inst)));
|
|
if (!value.IsEmpty()) {
|
|
return EvaluateWide(value, result);
|
|
}
|
|
std::vector<Value> leaves;
|
|
CollectPhiLeaves(Value(const_cast<Inst*>(&inst)), leaves);
|
|
std::vector<uint64_t> values;
|
|
for (const auto& leaf: leaves) {
|
|
uint64_t leaf_value = 0;
|
|
if (EvaluateWide(leaf, leaf_value)) {
|
|
values.push_back(leaf_value);
|
|
}
|
|
}
|
|
if (values.empty()) {
|
|
return false;
|
|
}
|
|
bool all_same = true;
|
|
for (const auto v: values) {
|
|
all_same = all_same && v == values.front();
|
|
}
|
|
if (all_same) {
|
|
result = values.front();
|
|
return true;
|
|
}
|
|
uint64_t only_real = 0;
|
|
uint32_t real_count = 0;
|
|
for (const auto v: values) {
|
|
if (v != 0 && static_cast<uint32_t>(v) != 0xffffffffu) {
|
|
if (real_count == 0 || v != only_real) {
|
|
real_count++;
|
|
}
|
|
only_real = v;
|
|
}
|
|
}
|
|
if (real_count != 1u) {
|
|
return false;
|
|
}
|
|
result = only_real;
|
|
return true;
|
|
}
|
|
|
|
bool EvaluateExtract(const Inst& inst, uint64_t& result) {
|
|
const auto index = inst.Arg(1).Resolve();
|
|
if (!index.IsImmediate() || index.GetType() != Type::U32) {
|
|
return false;
|
|
}
|
|
const auto component = index.U32();
|
|
if (component >= 2u) {
|
|
return false;
|
|
}
|
|
if (inst.GetOpcode() == ValueOpcode::CompositeExtractU64) {
|
|
uint64_t packed = 0;
|
|
if (!Arg(inst, 0, packed)) {
|
|
return false;
|
|
}
|
|
result = static_cast<uint32_t>(packed >> (component * 32u));
|
|
return true;
|
|
}
|
|
const auto* source = inst.Arg(0).ResolveInstruction();
|
|
if (source == nullptr) {
|
|
return false;
|
|
}
|
|
if (source->GetOpcode() == ValueOpcode::CompositeConstructU32x2) {
|
|
return EvaluateWide(source->Arg(component), result);
|
|
}
|
|
if (source->GetOpcode() == ValueOpcode::IAddCarry32) {
|
|
uint64_t lhs = 0;
|
|
uint64_t rhs = 0;
|
|
if (!Arg(*source, 0, lhs) || !Arg(*source, 1, rhs)) {
|
|
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 false;
|
|
}
|
|
|
|
bool EvaluateRawRead(const Inst& inst, uint64_t& result) {
|
|
const auto flags = inst.Flags<MemoryFlags>();
|
|
if (flags.index >= m_program.memory_info.size()) {
|
|
return false;
|
|
}
|
|
const auto& mem = m_program.memory_info[flags.index];
|
|
const auto* handle = inst.Arg(0).ResolveInstruction();
|
|
if (handle == nullptr) {
|
|
return false;
|
|
}
|
|
uint64_t low = 0;
|
|
uint64_t high = 0;
|
|
uint64_t offset = 0;
|
|
if (!Arg(*handle, 0, low) || !Arg(*handle, 1, high) || !Arg(inst, 1, offset)) {
|
|
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) || !Arg(*handle, 3, word3)) {
|
|
return false;
|
|
}
|
|
if (immediate < 0) {
|
|
return false;
|
|
}
|
|
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 false;
|
|
}
|
|
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 false;
|
|
}
|
|
}
|
|
uint32_t word = 0;
|
|
if (m_runtime.read_memory != nullptr) {
|
|
if (!m_runtime.read_memory(m_runtime.userdata, address, &word)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
std::memcpy(&word, reinterpret_cast<const void*>(address), sizeof(word));
|
|
}
|
|
result = word;
|
|
return true;
|
|
}
|
|
|
|
bool EvaluateInst(const Inst& inst, uint64_t& result) {
|
|
uint64_t a = 0;
|
|
uint64_t b = 0;
|
|
uint64_t c = 0;
|
|
const auto binary = [&]() { return Arg(inst, 0, a) && Arg(inst, 1, b); };
|
|
const auto ternary = [&]() {
|
|
return Arg(inst, 0, a) && Arg(inst, 1, b) && Arg(inst, 2, c);
|
|
};
|
|
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 false;
|
|
}
|
|
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);
|
|
case ValueOpcode::CompositeExtractU64:
|
|
case ValueOpcode::CompositeExtractU32x2: return EvaluateExtract(inst, result);
|
|
case ValueOpcode::CompositeConstructU64:
|
|
if (!binary()) {
|
|
return false;
|
|
}
|
|
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_program.srt_reads.size()) {
|
|
return false;
|
|
}
|
|
if (slot.U32() < m_clean_flat_slots.size() &&
|
|
m_clean_flat_slots[slot.U32()] != 0u && m_clean_evaluator != nullptr) {
|
|
return m_clean_evaluator->EvaluateWide(m_program.srt_reads[slot.U32()].value,
|
|
result);
|
|
}
|
|
return EvaluateWide(m_program.srt_reads[slot.U32()].value, result);
|
|
}
|
|
case ValueOpcode::LoadAddressU32:
|
|
case ValueOpcode::ReadConstBuffer:
|
|
if (IsRawRead(m_program, inst)) {
|
|
return EvaluateRawRead(inst, result);
|
|
}
|
|
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::UMin32:
|
|
if (binary()) {
|
|
result = std::min(static_cast<uint32_t>(a), static_cast<uint32_t>(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::BitwiseXor32:
|
|
if (binary()) {
|
|
result = static_cast<uint32_t>(a ^ b);
|
|
return true;
|
|
}
|
|
return false;
|
|
case ValueOpcode::BitwiseNot32:
|
|
if (Arg(inst, 0, a)) {
|
|
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 false;
|
|
}
|
|
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 false;
|
|
}
|
|
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)) {
|
|
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 false;
|
|
}
|
|
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)) {
|
|
result = a == 0u;
|
|
return true;
|
|
}
|
|
return false;
|
|
case ValueOpcode::UndefU1:
|
|
case ValueOpcode::UndefU8:
|
|
case ValueOpcode::UndefU16:
|
|
case ValueOpcode::UndefU32:
|
|
case ValueOpcode::UndefU64: return false;
|
|
default: break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const Program& m_program;
|
|
const SrtRuntime& m_runtime;
|
|
std::span<const uint8_t> m_clean_flat_slots;
|
|
Evaluator* m_clean_evaluator = nullptr;
|
|
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 (source >= program.descriptor_sources.size()) {
|
|
return nullptr;
|
|
}
|
|
return &program.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::span<const uint8_t> clean_flat_slots) {
|
|
if (!program.srt_plan_complete) {
|
|
return false;
|
|
}
|
|
if (std::ranges::any_of(clean_flat_slots, [](uint8_t clean) { return clean != 0u; }) &&
|
|
runtime.read_specialization_memory == nullptr) {
|
|
return false;
|
|
}
|
|
SrtRuntime clean_runtime = runtime;
|
|
clean_runtime.read_memory = runtime.read_specialization_memory;
|
|
Evaluator clean_evaluator(program, clean_runtime);
|
|
Evaluator evaluator(program, runtime, clean_flat_slots, &clean_evaluator);
|
|
std::vector<DescriptorValue> evaluated;
|
|
evaluated.reserve(requests.size());
|
|
for (const auto& request: requests) {
|
|
const auto* source = Source(program, request.source);
|
|
if (source == nullptr) {
|
|
return false;
|
|
}
|
|
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])) {
|
|
return false;
|
|
}
|
|
}
|
|
evaluated.push_back(value);
|
|
}
|
|
std::vector<uint32_t> flattened;
|
|
if (evaluate_flat) {
|
|
flattened.resize(program.srt_reads.size());
|
|
for (const auto& read: program.srt_reads) {
|
|
const bool clean = read.flat_offset < clean_flat_slots.size() &&
|
|
clean_flat_slots[read.flat_offset] != 0u;
|
|
auto& selected = clean ? clean_evaluator : evaluator;
|
|
if (read.flat_offset >= flattened.size() ||
|
|
!selected.Evaluate(read.value, flattened[read.flat_offset])) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
results = std::move(evaluated);
|
|
if (evaluate_flat) {
|
|
flat = std::move(flattened);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool ValidateRuntimeValue(const Program& program, Value value, bool allow_read_first_lane) {
|
|
return RuntimeValidator(program, allow_read_first_lane).Run(value);
|
|
}
|
|
|
|
void BuildSrtPlan(Program& program) {
|
|
if (program.resource_tracking_complete) {
|
|
EXIT("shader SRT planning failed: cannot rebuild SRT after resource tracking");
|
|
}
|
|
program.srt_plan_complete = false;
|
|
PlanBuilder(program).Run();
|
|
program.srt_plan_complete = true;
|
|
}
|
|
|
|
bool EvaluateDescriptorSource(const Program& program, uint32_t source, const SrtRuntime& runtime,
|
|
DescriptorValue& result) {
|
|
const DescriptorSourceRequest request {source};
|
|
std::vector<DescriptorValue> results;
|
|
if (!EvaluateDescriptorSources(program, std::span {&request, 1}, runtime, results)) {
|
|
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::vector<uint32_t> ignored;
|
|
return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, ignored, false, {});
|
|
}
|
|
|
|
bool EvaluateRuntimeSources(const Program& program,
|
|
std::span<const DescriptorSourceRequest> requests,
|
|
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
|
std::vector<uint32_t>& flat,
|
|
std::span<const uint8_t> clean_flat_slots) {
|
|
return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, flat, true,
|
|
clean_flat_slots);
|
|
}
|
|
|
|
bool WalkSrt(const Program& program, const SrtRuntime& runtime, std::vector<uint32_t>& flat) {
|
|
std::vector<DescriptorValue> ignored;
|
|
return EvaluateRuntimeSources(program, {}, runtime, ignored, flat, {});
|
|
}
|
|
|
|
} // namespace Libs::Graphics::ShaderRecompiler::IR
|