feat(shader): add srt walker impl + refactor
This commit is contained in:
parent
3dc20758a5
commit
3400a2b6e5
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
#ifndef CORE_SHADER_RECOMPILIER_OPTIMIZATION_SRTWALKER_SRTADDRESSARITHMETIC_HPP
|
||||
#define CORE_SHADER_RECOMPILIER_OPTIMIZATION_SRTWALKER_SRTADDRESSARITHMETIC_HPP
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
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
|
||||
|
|
@ -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 <cstdint>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace ShaderRecompiler::Detail {
|
||||
|
||||
bool EvaluateRuntimeSourcesImpl(const IrResourcePlan& program, std::span<const std::uint32_t> sources, const SrtRuntime& runtime, std::vector<DescriptorValue>& results, std::vector<std::uint32_t>& flat, bool evaluateFlat, std::span<const std::uint8_t> cleanFlatSlots, std::vector<std::uint8_t>& activeSources);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -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 <cstdint>
|
||||
#include <string>
|
||||
|
||||
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
|
||||
|
|
@ -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 <cstdint>
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace ShaderRecompiler::Detail {
|
||||
|
||||
class Evaluator {
|
||||
public:
|
||||
Evaluator(const IrResourcePlan& program, const SrtRuntime& runtime, std::span<const std::uint8_t> 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<const std::uint8_t> _cleanFlatSlots;
|
||||
Evaluator* _cleanEvaluator = nullptr;
|
||||
IrValue* _activeMask = nullptr;
|
||||
std::unordered_map<IrValue*, std::uint64_t> _cache;
|
||||
std::vector<IrValue*> _visiting;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -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
|
||||
|
|
@ -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 <cstdint>
|
||||
#include <vector>
|
||||
|
||||
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<IrValue*> _visiting;
|
||||
std::vector<IrValue*> _visited;
|
||||
std::vector<Patch> _patches;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -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 <unordered_set>
|
||||
|
||||
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<IrValue*> _visiting;
|
||||
std::unordered_set<IrValue*> _validatedDependencies;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
#include "Optimization/SrtWalker.hpp"
|
||||
#include <stdexcept>
|
||||
|
||||
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<IrValue* const> values, const SrtRuntime& runtime, std::span<std::uint32_t> 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<const std::uint32_t> sources, const SrtRuntime& runtime, std::vector<DescriptorValue>& results) const {
|
||||
throw std::runtime_error("SrtWalker::EvaluateDescriptorSources not implemented");
|
||||
}
|
||||
|
||||
void SrtWalker::EvaluateRuntimeSources(const IrResourcePlan& program, std::span<const std::uint32_t> sources, const SrtRuntime& runtime, std::vector<DescriptorValue>& results, std::vector<std::uint32_t>& flat, std::span<const std::uint8_t> cleanFlatSlots, std::vector<std::uint8_t>& activeSources) const {
|
||||
throw std::runtime_error("SrtWalker::EvaluateRuntimeSources not implemented");
|
||||
}
|
||||
|
||||
void SrtWalker::Walk(const IrResourcePlan& program, const SrtRuntime& runtime, std::vector<std::uint32_t>& flat) const {
|
||||
throw std::runtime_error("SrtWalker::Walk not implemented");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<std::uint64_t>(offset);
|
||||
if (magnitude > base) {
|
||||
return false;
|
||||
}
|
||||
result = base - magnitude;
|
||||
return true;
|
||||
}
|
||||
const auto magnitude = static_cast<std::uint64_t>(offset);
|
||||
if (magnitude > AddressMask - base) {
|
||||
return false;
|
||||
}
|
||||
result = base + magnitude;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
#include "Optimization/SrtWalker/SrtDescriptorEvaluation.hpp"
|
||||
#include "Optimization/SrtWalker/SrtEvaluator.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
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<const std::uint32_t> sources, const SrtRuntime& runtime, std::vector<DescriptorValue>& results, std::vector<std::uint32_t>& flat, bool evaluateFlat, std::span<const std::uint8_t> cleanFlatSlots, std::vector<std::uint8_t>& 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<std::uint8_t> 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<std::uint8_t> visited(program.controlFlow.size());
|
||||
std::vector<std::uint32_t> 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<DescriptorValue> 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<std::uint32_t> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
#include "Optimization/SrtWalker/SrtDiagnostics.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <stdexcept>
|
||||
|
||||
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<unsigned long long>(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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,480 @@
|
|||
#include "Optimization/SrtWalker/SrtEvaluator.hpp"
|
||||
#include "Optimization/SrtWalker/SrtAddressArithmetic.hpp"
|
||||
#include "Optimization/SrtWalker/SrtInstructionPredicates.hpp"
|
||||
#include "IntermediateRepresentation/IrBuilder.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
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<std::uint32_t>(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<float>(static_cast<std::uint32_t>(bits)); }
|
||||
|
||||
std::uint64_t Evaluator::Float32Bits(float value) { return std::bit_cast<std::uint32_t>(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<std::uint32_t>(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<std::uint64_t>(static_cast<std::uint32_t>(lhs)) + static_cast<std::uint32_t>(rhs);
|
||||
result = component == 0u ? static_cast<std::uint32_t>(sum) : static_cast<std::uint32_t>(sum >> 32u);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Evaluator::EvaluateRawRead(IrValue& inst, std::uint64_t& result) {
|
||||
const auto flags = inst.Flags<MemoryFlags>();
|
||||
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<std::uint32_t>(low)) & AddressMask;
|
||||
const auto immediate = static_cast<std::int64_t>(static_cast<std::int32_t>(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<std::uint64_t>(immediate) + static_cast<std::uint32_t>(offset);
|
||||
const auto aligned = byteOffset & ~std::uint64_t {3};
|
||||
const auto stride = (static_cast<std::uint32_t>(high) >> 16u) & 0x3fffu;
|
||||
const auto size = stride == 0u ? static_cast<std::uint64_t>(static_cast<std::uint32_t>(records)) : static_cast<std::uint64_t>(stride) * static_cast<std::uint32_t>(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<std::int64_t>(static_cast<std::uint32_t>(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<const void*>(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<ScalarReg>(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<std::uint32_t>(a) | (static_cast<std::uint64_t>(static_cast<std::uint32_t>(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<std::uint32_t>(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<std::uint32_t>(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<std::uint32_t>(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<std::uint32_t>(a), static_cast<std::uint32_t>(b));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case IrOpcode::ConvertF32U32:
|
||||
if (Arg(inst, 0, a)) {
|
||||
result = Float32Bits(static_cast<float>(static_cast<std::uint32_t>(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<double>(value) > 4294967295.0) {
|
||||
return false;
|
||||
}
|
||||
result = static_cast<std::uint32_t>(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<std::uint32_t>(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<std::uint32_t>(a | b);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case IrOpcode::BitwiseXor32:
|
||||
if (binary()) {
|
||||
result = static_cast<std::uint32_t>(a ^ b);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case IrOpcode::BitwiseNot32:
|
||||
if (Arg(inst, 0, a)) {
|
||||
result = ~static_cast<std::uint32_t>(a);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case IrOpcode::ShiftLeftLogical32:
|
||||
if (binary()) {
|
||||
result = static_cast<std::uint32_t>(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<std::uint32_t>(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::uint32_t>(std::bit_cast<std::int32_t>(static_cast<std::uint32_t>(a)) >> (b & 31u));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case IrOpcode::ShiftRightArithmetic64:
|
||||
if (binary()) {
|
||||
result = static_cast<std::uint64_t>(std::bit_cast<std::int64_t>(a) >> (b & 63u));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case IrOpcode::BitFieldUExtract:
|
||||
if (ternary()) {
|
||||
const auto offset = static_cast<std::uint32_t>(b);
|
||||
const auto width = static_cast<std::uint32_t>(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<std::uint32_t>(a) >> offset) & mask;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case IrOpcode::BitFieldSExtract:
|
||||
if (ternary()) {
|
||||
const auto offset = static_cast<std::uint32_t>(b);
|
||||
const auto width = static_cast<std::uint32_t>(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<std::uint32_t>(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<std::uint32_t>(c);
|
||||
const auto width = static_cast<std::uint32_t>(d);
|
||||
if (offset > 32u || width > 32u - offset) {
|
||||
return false;
|
||||
}
|
||||
if (width == 0u) {
|
||||
result = static_cast<std::uint32_t>(a);
|
||||
return true;
|
||||
}
|
||||
const auto mask = width == 32u ? 0xffffffffu : ((std::uint32_t {1} << width) - 1u) << offset;
|
||||
result = (static_cast<std::uint32_t>(a) & ~mask) | ((static_cast<std::uint32_t>(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<std::uint32_t>(a) == static_cast<std::uint32_t>(b) ? 1u : 0u;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case IrOpcode::INotEqual32:
|
||||
if (binary()) {
|
||||
result = static_cast<std::uint32_t>(a) != static_cast<std::uint32_t>(b) ? 1u : 0u;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case IrOpcode::ULessThan32:
|
||||
if (binary()) {
|
||||
result = static_cast<std::uint32_t>(a) < static_cast<std::uint32_t>(b) ? 1u : 0u;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case IrOpcode::UGreaterThan32:
|
||||
if (binary()) {
|
||||
result = static_cast<std::uint32_t>(a) > static_cast<std::uint32_t>(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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<MemoryFlags>().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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
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<MemoryFlags>();
|
||||
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<MemoryFlags>().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<std::uint32_t>(_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<IrUse>(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<MemoryFlags>().index;
|
||||
if (memory < _program.Resources().memoryInfo.size()) {
|
||||
_program.Resources().memoryInfo[memory].planningOnly = true;
|
||||
}
|
||||
builder.Emit(IrOpcode::ReferenceU32, IrType::Void, {patch.inst});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<ScalarReg>(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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 <stdexcept>
|
||||
|
||||
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<IrValue*>(value));
|
||||
}
|
||||
|
||||
void SrtWalker::EvaluateUniformValues(const IrResourcePlan& program, std::span<IrValue* const> values, const SrtRuntime& runtime, std::span<std::uint32_t> 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<DescriptorValue> results;
|
||||
EvaluateDescriptorSources(program, std::span {&source, 1}, runtime, results);
|
||||
result = results.front();
|
||||
}
|
||||
|
||||
void SrtWalker::EvaluateDescriptorSources(const IrResourcePlan& program, std::span<const std::uint32_t> sources, const SrtRuntime& runtime, std::vector<DescriptorValue>& results) const {
|
||||
std::vector<std::uint32_t> ignored;
|
||||
std::vector<std::uint8_t> 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<const std::uint32_t> sources, const SrtRuntime& runtime, std::vector<DescriptorValue>& results, std::vector<std::uint32_t>& flat, std::span<const std::uint8_t> cleanFlatSlots, std::vector<std::uint8_t>& 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<std::uint32_t>& flat) const {
|
||||
std::vector<DescriptorValue> ignored;
|
||||
std::vector<std::uint8_t> active;
|
||||
EvaluateRuntimeSources(program, {}, runtime, ignored, flat, {}, active);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue