#include #include "graphics/shader/recompiler/ir/passes/SrtWalker.h" #include "common/assert.h" #include "graphics/shader/recompiler/ir/ShaderIR.h" #include #include #include #include #include #include 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(offset); if (magnitude > base) { return false; } result = base - magnitude; return true; } const auto magnitude = static_cast(offset); if (magnitude > AddressMask - base) { return false; } result = base + magnitude; return true; } void CollectPhiLeaves(Value value, std::vector& out) { std::vector pending {value}; std::unordered_set 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().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 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(); 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().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(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().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 m_visiting; std::vector m_visited; std::vector m_patches; }; class Evaluator { public: Evaluator(const Program& program, const SrtRuntime& runtime, std::span 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(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))); if (!value.IsEmpty()) { return EvaluateWide(value, result); } std::vector leaves; CollectPhiLeaves(Value(const_cast(&inst)), leaves); std::vector 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(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(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(static_cast(lhs)) + static_cast(rhs); result = component == 0u ? static_cast(sum) : static_cast(sum >> 32u); return true; } return false; } bool EvaluateRawRead(const Inst& inst, uint64_t& result) { const auto flags = inst.Flags(); 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(low)) & AddressMask; const auto immediate = static_cast(static_cast(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(immediate) + static_cast(offset); const auto aligned = byte_offset & ~uint64_t {3}; const auto stride = (static_cast(high) >> 16u) & 0x3fffu; const auto size = stride == 0u ? static_cast(static_cast(records)) : static_cast(stride) * static_cast(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(static_cast(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(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(a) | (static_cast(static_cast(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(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(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(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(a), static_cast(b)); return true; } return false; case ValueOpcode::BitwiseAnd32: if (binary()) { result = static_cast(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(a | b); return true; } return false; case ValueOpcode::BitwiseXor32: if (binary()) { result = static_cast(a ^ b); return true; } return false; case ValueOpcode::BitwiseNot32: if (Arg(inst, 0, a)) { result = ~static_cast(a); return true; } return false; case ValueOpcode::ShiftLeftLogical32: if (binary()) { result = static_cast(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(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( std::bit_cast(static_cast(a)) >> (b & 31u)); return true; } return false; case ValueOpcode::ShiftRightArithmetic64: if (binary()) { result = static_cast(std::bit_cast(a) >> (b & 63u)); return true; } return false; case ValueOpcode::BitFieldUExtract: if (ternary()) { const auto offset = static_cast(b); const auto width = static_cast(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(a) >> offset) & mask; return true; } return false; case ValueOpcode::BitFieldSExtract: if (ternary()) { const auto offset = static_cast(b); const auto width = static_cast(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(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(c); const auto width = static_cast(d); if (offset > 32u || width > 32u - offset) { return false; } if (width == 0u) { result = static_cast(a); return true; } const auto mask = width == 32u ? UINT32_MAX : ((uint32_t {1} << width) - 1u) << offset; result = (static_cast(a) & ~mask) | ((static_cast(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(a) == static_cast(b); return true; } return false; case ValueOpcode::INotEqual32: if (binary()) { result = static_cast(a) != static_cast(b); return true; } return false; case ValueOpcode::ULessThan32: if (binary()) { result = static_cast(a) < static_cast(b); return true; } return false; case ValueOpcode::UGreaterThan32: if (binary()) { result = static_cast(a) > static_cast(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 m_clean_flat_slots; Evaluator* m_clean_evaluator = nullptr; std::unordered_map m_cache; std::vector 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 requests, const SrtRuntime& runtime, std::vector& results, std::vector& flat, bool evaluate_flat, std::span 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 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 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 results; if (!EvaluateDescriptorSources(program, std::span {&request, 1}, runtime, results)) { return false; } result = results.front(); return true; } bool EvaluateDescriptorSources(const Program& program, std::span requests, const SrtRuntime& runtime, std::vector& results) { std::vector ignored; return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, ignored, false, {}); } bool EvaluateRuntimeSources(const Program& program, std::span requests, const SrtRuntime& runtime, std::vector& results, std::vector& flat, std::span clean_flat_slots) { return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, flat, true, clean_flat_slots); } bool WalkSrt(const Program& program, const SrtRuntime& runtime, std::vector& flat) { std::vector ignored; return EvaluateRuntimeSources(program, {}, runtime, ignored, flat, {}); } } // namespace Libs::Graphics::ShaderRecompiler::IR