mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-21 19:36:55 +02:00
16a7fe689a
* fix windows compialtion (2) * loader: guard LoadExec close_range path with __linux__ for macOS --------- Co-authored-by: ReverendOzmund <catmilker@proton.me>
1154 lines
38 KiB
C++
1154 lines
38 KiB
C++
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
|
|
|
|
#include "common/abi.h"
|
|
#include "common/assert.h"
|
|
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
|
|
|
#include <xbyak/xbyak.h>
|
|
|
|
#include <algorithm>
|
|
#include <bit>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <map>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
|
|
namespace Libs::Graphics::ShaderRecompiler::IR {
|
|
|
|
SrtRuntime SrtReadCapture::ObservedRuntime() {
|
|
auto runtime = m_source;
|
|
runtime.userdata = this;
|
|
runtime.read_specialization_memory = m_source.read_specialization_memory != nullptr
|
|
? ReadStrict : nullptr;
|
|
runtime.read_memory = ReadOrdinary;
|
|
return runtime;
|
|
}
|
|
|
|
bool SrtReadCapture::ReadStrict(void* userdata, uint64_t address, std::span<uint32_t> values) {
|
|
auto& capture = *static_cast<SrtReadCapture*>(userdata);
|
|
if (!capture.m_source.read_specialization_memory(capture.m_source.userdata, address, values)) {
|
|
return false;
|
|
}
|
|
capture.m_ranges.emplace_back(address, values.size_bytes());
|
|
return true;
|
|
}
|
|
|
|
bool SrtReadCapture::ReadOrdinary(void* userdata, uint64_t address, std::span<uint32_t> values) {
|
|
auto& capture = *static_cast<SrtReadCapture*>(userdata);
|
|
if (capture.m_source.read_memory != nullptr) {
|
|
if (!capture.m_source.read_memory(capture.m_source.userdata, address, values)) return false;
|
|
} else {
|
|
std::memcpy(values.data(), reinterpret_cast<const void*>(address), values.size_bytes());
|
|
}
|
|
capture.m_ranges.emplace_back(address, values.size_bytes());
|
|
return true;
|
|
}
|
|
|
|
SrtRuntime CleanRuntime(SrtRuntime runtime) {
|
|
runtime.read_memory = runtime.read_specialization_memory != nullptr
|
|
? runtime.read_specialization_memory
|
|
: +[](void*, uint64_t, std::span<uint32_t>) { return false; };
|
|
return runtime;
|
|
}
|
|
|
|
namespace {
|
|
|
|
constexpr uint64_t AddressMask = 0x0000ffffffffffffull;
|
|
|
|
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 ResourcePlan& values, const Inst& inst) {
|
|
const auto op = inst.GetOpcode();
|
|
if (op != ValueOpcode::LoadAddressU32 && op != ValueOpcode::ReadConstBuffer &&
|
|
op != ValueOpcode::LoadBufferU32) {
|
|
return false;
|
|
}
|
|
const auto index = inst.Flags<MemoryFlags>().index;
|
|
if (index >= values.memory_info.size()) {
|
|
return false;
|
|
}
|
|
const auto& memory = values.memory_info[index];
|
|
if (op != ValueOpcode::LoadBufferU32) {
|
|
return (op == ValueOpcode::LoadAddressU32 && memory.kind == ResourceKind::ScalarAddress) ||
|
|
(op == ValueOpcode::ReadConstBuffer && memory.kind == ResourceKind::ScalarBuffer);
|
|
}
|
|
if (inst.NumArgs() != 5u || memory.kind != ResourceKind::Buffer || memory.typed ||
|
|
memory.formatted || memory.coherent || memory.data_bits != 32u ||
|
|
memory.data_dwords != 1u || memory.idxen || memory.offen || memory.offset != 0u ||
|
|
inst.Arg(4).GetType() != Type::U1) {
|
|
return false;
|
|
}
|
|
for (uint32_t arg = 1; arg <= 3; ++arg) {
|
|
if (inst.Arg(arg).Resolve() != Value(0u)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IsDescriptorHandle(ValueOpcode opcode) {
|
|
switch (opcode) {
|
|
case ValueOpcode::GetBufferResource:
|
|
case ValueOpcode::GetAddressResource:
|
|
case ValueOpcode::GetImageResource:
|
|
case ValueOpcode::GetSamplerResource: return true;
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
bool IsRuntimeSelect(ValueOpcode op) {
|
|
return op == ValueOpcode::SelectU1 || op == ValueOpcode::SelectU32 ||
|
|
op == ValueOpcode::SelectF32;
|
|
}
|
|
|
|
bool IsRuntimeUniformOp(ValueOpcode op) {
|
|
switch (op) {
|
|
case ValueOpcode::ConditionRef:
|
|
case ValueOpcode::BitCastU32F32:
|
|
case ValueOpcode::BitCastF32U32:
|
|
case ValueOpcode::ConvertU32F32:
|
|
case ValueOpcode::ConvertF32U32:
|
|
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::UMulHi:
|
|
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::SelectF32:
|
|
case ValueOpcode::ULessThan32:
|
|
case ValueOpcode::ULessThanEqual32:
|
|
case ValueOpcode::IEqual32:
|
|
case ValueOpcode::UGreaterThan32:
|
|
case ValueOpcode::SGreaterThanEqual32:
|
|
case ValueOpcode::INotEqual32:
|
|
case ValueOpcode::LogicalOr:
|
|
case ValueOpcode::LogicalAnd:
|
|
case ValueOpcode::LogicalXor:
|
|
case ValueOpcode::LogicalNot:
|
|
case ValueOpcode::FPOrdLessThanEqual32:
|
|
case ValueOpcode::FPOrdGreaterThanEqual32:
|
|
case ValueOpcode::FPIsNan32:
|
|
case ValueOpcode::FPMul32:
|
|
case ValueOpcode::FPRecipIFlag32:
|
|
case ValueOpcode::FPTrunc32: return true;
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
class RuntimeValidator {
|
|
public:
|
|
explicit RuntimeValidator(const ResourcePlan& program, RuntimeValueType type)
|
|
: m_program(program), m_type(type) {}
|
|
|
|
bool Run(Value value) { return Validate(value); }
|
|
|
|
private:
|
|
bool ValidateArguments(const Inst& inst, bool require_uniform) {
|
|
for (size_t index = 0; index < inst.NumArgs(); index++) {
|
|
if (!Validate(inst.Arg(index), require_uniform)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Validate(Value value, bool require_uniform = true) {
|
|
value = value.Resolve();
|
|
// Host floating-point evaluation does not model shader rounding/denormal modes.
|
|
if (m_type != RuntimeValueType::Any &&
|
|
TypesOverlap(value.GetType(), Type::F16 | Type::F32 | Type::F32x2)) {
|
|
return false;
|
|
}
|
|
const auto* inst = value.TryInstruction();
|
|
if (inst == nullptr) {
|
|
if (!require_uniform) return true;
|
|
switch (value.GetType()) {
|
|
case Type::U1:
|
|
case Type::U8:
|
|
case Type::U16:
|
|
case Type::U32:
|
|
case Type::U64:
|
|
case Type::F32: return true;
|
|
default: return false;
|
|
}
|
|
}
|
|
if (require_uniform && !m_active_mask.IsEmpty() && value == m_active_mask) return true;
|
|
// Integer-only dependency checks do not depend on the active EXEC mask.
|
|
if (!require_uniform && m_validated_dependencies.contains(inst)) return true;
|
|
if (!m_visiting.insert(inst).second) {
|
|
return !require_uniform;
|
|
}
|
|
const auto finish = [&](bool valid) {
|
|
m_visiting.erase(inst);
|
|
if (valid && !require_uniform) m_validated_dependencies.insert(inst);
|
|
return valid;
|
|
};
|
|
const auto op = inst->GetOpcode();
|
|
if (m_type == RuntimeValueType::ImmutableInteger &&
|
|
(op == ValueOpcode::ReadConst || BufferAccessOf(op) != BufferAccess::None ||
|
|
AddressOpcodeInfoOf(op).access != AddressAccess::None ||
|
|
ImageOpcodeInfoOf(op).access != ImageAccess::None ||
|
|
SharedAccessOf(op) != SharedAccess::None)) {
|
|
return finish(false);
|
|
}
|
|
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);
|
|
}
|
|
if (m_type != RuntimeValueType::Any) {
|
|
const auto active_mask = m_active_mask;
|
|
m_active_mask = {};
|
|
const bool valid = Validate(m_program.srt_reads[slot.U32()].value);
|
|
m_active_mask = active_mask;
|
|
if (!valid) return finish(false);
|
|
}
|
|
}
|
|
if (!require_uniform) return finish(ValidateArguments(*inst, false));
|
|
if (!m_active_mask.IsEmpty() && IsRuntimeSelect(op) && inst->NumArgs() == 3 &&
|
|
inst->Arg(0).Resolve() == m_active_mask) {
|
|
// Empty EXEC reads lane zero, so ignored operands still require integer types.
|
|
if (m_type != RuntimeValueType::Any && !Validate(inst->Arg(2), false)) {
|
|
return finish(false);
|
|
}
|
|
return finish(Validate(inst->Arg(1)));
|
|
}
|
|
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) {
|
|
if (m_type != RuntimeValueType::Any && !ValidateArguments(*inst, false)) {
|
|
return finish(false);
|
|
}
|
|
const auto invariant = ResolveInvariantPhi(m_program, value);
|
|
if (invariant.IsEmpty()) {
|
|
return finish(false);
|
|
}
|
|
return finish(Validate(invariant));
|
|
}
|
|
if (op == ValueOpcode::ReadFirstLane) {
|
|
if (inst->NumArgs() != 2 || inst->Arg(0).GetType() != Type::U32 ||
|
|
inst->Arg(1).GetType() != Type::U1) {
|
|
return finish(false);
|
|
}
|
|
if (m_type != RuntimeValueType::Any && !Validate(inst->Arg(1), false)) {
|
|
return finish(false);
|
|
}
|
|
const auto active_mask = m_active_mask;
|
|
m_active_mask = inst->Arg(1).Resolve();
|
|
const bool valid = Validate(inst->Arg(0));
|
|
m_active_mask = active_mask;
|
|
return finish(valid);
|
|
}
|
|
if (op == ValueOpcode::GetSrtResource) {
|
|
if (inst->NumArgs() != 0) {
|
|
return finish(false);
|
|
}
|
|
return finish(true);
|
|
}
|
|
if (op == ValueOpcode::LoadAddressU32 || op == ValueOpcode::ReadConstBuffer ||
|
|
op == ValueOpcode::LoadBufferU32) {
|
|
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);
|
|
}
|
|
if (op == ValueOpcode::LoadBufferU32) {
|
|
const auto guard = inst->Arg(4).Resolve();
|
|
return finish(Validate(inst->Arg(0)) &&
|
|
((!m_active_mask.IsEmpty() && guard == m_active_mask) || Validate(guard)));
|
|
}
|
|
} 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 (IsDescriptorHandle(op)) {
|
|
size_t expected = 4u;
|
|
if (op == ValueOpcode::GetImageResource) {
|
|
expected = 8u;
|
|
} else if (op == ValueOpcode::GetAddressResource) {
|
|
expected = 2u;
|
|
}
|
|
if (inst->NumArgs() != expected) {
|
|
return finish(false);
|
|
}
|
|
} else if (op != ValueOpcode::ReadConst && op != ValueOpcode::ReadConstBuffer &&
|
|
op != ValueOpcode::LoadAddressU32 && !IsRuntimeUniformOp(op)) {
|
|
return finish(false);
|
|
}
|
|
return finish(ValidateArguments(*inst, true));
|
|
}
|
|
|
|
const ResourcePlan& m_program;
|
|
RuntimeValueType m_type;
|
|
Value m_active_mask;
|
|
std::unordered_set<const Inst*> m_visiting;
|
|
std::unordered_set<const Inst*> m_validated_dependencies;
|
|
};
|
|
|
|
|
|
} // namespace
|
|
|
|
void CompileSrtWalker(ResourcePlan& program) {
|
|
if (!program.resource_tracking_complete || !program.srt_plan_complete ||
|
|
program.capture_specialization_reads || !program.control_flow.empty() ||
|
|
program.srt_reads.empty()) return;
|
|
|
|
using Reads = std::map<int32_t, const SrtRead*>;
|
|
std::map<uint32_t, Reads> roots;
|
|
std::unordered_map<const Inst*, Reads> pointer_uses;
|
|
std::unordered_map<const Inst*, int32_t> offsets;
|
|
for (const auto& read: program.srt_reads) {
|
|
const auto* inst = read.value.Resolve().TryInstruction();
|
|
if (inst == nullptr || inst->GetOpcode() != ValueOpcode::LoadAddressU32 ||
|
|
inst->NumArgs() < 2 || !IsRawRead(program, *inst) ||
|
|
read.flat_offset >= program.srt_reads.size()) return;
|
|
const auto offset = inst->Arg(1).Resolve();
|
|
if (!offset.IsImmediate() || offset.GetType() != Type::U32) return;
|
|
const auto& memory = program.memory_info[inst->Flags<SrtReadFlags>().index];
|
|
const auto relative =
|
|
(static_cast<int64_t>(static_cast<int32_t>(memory.offset)) & ~int64_t {3}) +
|
|
static_cast<int64_t>(offset.U32() & ~3u);
|
|
if (relative < INT32_MIN || relative > INT32_MAX ||
|
|
!offsets.emplace(inst, static_cast<int32_t>(relative)).second) return;
|
|
}
|
|
uint32_t user_data_count = 0;
|
|
for (const auto& read: program.srt_reads) {
|
|
const auto* inst = read.value.ResolveInstruction();
|
|
const auto* handle = inst->Arg(0).Resolve().TryInstruction();
|
|
if (handle == nullptr || handle->GetOpcode() != ValueOpcode::GetAddressResource ||
|
|
handle->NumArgs() != 2) return;
|
|
const auto* low = handle->Arg(0).Resolve().TryInstruction();
|
|
const auto* high = handle->Arg(1).Resolve().TryInstruction();
|
|
if (low == nullptr || high == nullptr) return;
|
|
Reads* uses = nullptr;
|
|
if (low->GetOpcode() == ValueOpcode::GetUserData &&
|
|
high->GetOpcode() == ValueOpcode::GetUserData && low->NumArgs() == 1 &&
|
|
high->NumArgs() == 1 && low->Arg(0).GetType() == Type::ScalarReg &&
|
|
high->Arg(0).GetType() == Type::ScalarReg) {
|
|
const auto reg = RegIndex(low->Arg(0).ScalarRegister());
|
|
if (reg < program.user_data_base ||
|
|
RegIndex(high->Arg(0).ScalarRegister()) != reg + 1u) return;
|
|
const auto base = reg - program.user_data_base;
|
|
user_data_count = std::max(user_data_count, base + 2u);
|
|
uses = &roots[base];
|
|
} else {
|
|
const auto lo = offsets.find(low);
|
|
const auto hi = offsets.find(high);
|
|
if (lo == offsets.end() || hi == offsets.end() ||
|
|
static_cast<int64_t>(lo->second) + 4 != hi->second ||
|
|
!EquivalentValue(program, low->Arg(0), high->Arg(0))) return;
|
|
uses = &pointer_uses[low];
|
|
}
|
|
if (!uses->emplace(offsets.at(inst), &read).second) return;
|
|
}
|
|
std::unordered_set<const Inst*> visited;
|
|
const auto reachable = [&](auto&& self, const Reads& reads) -> bool {
|
|
for (const auto& [offset, read]: reads) {
|
|
const auto* inst = read->value.ResolveInstruction();
|
|
const auto child = pointer_uses.find(inst);
|
|
if (child != pointer_uses.end() &&
|
|
(!visited.insert(inst).second || !self(self, child->second))) return false;
|
|
}
|
|
return true;
|
|
};
|
|
for (const auto& [reg, reads]: roots) {
|
|
if (!reachable(reachable, reads)) return;
|
|
}
|
|
if (visited.size() != pointer_uses.size()) return;
|
|
|
|
program.srt_code = std::make_unique<Xbyak::CodeGenerator>(4096, Xbyak::AutoGrow);
|
|
auto& code = *program.srt_code;
|
|
Xbyak::Label failed, finished;
|
|
code.push(code.rbp);
|
|
code.mov(code.rbp, code.rsp);
|
|
code.cmp(code.r8, user_data_count);
|
|
code.jb(failed, Xbyak::CodeGenerator::T_NEAR);
|
|
code.mov(code.r9, AddressMask & ~uint64_t {3});
|
|
// Follow shadPS4's VisitPointer: copy one table level in offset order, then
|
|
// descend through its child pointers. Seed the descriptor evaluator's memo.
|
|
const auto visit = [&](auto&& self, int32_t pointer_offset, const Reads& reads) -> void {
|
|
code.push(code.rdi);
|
|
code.mov(code.rdi, code.qword[code.rdi + pointer_offset]);
|
|
code.and_(code.rdi, code.r9);
|
|
const auto first = reads.begin()->first;
|
|
const auto last = reads.rbegin()->first;
|
|
if (first < 0) {
|
|
code.mov(code.r10, -static_cast<int64_t>(first));
|
|
code.cmp(code.rdi, code.r10);
|
|
code.jb(failed, Xbyak::CodeGenerator::T_NEAR);
|
|
}
|
|
if (last > 0) {
|
|
code.mov(code.r10, AddressMask - last);
|
|
code.cmp(code.rdi, code.r10);
|
|
code.ja(failed, Xbyak::CodeGenerator::T_NEAR);
|
|
}
|
|
for (const auto& [offset, read]: reads) {
|
|
const auto index = read->value.ResolveInstruction()->EvaluationIndex(program.evaluation_value_count);
|
|
using Entry = ResourcePlan::EvaluationContext::Entry;
|
|
const auto slot = code.rdx + index * sizeof(Entry);
|
|
code.mov(code.r10d, code.dword[code.rdi + offset]);
|
|
code.mov(code.dword[code.rsi + read->flat_offset * sizeof(uint32_t)], code.r10d);
|
|
code.mov(code.qword[slot + offsetof(Entry, value)], code.r10);
|
|
code.mov(code.qword[slot + offsetof(Entry, generation)], code.rcx);
|
|
}
|
|
for (const auto& [offset, read]: reads) {
|
|
const auto child = pointer_uses.find(read->value.ResolveInstruction());
|
|
if (child == pointer_uses.end()) continue;
|
|
self(self, offset, child->second);
|
|
}
|
|
code.pop(code.rdi);
|
|
};
|
|
for (const auto& [reg, reads]: roots) {
|
|
visit(visit, reg * sizeof(uint32_t), reads);
|
|
}
|
|
code.mov(code.eax, 1);
|
|
code.jmp(finished, Xbyak::CodeGenerator::T_NEAR);
|
|
code.L(failed);
|
|
code.xor_(code.eax, code.eax);
|
|
code.L(finished);
|
|
code.mov(code.rsp, code.rbp);
|
|
code.pop(code.rbp);
|
|
code.ret();
|
|
code.readyRE();
|
|
EXIT_IF(Xbyak::GetError() != 0);
|
|
}
|
|
|
|
SrtWalker::SrtWalker(const ResourcePlan& program, const SrtRuntime& runtime,
|
|
SrtWalker* clean_evaluator, Value active_mask)
|
|
: m_program(program), m_runtime(runtime), m_clean_evaluator(clean_evaluator),
|
|
m_active_mask(active_mask.Resolve()),
|
|
m_context(AcquireContext(program)) {}
|
|
|
|
SrtWalker::~SrtWalker() { --m_program.evaluation_depth; }
|
|
|
|
bool SrtWalker::Evaluate(Value value, uint32_t& result) {
|
|
uint64_t wide = 0;
|
|
if (!EvaluateWide(value, wide)) {
|
|
return false;
|
|
}
|
|
result = static_cast<uint32_t>(wide);
|
|
return true;
|
|
}
|
|
|
|
ResourcePlan::EvaluationContext& SrtWalker::AcquireContext(const ResourcePlan& program) {
|
|
if (program.evaluation_depth == program.evaluation_contexts.size()) {
|
|
program.evaluation_contexts.emplace_back();
|
|
}
|
|
auto& context = program.evaluation_contexts[program.evaluation_depth++];
|
|
context.generation += 2;
|
|
return context;
|
|
}
|
|
|
|
float SrtWalker::Float32(uint64_t bits) {
|
|
return std::bit_cast<float>(static_cast<uint32_t>(bits));
|
|
}
|
|
|
|
bool SrtWalker::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;
|
|
case Type::F32: result = std::bit_cast<uint32_t>(value.F32Value()); return true;
|
|
default: return false;
|
|
}
|
|
}
|
|
if (!m_active_mask.IsEmpty() && value == m_active_mask) {
|
|
result = 1u;
|
|
return true;
|
|
}
|
|
auto* inst = value.TryInstruction();
|
|
if (inst == nullptr) {
|
|
return false;
|
|
}
|
|
if (!m_active_mask.IsEmpty() && IsRuntimeSelect(inst->GetOpcode()) &&
|
|
inst->NumArgs() == 3 && inst->Arg(0).Resolve() == m_active_mask) {
|
|
return EvaluateWide(inst->Arg(1), result);
|
|
}
|
|
const auto index = inst->EvaluationIndex(m_program.evaluation_value_count);
|
|
if (index >= m_context.values.size()) {
|
|
m_context.values.resize(m_program.evaluation_value_count);
|
|
}
|
|
if (m_context.values[index].generation == m_context.generation) {
|
|
result = m_context.values[index].value;
|
|
return true;
|
|
}
|
|
// The low generation bit marks an instruction that is still being evaluated.
|
|
if (m_context.values[index].generation == (m_context.generation | 1u)) {
|
|
return false;
|
|
}
|
|
m_context.values[index].generation = m_context.generation | 1u;
|
|
uint64_t out = 0;
|
|
const bool evaluated = EvaluateInst(*inst, out);
|
|
// Recursive evaluation may grow the dense memo vector.
|
|
auto& memo = m_context.values[index];
|
|
if (!evaluated) {
|
|
memo.generation = 0;
|
|
return false;
|
|
}
|
|
memo.value = out;
|
|
memo.generation = m_context.generation;
|
|
result = out;
|
|
return true;
|
|
}
|
|
|
|
bool SrtWalker::Arg(const Inst& inst, size_t index, uint64_t& result) {
|
|
return EvaluateWide(inst.Arg(index), result);
|
|
}
|
|
|
|
bool SrtWalker::EvaluatePhi(const Inst& inst, uint64_t& result) {
|
|
const auto value = ResolveInvariantPhi(m_program, Value(const_cast<Inst*>(&inst)));
|
|
return !value.IsEmpty() && EvaluateWide(value, result);
|
|
}
|
|
|
|
bool SrtWalker::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 SrtWalker::EvaluateRawRead(const Inst& inst, uint64_t& result) {
|
|
const auto flags = inst.Flags<SrtReadFlags>();
|
|
if (flags.index >= m_program.memory_info.size()) {
|
|
return false;
|
|
}
|
|
const auto& mem = m_program.memory_info[flags.index];
|
|
const bool vector = inst.GetOpcode() == ValueOpcode::LoadBufferU32;
|
|
const auto guard = vector ? inst.Arg(4).Resolve() : Value {};
|
|
if (vector && (guard.IsImmediate() || guard != m_active_mask)) {
|
|
uint64_t enabled = 0;
|
|
if (!Arg(inst, 4, enabled)) return false;
|
|
if (enabled == 0u) {
|
|
result = 0u;
|
|
return true;
|
|
}
|
|
}
|
|
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 || vector) {
|
|
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) & ~uint64_t {3}) + (static_cast<uint32_t>(offset) & ~3u);
|
|
const auto stride = (static_cast<uint32_t>(high) >> 16u) & 0x3fffu;
|
|
if (vector) {
|
|
// Only the uniform, unswizzled structured DWORD address is evaluated on the host.
|
|
if ((high & (1u << 31u)) != 0u || (word3 & ((1u << 23u) | 0xf0000000u)) != 0u)
|
|
return false;
|
|
if (stride == 0u || records == 0u || ((word3 >> 12u) & 0x7fu) == 0u) {
|
|
result = 0u;
|
|
return true;
|
|
}
|
|
}
|
|
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 (byte_offset > size || size - byte_offset < sizeof(uint32_t)) {
|
|
return false;
|
|
}
|
|
address = (base & ~uint64_t {3}) + byte_offset;
|
|
} 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;
|
|
const auto reader = vector ? m_runtime.read_specialization_memory : m_runtime.read_memory;
|
|
if (reader != nullptr) {
|
|
if (!reader(m_runtime.userdata, address, {&word, 1})) {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (vector) return false;
|
|
std::memcpy(&word, reinterpret_cast<const void*>(address), sizeof(word));
|
|
}
|
|
result = word;
|
|
return true;
|
|
}
|
|
|
|
bool SrtWalker::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::ReadFirstLane: {
|
|
if (!m_active_mask.IsEmpty() && inst.Arg(1).Resolve() == m_active_mask) {
|
|
return EvaluateWide(inst.Arg(0), result);
|
|
}
|
|
const auto clean_runtime = CleanRuntime(m_runtime);
|
|
SrtWalker clean_active(m_program, clean_runtime, nullptr, inst.Arg(1));
|
|
SrtWalker active(m_program, m_runtime, &clean_active, inst.Arg(1));
|
|
return active.EvaluateWide(inst.Arg(0), result);
|
|
}
|
|
case ValueOpcode::BitCastU32F32:
|
|
case ValueOpcode::BitCastF32U32: return Arg(inst, 0, 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::LoadAddressU32:
|
|
case ValueOpcode::ReadConstBuffer:
|
|
case ValueOpcode::LoadBufferU32:
|
|
if (IsRawRead(m_program, inst)) {
|
|
if (m_clean_evaluator != nullptr &&
|
|
(inst.Flags<SrtReadFlags>().clean != 0u ||
|
|
(inst.GetOpcode() == ValueOpcode::LoadBufferU32 &&
|
|
m_clean_evaluator->m_active_mask == m_active_mask))) {
|
|
return m_clean_evaluator->EvaluateWide(Value(const_cast<Inst*>(&inst)), result);
|
|
}
|
|
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::UMulHi:
|
|
if (binary()) {
|
|
result = (static_cast<uint64_t>(static_cast<uint32_t>(a)) * static_cast<uint32_t>(b)) >> 32u;
|
|
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::ConvertF32U32:
|
|
if (Arg(inst, 0, a)) {
|
|
result = std::bit_cast<uint32_t>(static_cast<float>(static_cast<uint32_t>(a)));
|
|
return true;
|
|
}
|
|
return false;
|
|
case ValueOpcode::ConvertU32F32:
|
|
if (Arg(inst, 0, a)) {
|
|
const auto value = Float32(a);
|
|
if (!std::isfinite(value) || value < 0.0f ||
|
|
static_cast<double>(value) > UINT32_MAX) {
|
|
return false;
|
|
}
|
|
result = static_cast<uint32_t>(value);
|
|
return true;
|
|
}
|
|
return false;
|
|
case ValueOpcode::FPMul32:
|
|
if (binary()) {
|
|
result = std::bit_cast<uint32_t>(Float32(a) * Float32(b));
|
|
return true;
|
|
}
|
|
return false;
|
|
case ValueOpcode::FPTrunc32:
|
|
if (Arg(inst, 0, a)) {
|
|
result = std::bit_cast<uint32_t>(std::trunc(Float32(a)));
|
|
return true;
|
|
}
|
|
return false;
|
|
case ValueOpcode::FPRecipIFlag32:
|
|
if (Arg(inst, 0, a)) {
|
|
const auto exponent = (a >> 23u) & 0xffu;
|
|
// Normal positive powers of two have exact normal reciprocals in every FP mode.
|
|
if ((a & 0x807fffffu) != 0u || exponent == 0u || exponent >= 254u) return false;
|
|
result = (254u - exponent) << 23u;
|
|
return true;
|
|
}
|
|
return false;
|
|
case ValueOpcode::FPIsNan32:
|
|
if (Arg(inst, 0, a)) {
|
|
result = std::isnan(Float32(a));
|
|
return true;
|
|
}
|
|
return false;
|
|
case ValueOpcode::FPOrdLessThanEqual32:
|
|
case ValueOpcode::FPOrdGreaterThanEqual32:
|
|
if (binary()) {
|
|
const auto operand = [&](uint64_t bits) {
|
|
if (inst.Flags<FPCompareFlags>().flush_input_denorms &&
|
|
(bits & 0x7fffffffu) < 0x00800000u) {
|
|
bits &= 0x80000000u;
|
|
}
|
|
return Float32(bits);
|
|
};
|
|
result = inst.GetOpcode() == ValueOpcode::FPOrdLessThanEqual32
|
|
? operand(a) <= operand(b)
|
|
: operand(a) >= operand(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:
|
|
case ValueOpcode::SelectF32: {
|
|
auto& predicate = m_clean_evaluator != nullptr ? *m_clean_evaluator : *this;
|
|
if (predicate.EvaluateWide(inst.Arg(0), a)) {
|
|
return Arg(inst, a != 0u ? 1u : 2u, result);
|
|
}
|
|
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::ULessThanEqual32:
|
|
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::SGreaterThanEqual32:
|
|
if (binary()) {
|
|
result = std::bit_cast<int32_t>(static_cast<uint32_t>(a)) >=
|
|
std::bit_cast<int32_t>(static_cast<uint32_t>(b));
|
|
return true;
|
|
}
|
|
return false;
|
|
case ValueOpcode::LogicalAnd: {
|
|
const bool left = Arg(inst, 0, a);
|
|
if (left && a == 0u) {
|
|
result = 0u;
|
|
return true;
|
|
}
|
|
if (!Arg(inst, 1, b) || (b != 0u && !left)) return false;
|
|
result = b != 0u;
|
|
return true;
|
|
}
|
|
case ValueOpcode::LogicalOr: {
|
|
const bool left = Arg(inst, 0, a);
|
|
if (left && a != 0u) {
|
|
result = 1u;
|
|
return true;
|
|
}
|
|
if (!Arg(inst, 1, b) || (b == 0u && !left)) return false;
|
|
result = b != 0u;
|
|
return true;
|
|
}
|
|
case ValueOpcode::LogicalXor:
|
|
if (binary()) {
|
|
result = (a != 0u) != (b != 0u);
|
|
return true;
|
|
}
|
|
return false;
|
|
case ValueOpcode::ConditionRef: return Arg(inst, 0, result);
|
|
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;
|
|
}
|
|
bool SrtWalker::EvaluateDescriptor(uint32_t source, DescriptorValue& result) {
|
|
if (source >= m_program.descriptor_sources.size()) {
|
|
return false;
|
|
}
|
|
const auto& descriptor = m_program.descriptor_sources[source];
|
|
result = {};
|
|
result.dword_count = descriptor.dword_count;
|
|
for (uint32_t index = 0; index < descriptor.dword_count; ++index) {
|
|
if (!Evaluate(descriptor.dwords[index], result.dwords[index])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool SrtWalker::RefreshFlatBuffer(std::vector<uint32_t>& flat) {
|
|
if (!m_program.srt_plan_complete) return false;
|
|
if (m_program.srt_code && m_runtime.read_memory == nullptr) {
|
|
flat.resize(m_program.srt_reads.size());
|
|
m_program.active_sources.clear();
|
|
m_context.values.resize(m_program.evaluation_value_count);
|
|
using Walker = bool KYTY_SYSV_ABI (*)(const uint32_t*, uint32_t*,
|
|
ResourcePlan::EvaluationContext::Entry*, uint64_t, size_t);
|
|
return m_program.srt_code->getCode<Walker>()(m_runtime.user_data.data(), flat.data(),
|
|
m_context.values.data(), m_context.generation, m_runtime.user_data.size());
|
|
}
|
|
const auto refresh = [&](uint32_t slot) {
|
|
if (slot >= m_program.srt_reads.size()) return false;
|
|
const auto& read = m_program.srt_reads[slot];
|
|
const auto* inst = read.value.ResolveInstruction();
|
|
const bool clean = inst != nullptr && inst->Flags<SrtReadFlags>().clean != 0u;
|
|
if (clean && (m_clean_evaluator == nullptr || m_runtime.read_specialization_memory == nullptr))
|
|
return false;
|
|
auto& evaluator = clean ? *m_clean_evaluator : *this;
|
|
return read.flat_offset < flat.size() && evaluator.Evaluate(read.value, flat[read.flat_offset]);
|
|
};
|
|
auto& active = m_program.active_sources;
|
|
if (m_program.control_flow.empty()) {
|
|
active.clear();
|
|
flat.resize(m_program.srt_reads.size());
|
|
for (uint32_t slot = 0; slot < m_program.srt_reads.size(); ++slot) {
|
|
if (!refresh(slot)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
flat.assign(m_program.srt_reads.size(), 0u);
|
|
active.assign(m_program.descriptor_sources.size(), 1u);
|
|
for (const auto& block: m_program.control_flow) {
|
|
for (const auto source: block.sources) active.at(source) = 0u;
|
|
}
|
|
auto& visited = m_program.visited_blocks;
|
|
auto& pending = m_program.pending_blocks;
|
|
visited.assign(m_program.control_flow.size(), 0u);
|
|
pending.clear();
|
|
pending.push_back(0u);
|
|
while (!pending.empty()) {
|
|
const auto index = pending.back();
|
|
pending.pop_back();
|
|
if (visited.at(index)) continue;
|
|
visited[index] = 1u;
|
|
const auto& block = m_program.control_flow[index];
|
|
for (const auto source: block.sources) active[source] = 1u;
|
|
for (const auto slot: block.srt_reads) {
|
|
if (!refresh(slot)) return false;
|
|
}
|
|
uint32_t condition = 0;
|
|
auto& predicate = m_clean_evaluator != nullptr ? *m_clean_evaluator : *this;
|
|
if (!block.condition.IsEmpty() &&
|
|
(m_runtime.read_specialization_memory != nullptr || !m_program.capture_specialization_reads) &&
|
|
predicate.Evaluate(block.condition, condition)) {
|
|
pending.push_back(block.successors[condition != 0u ? 0u : 1u]);
|
|
} else {
|
|
pending.insert(pending.end(), block.successors.begin(), block.successors.end());
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ValidateRuntimeValue(const ResourcePlan& program, Value value, RuntimeValueType type) {
|
|
return RuntimeValidator(program, type).Run(value);
|
|
}
|
|
|
|
|
|
} // namespace Libs::Graphics::ShaderRecompiler::IR
|