From 3400a2b6e54d6471df126848df6958c2402ee60b Mon Sep 17 00:00:00 2001 From: boykopovar Date: Fri, 18 Sep 2026 11:31:21 +0300 Subject: [PATCH] feat(shader): add srt walker impl + refactor --- core/shader/recompiler/CMakeLists.txt | 9 +- .../SrtWalker/SrtAddressArithmetic.hpp | 14 + .../SrtWalker/SrtDescriptorEvaluation.hpp | 17 + .../Optimization/SrtWalker/SrtDiagnostics.hpp | 17 + .../Optimization/SrtWalker/SrtEvaluator.hpp | 42 ++ .../SrtWalker/SrtInstructionPredicates.hpp | 15 + .../Optimization/SrtWalker/SrtPlanBuilder.hpp | 35 ++ .../SrtWalker/SrtRuntimeValidator.hpp | 30 ++ .../recompiler/Optimization/src/SrtWalker.cpp | 34 -- .../src/SrtWalker/SrtAddressArithmetic.cpp | 25 + .../src/SrtWalker/SrtDescriptorEvaluation.cpp | 99 ++++ .../src/SrtWalker/SrtDiagnostics.cpp | 30 ++ .../src/SrtWalker/SrtEvaluator.cpp | 480 ++++++++++++++++++ .../SrtWalker/SrtInstructionPredicates.cpp | 84 +++ .../src/SrtWalker/SrtPlanBuilder.cpp | 129 +++++ .../src/SrtWalker/SrtRuntimeValidator.cpp | 145 ++++++ .../Optimization/src/SrtWalker/SrtWalker.cpp | 64 +++ 17 files changed, 1234 insertions(+), 35 deletions(-) create mode 100644 core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtAddressArithmetic.hpp create mode 100644 core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtDescriptorEvaluation.hpp create mode 100644 core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtDiagnostics.hpp create mode 100644 core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtEvaluator.hpp create mode 100644 core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtInstructionPredicates.hpp create mode 100644 core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtPlanBuilder.hpp create mode 100644 core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtRuntimeValidator.hpp delete mode 100644 core/shader/recompiler/Optimization/src/SrtWalker.cpp create mode 100644 core/shader/recompiler/Optimization/src/SrtWalker/SrtAddressArithmetic.cpp create mode 100644 core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp create mode 100644 core/shader/recompiler/Optimization/src/SrtWalker/SrtDiagnostics.cpp create mode 100644 core/shader/recompiler/Optimization/src/SrtWalker/SrtEvaluator.cpp create mode 100644 core/shader/recompiler/Optimization/src/SrtWalker/SrtInstructionPredicates.cpp create mode 100644 core/shader/recompiler/Optimization/src/SrtWalker/SrtPlanBuilder.cpp create mode 100644 core/shader/recompiler/Optimization/src/SrtWalker/SrtRuntimeValidator.cpp create mode 100644 core/shader/recompiler/Optimization/src/SrtWalker/SrtWalker.cpp diff --git a/core/shader/recompiler/CMakeLists.txt b/core/shader/recompiler/CMakeLists.txt index 651340a94..a54e0ff2d 100644 --- a/core/shader/recompiler/CMakeLists.txt +++ b/core/shader/recompiler/CMakeLists.txt @@ -62,7 +62,14 @@ function(add_shader_recompiler target runtimeTarget) ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/BindingAllocator.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/ShaderInfoCollector.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/SharedMemoryBarrierInserter.cpp - ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/SrtWalker.cpp + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/SrtWalker/SrtAddressArithmetic.cpp + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/SrtWalker/SrtDiagnostics.cpp + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/SrtWalker/SrtEvaluator.cpp + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/SrtWalker/SrtInstructionPredicates.cpp + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/SrtWalker/SrtPlanBuilder.cpp + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/SrtWalker/SrtRuntimeValidator.cpp + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/SrtWalker/SrtWalker.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvModule.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvAnalysis.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvAluEmitter.cpp diff --git a/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtAddressArithmetic.hpp b/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtAddressArithmetic.hpp new file mode 100644 index 000000000..ae6e279ac --- /dev/null +++ b/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtAddressArithmetic.hpp @@ -0,0 +1,14 @@ +#ifndef CORE_SHADER_RECOMPILIER_OPTIMIZATION_SRTWALKER_SRTADDRESSARITHMETIC_HPP +#define CORE_SHADER_RECOMPILIER_OPTIMIZATION_SRTWALKER_SRTADDRESSARITHMETIC_HPP + +#include + +namespace ShaderRecompiler::Detail { + +inline constexpr std::uint64_t AddressMask = 0x0000ffffffffffffull; + +bool AddSignedAddress(std::uint64_t base, std::int64_t offset, std::uint64_t& result); + +} + +#endif diff --git a/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtDescriptorEvaluation.hpp b/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtDescriptorEvaluation.hpp new file mode 100644 index 000000000..f962868da --- /dev/null +++ b/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtDescriptorEvaluation.hpp @@ -0,0 +1,17 @@ +#ifndef CORE_SHADER_RECOMPILIER_OPTIMIZATION_SRTWALKER_SRTDESCRIPTOREVALUATION_HPP +#define CORE_SHADER_RECOMPILIER_OPTIMIZATION_SRTWALKER_SRTDESCRIPTOREVALUATION_HPP + +#include "IntermediateRepresentation/IrProgram.hpp" +#include "Optimization/SrtWalker.hpp" + +#include +#include +#include + +namespace ShaderRecompiler::Detail { + +bool EvaluateRuntimeSourcesImpl(const IrResourcePlan& program, std::span sources, const SrtRuntime& runtime, std::vector& results, std::vector& flat, bool evaluateFlat, std::span cleanFlatSlots, std::vector& activeSources); + +} + +#endif diff --git a/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtDiagnostics.hpp b/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtDiagnostics.hpp new file mode 100644 index 000000000..43bb2f57e --- /dev/null +++ b/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtDiagnostics.hpp @@ -0,0 +1,17 @@ +#ifndef CORE_SHADER_RECOMPILIER_OPTIMIZATION_SRTWALKER_SRTDIAGNOSTICS_HPP +#define CORE_SHADER_RECOMPILIER_OPTIMIZATION_SRTWALKER_SRTDIAGNOSTICS_HPP + +#include "IntermediateRepresentation/IrProgram.hpp" + +#include +#include + +namespace ShaderRecompiler::Detail { + +const char* StageName(IrShaderStage stage); +std::string Diagnostic(const IrResourcePlan& program, std::uint32_t pc, const std::string& message); +[[noreturn]] void Fail(const IrResourcePlan& program, std::uint32_t pc, const std::string& message); + +} + +#endif diff --git a/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtEvaluator.hpp b/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtEvaluator.hpp new file mode 100644 index 000000000..bd7f6ea75 --- /dev/null +++ b/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtEvaluator.hpp @@ -0,0 +1,42 @@ +#ifndef CORE_SHADER_RECOMPILIER_OPTIMIZATION_SRTWALKER_SRTEVALUATOR_HPP +#define CORE_SHADER_RECOMPILIER_OPTIMIZATION_SRTWALKER_SRTEVALUATOR_HPP + +#include "IntermediateRepresentation/IrProgram.hpp" +#include "Optimization/SrtWalker.hpp" + +#include +#include +#include +#include + +namespace ShaderRecompiler::Detail { + +class Evaluator { +public: + Evaluator(const IrResourcePlan& program, const SrtRuntime& runtime, std::span cleanFlatSlots = {}, Evaluator* cleanEvaluator = nullptr, IrValue* activeMask = nullptr) : _program(program), _runtime(runtime), _cleanFlatSlots(cleanFlatSlots), _cleanEvaluator(cleanEvaluator), _activeMask(activeMask != nullptr ? activeMask->Resolve() : nullptr) {} + + bool Evaluate(IrValue* value, std::uint32_t& result); + bool EvaluateWide(IrValue* raw, std::uint64_t& result); + +private: + static float Float32(std::uint64_t bits); + static std::uint64_t Float32Bits(float value); + + bool Arg(IrValue& inst, std::size_t index, std::uint64_t& result); + bool EvaluatePhi(IrValue& inst, std::uint64_t& result); + bool EvaluateExtract(IrValue& inst, std::uint64_t& result); + bool EvaluateRawRead(IrValue& inst, std::uint64_t& result); + bool EvaluateInst(IrValue& inst, std::uint64_t& result); + + const IrResourcePlan& _program; + const SrtRuntime& _runtime; + std::span _cleanFlatSlots; + Evaluator* _cleanEvaluator = nullptr; + IrValue* _activeMask = nullptr; + std::unordered_map _cache; + std::vector _visiting; +}; + +} + +#endif diff --git a/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtInstructionPredicates.hpp b/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtInstructionPredicates.hpp new file mode 100644 index 000000000..e4ab1ce06 --- /dev/null +++ b/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtInstructionPredicates.hpp @@ -0,0 +1,15 @@ +#ifndef CORE_SHADER_RECOMPILIER_OPTIMIZATION_SRTWALKER_SRTINSTRUCTIONPREDICATES_HPP +#define CORE_SHADER_RECOMPILIER_OPTIMIZATION_SRTWALKER_SRTINSTRUCTIONPREDICATES_HPP + +#include "IntermediateRepresentation/IrProgram.hpp" + +namespace ShaderRecompiler::Detail { + +bool IsRawRead(const IrResourcePlan& program, const IrValue& inst); +bool IsDescriptorHandle(IrOpcode opcode); +bool IsRuntimeSelect(IrOpcode opcode); +bool IsRuntimeUniformOp(IrOpcode opcode); + +} + +#endif diff --git a/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtPlanBuilder.hpp b/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtPlanBuilder.hpp new file mode 100644 index 000000000..f79ffe82e --- /dev/null +++ b/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtPlanBuilder.hpp @@ -0,0 +1,35 @@ +#ifndef CORE_SHADER_RECOMPILIER_OPTIMIZATION_SRTWALKER_SRTPLANBUILDER_HPP +#define CORE_SHADER_RECOMPILIER_OPTIMIZATION_SRTWALKER_SRTPLANBUILDER_HPP + +#include "IntermediateRepresentation/IrProgram.hpp" + +#include +#include + +namespace ShaderRecompiler::Detail { + +class PlanBuilder { +public: + explicit PlanBuilder(IrProgram& program) : _program(program) {} + + void Run(); + +private: + struct Patch { + IrValue* inst = nullptr; + std::uint32_t slot = 0; + bool keep = false; + }; + + void Collect(IrValue* raw, std::uint32_t usePc); + void PatchReads(); + + IrProgram& _program; + std::vector _visiting; + std::vector _visited; + std::vector _patches; +}; + +} + +#endif diff --git a/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtRuntimeValidator.hpp b/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtRuntimeValidator.hpp new file mode 100644 index 000000000..ffa1942b8 --- /dev/null +++ b/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtRuntimeValidator.hpp @@ -0,0 +1,30 @@ +#ifndef CORE_SHADER_RECOMPILIER_OPTIMIZATION_SRTWALKER_SRTRUNTIMEVALIDATOR_HPP +#define CORE_SHADER_RECOMPILIER_OPTIMIZATION_SRTWALKER_SRTRUNTIMEVALIDATOR_HPP + +#include "IntermediateRepresentation/IrProgram.hpp" +#include "Optimization/SrtWalker.hpp" + +#include + +namespace ShaderRecompiler::Detail { + +class RuntimeValidator { +public: + RuntimeValidator(const IrResourcePlan& program, RuntimeValueType type) : _program(program), _type(type) {} + + bool Run(IrValue* value) { return Validate(value); } + +private: + bool ValidateArguments(IrValue& inst, bool requireUniform); + bool Validate(IrValue* raw, bool requireUniform = true); + + const IrResourcePlan& _program; + RuntimeValueType _type; + IrValue* _activeMask = nullptr; + std::unordered_set _visiting; + std::unordered_set _validatedDependencies; +}; + +} + +#endif diff --git a/core/shader/recompiler/Optimization/src/SrtWalker.cpp b/core/shader/recompiler/Optimization/src/SrtWalker.cpp deleted file mode 100644 index 7492d0769..000000000 --- a/core/shader/recompiler/Optimization/src/SrtWalker.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "Optimization/SrtWalker.hpp" -#include - -namespace ShaderRecompiler { - -void SrtWalker::BuildPlan(IrProgram& program) const { - throw std::runtime_error("SrtWalker::BuildPlan not implemented"); -} - -bool SrtWalker::ValidateRuntimeValue(const IrResourcePlan& program, const IrValue* value, RuntimeValueType type) const { - throw std::runtime_error("SrtWalker::ValidateRuntimeValue not implemented"); -} - -void SrtWalker::EvaluateUniformValues(const IrResourcePlan& program, std::span values, const SrtRuntime& runtime, std::span results) const { - throw std::runtime_error("SrtWalker::EvaluateUniformValues not implemented"); -} - -void SrtWalker::EvaluateDescriptorSource(const IrResourcePlan& program, std::uint32_t source, const SrtRuntime& runtime, DescriptorValue& result) const { - throw std::runtime_error("SrtWalker::EvaluateDescriptorSource not implemented"); -} - -void SrtWalker::EvaluateDescriptorSources(const IrResourcePlan& program, std::span sources, const SrtRuntime& runtime, std::vector& results) const { - throw std::runtime_error("SrtWalker::EvaluateDescriptorSources not implemented"); -} - -void SrtWalker::EvaluateRuntimeSources(const IrResourcePlan& program, std::span sources, const SrtRuntime& runtime, std::vector& results, std::vector& flat, std::span cleanFlatSlots, std::vector& activeSources) const { - throw std::runtime_error("SrtWalker::EvaluateRuntimeSources not implemented"); -} - -void SrtWalker::Walk(const IrResourcePlan& program, const SrtRuntime& runtime, std::vector& flat) const { - throw std::runtime_error("SrtWalker::Walk not implemented"); -} - -} diff --git a/core/shader/recompiler/Optimization/src/SrtWalker/SrtAddressArithmetic.cpp b/core/shader/recompiler/Optimization/src/SrtWalker/SrtAddressArithmetic.cpp new file mode 100644 index 000000000..9040192bc --- /dev/null +++ b/core/shader/recompiler/Optimization/src/SrtWalker/SrtAddressArithmetic.cpp @@ -0,0 +1,25 @@ +#include "Optimization/SrtWalker/SrtAddressArithmetic.hpp" + +namespace ShaderRecompiler::Detail { + +bool AddSignedAddress(std::uint64_t base, std::int64_t offset, std::uint64_t& result) { + if (base > AddressMask) { + return false; + } + if (offset < 0) { + const auto magnitude = std::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; +} + +} diff --git a/core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp b/core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp new file mode 100644 index 000000000..2f676ce9b --- /dev/null +++ b/core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp @@ -0,0 +1,99 @@ +#include "Optimization/SrtWalker/SrtDescriptorEvaluation.hpp" +#include "Optimization/SrtWalker/SrtEvaluator.hpp" + +#include + +namespace ShaderRecompiler::Detail { + +namespace { + +const DescriptorSource* Source(const IrResourcePlan& program, std::uint32_t source) { + if (source >= program.descriptorSources.size()) { + return nullptr; + } + return &program.descriptorSources[source]; +} + +} + +bool EvaluateRuntimeSourcesImpl(const IrResourcePlan& program, std::span sources, const SrtRuntime& runtime, std::vector& results, std::vector& flat, bool evaluateFlat, std::span cleanFlatSlots, std::vector& activeSources) { + if (!program.srtPlanComplete) { + return false; + } + if (std::any_of(cleanFlatSlots.begin(), cleanFlatSlots.end(), [](std::uint8_t clean) { return clean != 0u; }) && runtime.readSpecializationMemory == nullptr) { + return false; + } + SrtRuntime cleanRuntime = runtime; + cleanRuntime.readMemory = runtime.readSpecializationMemory; + Evaluator cleanEvaluator(program, cleanRuntime); + Evaluator evaluator(program, runtime, cleanFlatSlots, &cleanEvaluator); + std::vector active; + if (evaluateFlat) { + active.assign(program.descriptorSources.size(), 1u); + } + if (evaluateFlat && !program.controlFlow.empty()) { + for (const auto& block : program.controlFlow) { + for (const auto source : block.sources) { + active.at(source) = 0u; + } + } + std::vector visited(program.controlFlow.size()); + std::vector pending {0}; + while (!pending.empty()) { + const auto index = pending.back(); + pending.pop_back(); + if (visited.at(index)) { + continue; + } + visited[index] = 1u; + const auto& block = program.controlFlow[index]; + for (const auto source : block.sources) { + active[source] = 1u; + } + std::uint32_t condition = 0; + const bool cleanEvaluable = block.condition != nullptr && runtime.readSpecializationMemory != nullptr && cleanEvaluator.Evaluate(block.condition, condition); + if (cleanEvaluable) { + pending.push_back(block.successors[condition != 0u ? 0u : 1u]); + } else { + pending.insert(pending.end(), block.successors.begin(), block.successors.end()); + } + } + } + std::vector evaluated; + evaluated.reserve(sources.size()); + for (const auto sourceIndex : sources) { + const auto* source = Source(program, sourceIndex); + if (source == nullptr) { + return false; + } + DescriptorValue value; + value.dwordCount = source->dwordCount; + if (!evaluateFlat || active[sourceIndex]) { + for (std::uint32_t index = 0; index < source->dwordCount; index++) { + if (!evaluator.Evaluate(source->dwords[index], value.dwords[index])) { + return false; + } + } + } + evaluated.push_back(value); + } + std::vector flattened; + if (evaluateFlat) { + flattened.resize(program.srtReads.size()); + for (const auto& read : program.srtReads) { + const bool clean = read.flatOffset < cleanFlatSlots.size() && cleanFlatSlots[read.flatOffset] != 0u; + auto& selected = clean ? cleanEvaluator : evaluator; + if (read.flatOffset >= flattened.size() || !selected.Evaluate(read.value, flattened[read.flatOffset])) { + return false; + } + } + } + results = std::move(evaluated); + activeSources = std::move(active); + if (evaluateFlat) { + flat = std::move(flattened); + } + return true; +} + +} diff --git a/core/shader/recompiler/Optimization/src/SrtWalker/SrtDiagnostics.cpp b/core/shader/recompiler/Optimization/src/SrtWalker/SrtDiagnostics.cpp new file mode 100644 index 000000000..5f240543b --- /dev/null +++ b/core/shader/recompiler/Optimization/src/SrtWalker/SrtDiagnostics.cpp @@ -0,0 +1,30 @@ +#include "Optimization/SrtWalker/SrtDiagnostics.hpp" + +#include +#include + +namespace ShaderRecompiler::Detail { + +const char* StageName(IrShaderStage stage) { + switch (stage) { + case IrShaderStage::Vertex: return "vertex"; + case IrShaderStage::Pixel: return "pixel"; + case IrShaderStage::Fetch: return "fetch"; + case IrShaderStage::Compute: return "compute"; + default: return "unknown"; + } +} + +std::string Diagnostic(const IrResourcePlan& program, std::uint32_t pc, const std::string& message) { + char hashBuffer[32]; + std::snprintf(hashBuffer, sizeof(hashBuffer), "0x%016llx", static_cast(program.shaderHash)); + char pcBuffer[16]; + std::snprintf(pcBuffer, sizeof(pcBuffer), "0x%08x", pc); + return "shader SRT: hash=" + std::string(hashBuffer) + " stage=" + StageName(program.stage) + " pc=" + std::string(pcBuffer) + " " + message; +} + +[[noreturn]] void Fail(const IrResourcePlan& program, std::uint32_t pc, const std::string& message) { + throw std::runtime_error(Diagnostic(program, pc, message)); +} + +} diff --git a/core/shader/recompiler/Optimization/src/SrtWalker/SrtEvaluator.cpp b/core/shader/recompiler/Optimization/src/SrtWalker/SrtEvaluator.cpp new file mode 100644 index 000000000..3706a66c3 --- /dev/null +++ b/core/shader/recompiler/Optimization/src/SrtWalker/SrtEvaluator.cpp @@ -0,0 +1,480 @@ +#include "Optimization/SrtWalker/SrtEvaluator.hpp" +#include "Optimization/SrtWalker/SrtAddressArithmetic.hpp" +#include "Optimization/SrtWalker/SrtInstructionPredicates.hpp" +#include "IntermediateRepresentation/IrBuilder.hpp" + +#include +#include +#include +#include + +namespace ShaderRecompiler::Detail { + +bool Evaluator::Evaluate(IrValue* value, std::uint32_t& result) { + std::uint64_t wide = 0; + if (!EvaluateWide(value, wide)) { + return false; + } + result = static_cast(wide); + return true; +} + +bool Evaluator::EvaluateWide(IrValue* raw, std::uint64_t& result) { + IrValue* value = raw->Resolve(); + if (value->HasImmediate()) { + switch (value->Type()) { + case IrType::Bool: result = value->ImmediateBool() ? 1u : 0u; return true; + case IrType::U8: result = value->ImmediateU8(); return true; + case IrType::U16: result = value->ImmediateU16(); return true; + case IrType::U32: result = value->ImmediateU32(); return true; + case IrType::U64: result = value->ImmediateU64(); return true; + case IrType::F32: result = Float32Bits(value->ImmediateF32()); return true; + default: return false; + } + } + if (value->Opcode() == IrOpcode::Void) { + return false; + } + IrValue* inst = value; + if (_activeMask != nullptr && IsRuntimeSelect(inst->Opcode()) && inst->ArgumentCount() == 3 && inst->Argument(0)->Resolve() == _activeMask) { + return EvaluateWide(inst->Argument(1), result); + } + if (const auto found = _cache.find(inst); found != _cache.end()) { + result = found->second; + return true; + } + if (std::find(_visiting.begin(), _visiting.end(), inst) != _visiting.end()) { + return false; + } + _visiting.push_back(inst); + std::uint64_t out = 0; + const bool evaluated = EvaluateInst(*inst, out); + _visiting.pop_back(); + if (!evaluated) { + return false; + } + _cache.emplace(inst, out); + result = out; + return true; +} + +float Evaluator::Float32(std::uint64_t bits) { return std::bit_cast(static_cast(bits)); } + +std::uint64_t Evaluator::Float32Bits(float value) { return std::bit_cast(value); } + +bool Evaluator::Arg(IrValue& inst, std::size_t index, std::uint64_t& result) { return EvaluateWide(inst.Argument(index), result); } + +bool Evaluator::EvaluatePhi(IrValue& inst, std::uint64_t& result) { + IrValue* value = ResolveInvariantPhi(_program, &inst); + return value != nullptr && EvaluateWide(value, result); +} + +bool Evaluator::EvaluateExtract(IrValue& inst, std::uint64_t& result) { + IrValue* index = inst.Argument(1)->Resolve(); + if (!index->HasImmediate() || index->Type() != IrType::U32) { + return false; + } + const auto component = index->ImmediateU32(); + if (component >= 2u) { + return false; + } + if (inst.Opcode() == IrOpcode::CompositeExtractU64) { + std::uint64_t packed = 0; + if (!Arg(inst, 0, packed)) { + return false; + } + result = static_cast(packed >> (component * 32u)); + return true; + } + IrValue* source = inst.Argument(0)->Resolve(); + if (source->Opcode() == IrOpcode::Void) { + return false; + } + if (source->Opcode() == IrOpcode::CompositeConstructU32x2) { + return EvaluateWide(source->Argument(component), result); + } + if (source->Opcode() == IrOpcode::IAddCarry32) { + std::uint64_t lhs = 0; + std::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 Evaluator::EvaluateRawRead(IrValue& inst, std::uint64_t& result) { + const auto flags = inst.Flags(); + if (flags.index >= _program.memoryInfo.size()) { + return false; + } + const auto& mem = _program.memoryInfo[flags.index]; + IrValue* handle = inst.Argument(0)->Resolve(); + if (handle->Opcode() == IrOpcode::Void) { + return false; + } + std::uint64_t low = 0; + std::uint64_t high = 0; + std::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)); + std::uint64_t address = 0; + if (inst.Opcode() == IrOpcode::ReadConstBuffer) { + std::uint64_t records = 0; + std::uint64_t word3 = 0; + if (handle->ArgumentCount() != 4u || !Arg(*handle, 2, records) || !Arg(*handle, 3, word3)) { + return false; + } + if (immediate < 0) { + return false; + } + const auto byteOffset = static_cast(immediate) + static_cast(offset); + const auto aligned = byteOffset & ~std::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(std::uint32_t)) { + return false; + } + address = ((base & ~std::uint64_t {3}) + byteOffset) & ~std::uint64_t {3}; + } else { + const auto relative = (immediate & ~std::int64_t {3}) + static_cast(static_cast(offset) & ~3u); + if (!AddSignedAddress(base & ~std::uint64_t {3}, relative, address)) { + return false; + } + } + std::uint32_t word = 0; + if (_runtime.readMemory != nullptr) { + if (!_runtime.readMemory(_runtime.userContext, address, &word)) { + return false; + } + } else { + std::memcpy(&word, reinterpret_cast(address), sizeof(word)); + } + result = word; + return true; +} + +bool Evaluator::EvaluateInst(IrValue& inst, std::uint64_t& result) { + std::uint64_t a = 0; + std::uint64_t b = 0; + std::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.Opcode()) { + case IrOpcode::GetUserData: { + const auto reg = RegIndex(static_cast(inst.Argument(0)->Register().index)); + if (reg < _program.userDataBase || reg - _program.userDataBase >= _runtime.userData.size()) { + return false; + } + result = _runtime.userData[reg - _program.userDataBase]; + return true; + } + case IrOpcode::GetShaderBase: result = _runtime.shaderBase; return true; + case IrOpcode::Phi: return EvaluatePhi(inst, result); + case IrOpcode::ReadFirstLane: { + Evaluator active(_program, _runtime, _cleanFlatSlots, _cleanEvaluator, inst.Argument(1)); + return active.EvaluateWide(inst.Argument(0), result); + } + case IrOpcode::BitCastU32F32: + case IrOpcode::BitCastF32U32: return Arg(inst, 0, result); + case IrOpcode::CompositeExtractU64: + case IrOpcode::CompositeExtractU32x2: return EvaluateExtract(inst, result); + case IrOpcode::CompositeConstructU64: + if (!binary()) { + return false; + } + result = static_cast(a) | (static_cast(static_cast(b)) << 32u); + return true; + case IrOpcode::ReadConst: { + IrValue* slot = inst.Argument(1)->Resolve(); + if (!slot->HasImmediate() || slot->Type() != IrType::U32 || slot->ImmediateU32() >= _program.srtReads.size()) { + return false; + } + if (slot->ImmediateU32() < _cleanFlatSlots.size() && _cleanFlatSlots[slot->ImmediateU32()] != 0u && _cleanEvaluator != nullptr) { + return _cleanEvaluator->EvaluateWide(_program.srtReads[slot->ImmediateU32()].value, result); + } + return EvaluateWide(_program.srtReads[slot->ImmediateU32()].value, result); + } + case IrOpcode::LoadAddressU32: + case IrOpcode::ReadConstBuffer: + if (IsRawRead(_program, inst)) { + return EvaluateRawRead(inst, result); + } + break; + case IrOpcode::IAdd32: + if (binary()) { + result = static_cast(a + b); + return true; + } + return false; + case IrOpcode::IAdd64: + if (binary()) { + result = a + b; + return true; + } + return false; + case IrOpcode::ISub32: + if (binary()) { + result = static_cast(a - b); + return true; + } + return false; + case IrOpcode::ISub64: + if (binary()) { + result = a - b; + return true; + } + return false; + case IrOpcode::IMul32: + if (binary()) { + result = static_cast(a * b); + return true; + } + return false; + case IrOpcode::IMul64: + if (binary()) { + result = a * b; + return true; + } + return false; + case IrOpcode::UMin32: + if (binary()) { + result = std::min(static_cast(a), static_cast(b)); + return true; + } + return false; + case IrOpcode::ConvertF32U32: + if (Arg(inst, 0, a)) { + result = Float32Bits(static_cast(static_cast(a))); + return true; + } + return false; + case IrOpcode::ConvertU32F32: + if (Arg(inst, 0, a)) { + const auto value = Float32(a); + if (!std::isfinite(value) || value < 0.0f || static_cast(value) > 4294967295.0) { + return false; + } + result = static_cast(value); + return true; + } + return false; + case IrOpcode::FPMul32: + if (binary()) { + result = Float32Bits(Float32(a) * Float32(b)); + return true; + } + return false; + case IrOpcode::FPTrunc32: + if (Arg(inst, 0, a)) { + result = Float32Bits(std::trunc(Float32(a))); + return true; + } + return false; + case IrOpcode::FPIsNan32: + if (Arg(inst, 0, a)) { + result = std::isnan(Float32(a)) ? 1u : 0u; + return true; + } + return false; + case IrOpcode::FPOrdLessThanEqual32: + if (binary()) { + result = Float32(a) <= Float32(b) ? 1u : 0u; + return true; + } + return false; + case IrOpcode::FPOrdGreaterThanEqual32: + if (binary()) { + result = Float32(a) >= Float32(b) ? 1u : 0u; + return true; + } + return false; + case IrOpcode::BitwiseAnd32: + if (binary()) { + result = static_cast(a & b); + return true; + } + return false; + case IrOpcode::BitwiseAnd64: + if (binary()) { + result = a & b; + return true; + } + return false; + case IrOpcode::BitwiseOr32: + if (binary()) { + result = static_cast(a | b); + return true; + } + return false; + case IrOpcode::BitwiseXor32: + if (binary()) { + result = static_cast(a ^ b); + return true; + } + return false; + case IrOpcode::BitwiseNot32: + if (Arg(inst, 0, a)) { + result = ~static_cast(a); + return true; + } + return false; + case IrOpcode::ShiftLeftLogical32: + if (binary()) { + result = static_cast(a) << (b & 31u); + return true; + } + return false; + case IrOpcode::ShiftLeftLogical64: + if (binary()) { + result = a << (b & 63u); + return true; + } + return false; + case IrOpcode::ShiftRightLogical32: + if (binary()) { + result = static_cast(a) >> (b & 31u); + return true; + } + return false; + case IrOpcode::ShiftRightLogical64: + if (binary()) { + result = a >> (b & 63u); + return true; + } + return false; + case IrOpcode::ShiftRightArithmetic32: + if (binary()) { + result = static_cast(std::bit_cast(static_cast(a)) >> (b & 31u)); + return true; + } + return false; + case IrOpcode::ShiftRightArithmetic64: + if (binary()) { + result = static_cast(std::bit_cast(a) >> (b & 63u)); + return true; + } + return false; + case IrOpcode::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 ? 0xffffffffu : width == 0u ? 0u : (std::uint32_t {1} << width) - 1u; + result = width == 0u ? 0u : (static_cast(a) >> offset) & mask; + return true; + } + return false; + case IrOpcode::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 ? 0xffffffffu : (std::uint32_t {1} << width) - 1u; + auto bits = (static_cast(a) >> offset) & mask; + if (width < 32u && (bits & (std::uint32_t {1} << (width - 1u))) != 0u) { + bits |= ~mask; + } + result = bits; + return true; + } + return false; + case IrOpcode::BitFieldInsert: { + std::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 ? 0xffffffffu : ((std::uint32_t {1} << width) - 1u) << offset; + result = (static_cast(a) & ~mask) | ((static_cast(b) << offset) & mask); + return true; + } + case IrOpcode::SelectU32: + case IrOpcode::SelectU1: + case IrOpcode::SelectF32: + if (ternary()) { + result = a != 0u ? b : c; + return true; + } + return false; + case IrOpcode::IEqual32: + if (binary()) { + result = static_cast(a) == static_cast(b) ? 1u : 0u; + return true; + } + return false; + case IrOpcode::INotEqual32: + if (binary()) { + result = static_cast(a) != static_cast(b) ? 1u : 0u; + return true; + } + return false; + case IrOpcode::ULessThan32: + if (binary()) { + result = static_cast(a) < static_cast(b) ? 1u : 0u; + return true; + } + return false; + case IrOpcode::UGreaterThan32: + if (binary()) { + result = static_cast(a) > static_cast(b) ? 1u : 0u; + return true; + } + return false; + case IrOpcode::LogicalAnd: + if (binary()) { + result = (a != 0u) && (b != 0u) ? 1u : 0u; + return true; + } + return false; + case IrOpcode::LogicalOr: + if (binary()) { + result = (a != 0u) || (b != 0u) ? 1u : 0u; + return true; + } + return false; + case IrOpcode::LogicalXor: + if (binary()) { + result = (a != 0u) != (b != 0u) ? 1u : 0u; + return true; + } + return false; + case IrOpcode::LogicalNot: + if (Arg(inst, 0, a)) { + result = a == 0u ? 1u : 0u; + return true; + } + return false; + case IrOpcode::UndefU1: + case IrOpcode::UndefU8: + case IrOpcode::UndefU16: + case IrOpcode::UndefU32: + case IrOpcode::UndefU64: return false; + default: break; + } + return false; +} + +} diff --git a/core/shader/recompiler/Optimization/src/SrtWalker/SrtInstructionPredicates.cpp b/core/shader/recompiler/Optimization/src/SrtWalker/SrtInstructionPredicates.cpp new file mode 100644 index 000000000..46735d2cc --- /dev/null +++ b/core/shader/recompiler/Optimization/src/SrtWalker/SrtInstructionPredicates.cpp @@ -0,0 +1,84 @@ +#include "Optimization/SrtWalker/SrtInstructionPredicates.hpp" + +namespace ShaderRecompiler::Detail { + +bool IsRawRead(const IrResourcePlan& program, const IrValue& inst) { + const auto op = inst.Opcode(); + if (op != IrOpcode::LoadAddressU32 && op != IrOpcode::ReadConstBuffer) { + return false; + } + const auto index = inst.Flags().index; + if (index >= program.memoryInfo.size()) { + return false; + } + const auto kind = program.memoryInfo[index].kind; + return (op == IrOpcode::LoadAddressU32 && kind == ResourceKind::ScalarAddress) || (op == IrOpcode::ReadConstBuffer && kind == ResourceKind::ScalarBuffer); +} + +bool IsDescriptorHandle(IrOpcode opcode) { + switch (opcode) { + case IrOpcode::GetBufferResource: + case IrOpcode::GetAddressResource: + case IrOpcode::GetImageResource: + case IrOpcode::GetSamplerResource: return true; + default: return false; + } +} + +bool IsRuntimeSelect(IrOpcode opcode) { + return opcode == IrOpcode::SelectU1 || opcode == IrOpcode::SelectU32 || opcode == IrOpcode::SelectF32; +} + +bool IsRuntimeUniformOp(IrOpcode opcode) { + switch (opcode) { + case IrOpcode::BitCastU32F32: + case IrOpcode::BitCastF32U32: + case IrOpcode::ConvertU32F32: + case IrOpcode::ConvertF32U32: + case IrOpcode::CompositeConstructU64: + case IrOpcode::CompositeExtractU64: + case IrOpcode::CompositeConstructU32x2: + case IrOpcode::CompositeExtractU32x2: + case IrOpcode::BitFieldInsert: + case IrOpcode::BitFieldUExtract: + case IrOpcode::BitFieldSExtract: + case IrOpcode::IAdd32: + case IrOpcode::IAdd64: + case IrOpcode::IAddCarry32: + case IrOpcode::ISub32: + case IrOpcode::ISub64: + case IrOpcode::IMul32: + case IrOpcode::IMul64: + case IrOpcode::UMin32: + case IrOpcode::ShiftLeftLogical32: + case IrOpcode::ShiftLeftLogical64: + case IrOpcode::ShiftRightLogical32: + case IrOpcode::ShiftRightLogical64: + case IrOpcode::ShiftRightArithmetic32: + case IrOpcode::ShiftRightArithmetic64: + case IrOpcode::BitwiseAnd32: + case IrOpcode::BitwiseAnd64: + case IrOpcode::BitwiseOr32: + case IrOpcode::BitwiseXor32: + case IrOpcode::BitwiseNot32: + case IrOpcode::SelectU1: + case IrOpcode::SelectU32: + case IrOpcode::SelectF32: + case IrOpcode::ULessThan32: + case IrOpcode::IEqual32: + case IrOpcode::UGreaterThan32: + case IrOpcode::INotEqual32: + case IrOpcode::LogicalOr: + case IrOpcode::LogicalAnd: + case IrOpcode::LogicalXor: + case IrOpcode::LogicalNot: + case IrOpcode::FPOrdLessThanEqual32: + case IrOpcode::FPOrdGreaterThanEqual32: + case IrOpcode::FPIsNan32: + case IrOpcode::FPMul32: + case IrOpcode::FPTrunc32: return true; + default: return false; + } +} + +} diff --git a/core/shader/recompiler/Optimization/src/SrtWalker/SrtPlanBuilder.cpp b/core/shader/recompiler/Optimization/src/SrtWalker/SrtPlanBuilder.cpp new file mode 100644 index 000000000..98d46bb59 --- /dev/null +++ b/core/shader/recompiler/Optimization/src/SrtWalker/SrtPlanBuilder.cpp @@ -0,0 +1,129 @@ +#include "Optimization/SrtWalker/SrtPlanBuilder.hpp" +#include "Optimization/SrtWalker/SrtDiagnostics.hpp" +#include "Optimization/SrtWalker/SrtInstructionPredicates.hpp" +#include "Optimization/SrtWalker/SrtRuntimeValidator.hpp" + +#include "IntermediateRepresentation/IrBuilder.hpp" + +#include +#include + +namespace ShaderRecompiler::Detail { + +void PlanBuilder::Run() { + _program.Resources().srtReads.clear(); + _program.Metadata().dynamicReads.clear(); + for (auto& block : _program.Blocks()) { + for (IrValue* inst : block->Instructions()) { + const auto op = inst->Opcode(); + if (op == IrOpcode::LoadAddressU32 || op == IrOpcode::ReadConstBuffer) { + const auto flags = inst->Flags(); + if (flags.index < _program.Resources().memoryInfo.size()) { + const auto kind = _program.Resources().memoryInfo[flags.index].kind; + const bool crosswired = (op == IrOpcode::LoadAddressU32 && kind == ResourceKind::ScalarBuffer) || (op == IrOpcode::ReadConstBuffer && kind == ResourceKind::ScalarAddress); + if (crosswired) { + Fail(_program.Resources(), flags.pc, std::string(IrOpcodeName(op)) + " has incompatible scalar memory metadata"); + } + } + } + if (IsDescriptorHandle(inst->Opcode())) { + for (std::size_t index = 0; index < inst->ArgumentCount(); index++) { + Collect(inst->Argument(index), 0); + } + } + } + } + for (auto& block : _program.Blocks()) { + for (IrValue* inst : block->Instructions()) { + if (inst->Opcode() == IrOpcode::LoadAddressU32 && IsRawRead(_program.Resources(), *inst) && inst->Argument(1)->Resolve()->HasImmediate() && RuntimeValidator(_program.Resources(), RuntimeValueType::Any).Run(inst)) { + Collect(inst, inst->Flags().pc); + } + } + } + PatchReads(); +} + +void PlanBuilder::Collect(IrValue* raw, std::uint32_t usePc) { + IrValue* value = raw->Resolve(); + if (value->HasImmediate()) { + return; + } + if (value->Opcode() == IrOpcode::Void) { + Fail(_program.Resources(), usePc, "invalid typed planning value"); + } + IrValue* inst = value; + const auto cycle = std::find(_visiting.begin(), _visiting.end(), inst); + if (cycle != _visiting.end()) { + const auto containsPhi = std::any_of(cycle, _visiting.end(), [](IrValue* candidate) { return candidate->Opcode() == IrOpcode::Phi; }); + if (containsPhi) { + return; + } + Fail(_program.Resources(), usePc, "cyclic typed planning value " + std::string(IrOpcodeName(inst->Opcode())) + " without a phi"); + } + if (std::find(_visited.begin(), _visited.end(), inst) != _visited.end()) { + return; + } + _visiting.push_back(inst); + for (std::size_t index = 0; index < inst->ArgumentCount(); index++) { + Collect(inst->Argument(index), usePc); + } + _visiting.pop_back(); + _visited.push_back(inst); + if (!IsRawRead(_program.Resources(), *inst)) { + return; + } + IrValue* offset = inst->Argument(1)->Resolve(); + if (!offset->HasImmediate() || offset->Type() != IrType::U32) { + if (std::find(_program.Metadata().dynamicReads.begin(), _program.Metadata().dynamicReads.end(), value) == _program.Metadata().dynamicReads.end()) { + _program.Metadata().dynamicReads.push_back(value); + } + return; + } + for (std::uint32_t slot = 0; slot < _program.Resources().srtReads.size(); slot++) { + if (EquivalentValue(_program.Resources(), value, _program.Resources().srtReads[slot].value)) { + _patches.push_back({inst, slot, false}); + return; + } + } + const auto slot = static_cast(_program.Resources().srtReads.size()); + _program.Resources().srtReads.push_back({value, slot}); + _patches.push_back({inst, slot, true}); +} + +void PlanBuilder::PatchReads() { + IrBuilder builder(_program); + for (const auto& patch : _patches) { + IrBlock* block = patch.inst->Parent(); + if (block == nullptr) { + throw std::runtime_error("SrtWalker::BuildPlan patched value has no parent block"); + } + builder.SetInsertionPoint(*block); + IrValue& resource = builder.Emit(IrOpcode::GetSrtResource, IrType::SrtResource, {}); + block->RemoveInstruction(&resource); + block->InsertInstructionBefore(patch.inst, &resource); + IrValue& slotConstant = builder.Constant(patch.slot); + IrValue& flat = builder.Emit(IrOpcode::ReadConst, IrType::U32, {&resource, &slotConstant}); + block->RemoveInstruction(&flat); + block->InsertInstructionBefore(patch.inst, &flat); + for (const auto& use : std::vector(patch.inst->OperandUses())) { + use.user->ReplaceArgument(use.operand, &flat); + } + for (auto& info : _program.Metadata().blockInfo) { + if (info.condition != nullptr && info.condition->Resolve() == patch.inst) { + info.condition = ♭ + } + if (info.indirectTarget != nullptr && info.indirectTarget->Resolve() == patch.inst) { + info.indirectTarget = ♭ + } + } + if (patch.keep) { + const auto memory = patch.inst->Flags().index; + if (memory < _program.Resources().memoryInfo.size()) { + _program.Resources().memoryInfo[memory].planningOnly = true; + } + builder.Emit(IrOpcode::ReferenceU32, IrType::Void, {patch.inst}); + } + } +} + +} diff --git a/core/shader/recompiler/Optimization/src/SrtWalker/SrtRuntimeValidator.cpp b/core/shader/recompiler/Optimization/src/SrtWalker/SrtRuntimeValidator.cpp new file mode 100644 index 000000000..9fdf4415a --- /dev/null +++ b/core/shader/recompiler/Optimization/src/SrtWalker/SrtRuntimeValidator.cpp @@ -0,0 +1,145 @@ +#include "Optimization/SrtWalker/SrtRuntimeValidator.hpp" +#include "Optimization/SrtWalker/SrtInstructionPredicates.hpp" + +#include "IntermediateRepresentation/IrBuilder.hpp" + +namespace ShaderRecompiler::Detail { + +bool RuntimeValidator::ValidateArguments(IrValue& inst, bool requireUniform) { + for (std::size_t index = 0; index < inst.ArgumentCount(); index++) { + if (!Validate(inst.Argument(index), requireUniform)) return false; + } + return true; +} + +bool RuntimeValidator::Validate(IrValue* raw, bool requireUniform) { + IrValue* value = raw->Resolve(); + if (_type == RuntimeValueType::Integer && TypesOverlap(value->Type(), IrType::F16 | IrType::F32 | IrType::Vec2F32)) { + return false; + } + if (value->Opcode() == IrOpcode::Void) { + if (!requireUniform) return true; + switch (value->Type()) { + case IrType::Bool: + case IrType::U8: + case IrType::U16: + case IrType::U32: + case IrType::U64: + case IrType::F32: return true; + default: return false; + } + } + IrValue& inst = *value; + if (!requireUniform && _validatedDependencies.contains(&inst)) return true; + if (!_visiting.insert(&inst).second) { + return !requireUniform; + } + const auto finish = [&](bool valid) { + _visiting.erase(&inst); + if (valid && !requireUniform) _validatedDependencies.insert(&inst); + return valid; + }; + const auto op = inst.Opcode(); + if (op == IrOpcode::ReadConst) { + IrValue* slot = inst.ArgumentCount() == 2 ? inst.Argument(1)->Resolve() : nullptr; + if (inst.ArgumentCount() != 2 || inst.Argument(0)->Resolve()->Opcode() == IrOpcode::Void || inst.Argument(0)->Resolve()->Opcode() != IrOpcode::GetSrtResource || slot == nullptr || !slot->HasImmediate() || slot->Type() != IrType::U32 || slot->ImmediateU32() >= _program.srtReads.size()) { + return finish(false); + } + if (_type == RuntimeValueType::Integer) { + const auto activeMask = _activeMask; + _activeMask = nullptr; + const bool valid = Validate(_program.srtReads[slot->ImmediateU32()].value); + _activeMask = activeMask; + if (!valid) return finish(false); + } + } + if (!requireUniform) return finish(ValidateArguments(inst, false)); + if (_activeMask != nullptr && IsRuntimeSelect(op) && inst.ArgumentCount() == 3 && inst.Argument(0)->Resolve() == _activeMask) { + if (_type == RuntimeValueType::Integer && !Validate(inst.Argument(2), false)) { + return finish(false); + } + return finish(Validate(inst.Argument(1))); + } + if (op == IrOpcode::UndefU1 || op == IrOpcode::UndefU8 || op == IrOpcode::UndefU16 || op == IrOpcode::UndefU32 || op == IrOpcode::UndefU64 || op == IrOpcode::Void) { + return finish(false); + } + if (op == IrOpcode::GetUserData) { + if (inst.ArgumentCount() != 1 || inst.Argument(0)->Type() != IrType::ScalarReg) { + return finish(false); + } + const auto reg = RegIndex(static_cast(inst.Argument(0)->Register().index)); + if (reg < _program.userDataBase || reg - _program.userDataBase >= _program.userDataCount) { + return finish(false); + } + return finish(true); + } + if (op == IrOpcode::GetShaderBase) { + if (inst.ArgumentCount() != 0) { + return finish(false); + } + return finish(true); + } + if (op == IrOpcode::Phi) { + if (_type == RuntimeValueType::Integer && !ValidateArguments(inst, false)) { + return finish(false); + } + IrValue* invariant = ResolveInvariantPhi(_program, &inst); + if (invariant == nullptr) { + return finish(false); + } + return finish(Validate(invariant)); + } + if (op == IrOpcode::ReadFirstLane) { + if (inst.ArgumentCount() != 2 || inst.Argument(0)->Type() != IrType::U32 || inst.Argument(1)->Type() != IrType::Bool) { + return finish(false); + } + if (_type == RuntimeValueType::Integer && !Validate(inst.Argument(1), false)) { + return finish(false); + } + const auto activeMask = _activeMask; + _activeMask = inst.Argument(1)->Resolve(); + const bool valid = Validate(inst.Argument(0)); + _activeMask = activeMask; + return finish(valid); + } + if (op == IrOpcode::GetSrtResource) { + if (inst.ArgumentCount() != 0) { + return finish(false); + } + return finish(true); + } + if (op == IrOpcode::LoadAddressU32 || op == IrOpcode::ReadConstBuffer) { + const auto expected = op == IrOpcode::LoadAddressU32 ? IrOpcode::GetAddressResource : IrOpcode::GetBufferResource; + IrValue* handle = inst.ArgumentCount() != 0 ? inst.Argument(0)->Resolve() : nullptr; + if (!IsRawRead(_program, inst) || handle == nullptr || handle->Opcode() != expected) { + return finish(false); + } + } else if (op == IrOpcode::CompositeExtractU64) { + IrValue* index = inst.ArgumentCount() == 2 ? inst.Argument(1)->Resolve() : nullptr; + if (index == nullptr || !index->HasImmediate() || index->Type() != IrType::U32 || index->ImmediateU32() >= 2u) { + return finish(false); + } + } else if (op == IrOpcode::CompositeExtractU32x2) { + IrValue* source = inst.ArgumentCount() == 2 ? inst.Argument(0)->Resolve() : nullptr; + IrValue* index = inst.ArgumentCount() == 2 ? inst.Argument(1)->Resolve() : nullptr; + if (source == nullptr || index == nullptr || !index->HasImmediate() || index->Type() != IrType::U32 || index->ImmediateU32() >= 2u || (source->Opcode() != IrOpcode::CompositeConstructU32x2 && source->Opcode() != IrOpcode::IAddCarry32)) { + return finish(false); + } + } + if (IsDescriptorHandle(op)) { + std::size_t expected = 4u; + if (op == IrOpcode::GetImageResource) { + expected = 8u; + } else if (op == IrOpcode::GetAddressResource) { + expected = 2u; + } + if (inst.ArgumentCount() != expected) { + return finish(false); + } + } else if (op != IrOpcode::ReadConst && op != IrOpcode::ReadConstBuffer && op != IrOpcode::LoadAddressU32 && !IsRuntimeUniformOp(op)) { + return finish(false); + } + return finish(ValidateArguments(inst, true)); +} + +} diff --git a/core/shader/recompiler/Optimization/src/SrtWalker/SrtWalker.cpp b/core/shader/recompiler/Optimization/src/SrtWalker/SrtWalker.cpp new file mode 100644 index 000000000..34edcda12 --- /dev/null +++ b/core/shader/recompiler/Optimization/src/SrtWalker/SrtWalker.cpp @@ -0,0 +1,64 @@ +#include "Optimization/SrtWalker.hpp" +#include "Optimization/SrtWalker/SrtDescriptorEvaluation.hpp" +#include "Optimization/SrtWalker/SrtEvaluator.hpp" +#include "Optimization/SrtWalker/SrtPlanBuilder.hpp" +#include "Optimization/SrtWalker/SrtRuntimeValidator.hpp" + +#include + +namespace ShaderRecompiler { + +void SrtWalker::BuildPlan(IrProgram& program) const { + if (program.Resources().resourceTrackingComplete) { + throw std::runtime_error("shader SRT planning failed: cannot rebuild SRT after resource tracking"); + } + program.Resources().srtPlanComplete = false; + Detail::PlanBuilder(program).Run(); + program.Resources().srtPlanComplete = true; +} + +bool SrtWalker::ValidateRuntimeValue(const IrResourcePlan& program, const IrValue* value, RuntimeValueType type) const { + return Detail::RuntimeValidator(program, type).Run(const_cast(value)); +} + +void SrtWalker::EvaluateUniformValues(const IrResourcePlan& program, std::span values, const SrtRuntime& runtime, std::span results) const { + if (values.size() != results.size()) { + throw std::runtime_error("SrtWalker::EvaluateUniformValues value and result counts differ"); + } + auto clean = runtime; + clean.readMemory = runtime.readSpecializationMemory != nullptr ? runtime.readSpecializationMemory : +[](void*, std::uint64_t, std::uint32_t*) { return false; }; + Detail::Evaluator evaluator(program, clean); + for (std::size_t i = 0; i < values.size(); ++i) { + if (!evaluator.Evaluate(values[i], results[i])) { + throw std::runtime_error("SrtWalker::EvaluateUniformValues failed to evaluate a uniform value"); + } + } +} + +void SrtWalker::EvaluateDescriptorSource(const IrResourcePlan& program, std::uint32_t source, const SrtRuntime& runtime, DescriptorValue& result) const { + std::vector results; + EvaluateDescriptorSources(program, std::span {&source, 1}, runtime, results); + result = results.front(); +} + +void SrtWalker::EvaluateDescriptorSources(const IrResourcePlan& program, std::span sources, const SrtRuntime& runtime, std::vector& results) const { + std::vector ignored; + std::vector active; + if (!Detail::EvaluateRuntimeSourcesImpl(program, sources, runtime, results, ignored, false, {}, active)) { + throw std::runtime_error("SrtWalker::EvaluateDescriptorSources failed to evaluate descriptor sources"); + } +} + +void SrtWalker::EvaluateRuntimeSources(const IrResourcePlan& program, std::span sources, const SrtRuntime& runtime, std::vector& results, std::vector& flat, std::span cleanFlatSlots, std::vector& activeSources) const { + if (!Detail::EvaluateRuntimeSourcesImpl(program, sources, runtime, results, flat, true, cleanFlatSlots, activeSources)) { + throw std::runtime_error("SrtWalker::EvaluateRuntimeSources failed to evaluate runtime sources"); + } +} + +void SrtWalker::Walk(const IrResourcePlan& program, const SrtRuntime& runtime, std::vector& flat) const { + std::vector ignored; + std::vector active; + EvaluateRuntimeSources(program, {}, runtime, ignored, flat, {}, active); +} + +}