Reuse scalar evaluation storage

Co-authored-by: MehmetCambaz <mcambaz18@gmail.com>
Co-authored-by: Leclowndu93150 <xdr.a123454321@gmail.com>
This commit is contained in:
nmzik
2026-09-23 10:27:00 +04:00
parent 90471836f7
commit d6dac92da1
4 changed files with 83 additions and 18 deletions
@@ -14,6 +14,7 @@
#include <array> #include <array>
#include <bit> #include <bit>
#include <deque>
#include <list> #include <list>
#include <memory> #include <memory>
#include <optional> #include <optional>
@@ -509,6 +510,16 @@ struct UniformFillPlan {
// Immutable runtime resource analysis retained by the shader cache. It owns descriptor/SRT, // Immutable runtime resource analysis retained by the shader cache. It owns descriptor/SRT,
// uniform condition and fill values without retaining translated blocks. // uniform condition and fill values without retaining translated blocks.
struct ResourcePlan { struct ResourcePlan {
struct EvaluationContext {
struct Entry {
uint64_t value = 0;
uint64_t generation = 0;
};
std::vector<Entry> values;
uint64_t generation = 0;
};
ResourcePlan() = default; ResourcePlan() = default;
~ResourcePlan(); ~ResourcePlan();
@@ -533,6 +544,10 @@ struct ResourcePlan {
bool resource_tracking_complete = false; bool resource_tracking_complete = false;
ShaderInfo info; ShaderInfo info;
UniformFillPlan uniform_fill; UniformFillPlan uniform_fill;
// GPU-thread scratch. Each nested clean/EXEC evaluation has its own reusable memo.
mutable std::deque<EvaluationContext> evaluation_contexts;
mutable uint32_t evaluation_value_count = 0;
mutable uint32_t evaluation_depth = 0;
}; };
struct Program: ResourcePlan { struct Program: ResourcePlan {
@@ -121,6 +121,13 @@ public:
[[nodiscard]] Block* PhiBlock(size_t index) const; [[nodiscard]] Block* PhiBlock(size_t index) const;
[[nodiscard]] Block* Parent() const; [[nodiscard]] Block* Parent() const;
[[nodiscard]] const std::vector<Use>& Uses() const; [[nodiscard]] const std::vector<Use>& Uses() const;
// Runtime indices belong to the resource plan that owns this instruction.
[[nodiscard]] uint32_t EvaluationIndex(uint32_t& count) const {
if (evaluation_index == UINT32_MAX) {
evaluation_index = count++;
}
return evaluation_index;
}
void SetParent(Block* block); void SetParent(Block* block);
void SetArg(size_t index, Value value); void SetArg(size_t index, Value value);
@@ -155,6 +162,7 @@ private:
std::vector<Value> args; std::vector<Value> args;
std::vector<Block*> phi_blocks; std::vector<Block*> phi_blocks;
std::vector<Use> uses; std::vector<Use> uses;
mutable uint32_t evaluation_index = UINT32_MAX;
}; };
} // namespace Libs::Graphics::ShaderRecompiler::IR } // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -8,7 +8,6 @@
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include <fmt/format.h> #include <fmt/format.h>
#include <unordered_map>
#include <unordered_set> #include <unordered_set>
namespace Libs::Graphics::ShaderRecompiler::IR { namespace Libs::Graphics::ShaderRecompiler::IR {
@@ -464,7 +463,12 @@ public:
std::span<const uint8_t> clean_flat_slots = {}, Evaluator* clean_evaluator = nullptr, std::span<const uint8_t> clean_flat_slots = {}, Evaluator* clean_evaluator = nullptr,
Value active_mask = {}) Value active_mask = {})
: m_program(program), m_runtime(runtime), m_clean_flat_slots(clean_flat_slots), : m_program(program), m_runtime(runtime), m_clean_flat_slots(clean_flat_slots),
m_clean_evaluator(clean_evaluator), m_active_mask(active_mask.Resolve()) {} m_clean_evaluator(clean_evaluator), m_active_mask(active_mask.Resolve()),
m_context(AcquireContext(program)) {}
~Evaluator() { --m_program.evaluation_depth; }
Evaluator(const Evaluator&) = delete;
Evaluator& operator=(const Evaluator&) = delete;
bool Evaluate(Value value, uint32_t& result) { bool Evaluate(Value value, uint32_t& result) {
uint64_t wide = 0; uint64_t wide = 0;
@@ -476,6 +480,15 @@ public:
} }
private: private:
static ResourcePlan::EvaluationContext& AcquireContext(const ResourcePlan& program) {
if (program.evaluation_depth == program.evaluation_contexts.size()) {
program.evaluation_contexts.emplace_back();
}
auto& context = program.evaluation_contexts[program.evaluation_depth++];
context.generation += 2;
return context;
}
static float Float32(uint64_t bits) { static float Float32(uint64_t bits) {
return std::bit_cast<float>(static_cast<uint32_t>(bits)); return std::bit_cast<float>(static_cast<uint32_t>(bits));
} }
@@ -497,30 +510,33 @@ private:
if (inst == nullptr) { if (inst == nullptr) {
return false; return false;
} }
if (!m_reserved) {
m_cache.reserve(m_program.value_storage.size());
m_visiting.reserve(m_program.value_storage.size());
m_reserved = true;
}
if (!m_active_mask.IsEmpty() && IsRuntimeSelect(inst->GetOpcode()) && if (!m_active_mask.IsEmpty() && IsRuntimeSelect(inst->GetOpcode()) &&
inst->NumArgs() == 3 && inst->Arg(0).Resolve() == m_active_mask) { inst->NumArgs() == 3 && inst->Arg(0).Resolve() == m_active_mask) {
return EvaluateWide(inst->Arg(1), result); return EvaluateWide(inst->Arg(1), result);
} }
if (const auto found = m_cache.find(inst); found != m_cache.end()) { const auto index = inst->EvaluationIndex(m_program.evaluation_value_count);
result = found->second; if (index >= m_context.values.size()) {
m_context.values.resize(m_program.evaluation_value_count);
}
if (m_context.values[index].generation == m_context.generation) {
result = m_context.values[index].value;
return true; return true;
} }
if (std::ranges::find(m_visiting, inst) != m_visiting.end()) { // The low generation bit marks an instruction that is still being evaluated.
if (m_context.values[index].generation == (m_context.generation | 1u)) {
return false; return false;
} }
m_visiting.push_back(inst); m_context.values[index].generation = m_context.generation | 1u;
uint64_t out = 0; uint64_t out = 0;
const bool evaluated = EvaluateInst(*inst, out); const bool evaluated = EvaluateInst(*inst, out);
m_visiting.pop_back(); // Recursive evaluation may grow the dense memo vector.
auto& memo = m_context.values[index];
if (!evaluated) { if (!evaluated) {
memo.generation = 0;
return false; return false;
} }
m_cache.emplace(inst, out); memo.value = out;
memo.generation = m_context.generation;
result = out; result = out;
return true; return true;
} }
@@ -977,9 +993,7 @@ private:
std::span<const uint8_t> m_clean_flat_slots; std::span<const uint8_t> m_clean_flat_slots;
Evaluator* m_clean_evaluator = nullptr; Evaluator* m_clean_evaluator = nullptr;
Value m_active_mask; Value m_active_mask;
std::unordered_map<const Inst*, uint64_t> m_cache; ResourcePlan::EvaluationContext& m_context;
std::vector<const Inst*> m_visiting;
bool m_reserved = false;
}; };
const DescriptorSource* Source(const ResourcePlan& program, uint32_t source) { const DescriptorSource* Source(const ResourcePlan& program, uint32_t source) {
+30 -2
View File
@@ -972,11 +972,13 @@ void TestFmaskLoadSpecialization() {
"FMASK load did not lower to a value vector"); "FMASK load did not lower to a value vector");
result = vector->Arg(0); result = vector->Arg(0);
uint32_t value = 0; uint32_t value = 0;
Check(EvaluateUniformValues(plan, {&result, 1}, {.user_data = user_data}, {&value, 1}) && Check(EvaluateUniformValues(fixture.program, {&result, 1},
{.user_data = user_data}, {&value, 1}) &&
value == 0x76543210u, value == 0x76543210u,
"FMASK load did not return the native sample-to-fragment mapping"); "FMASK load did not return the native sample-to-fragment mapping");
user_data[8] = 1; user_data[8] = 1;
Check(EvaluateUniformValues(plan, {&result, 1}, {.user_data = user_data}, {&value, 1}) && Check(EvaluateUniformValues(fixture.program, {&result, 1},
{.user_data = user_data}, {&value, 1}) &&
value == 0u, value == 0u,
"inactive FMASK load did not preserve the execution mask"); "inactive FMASK load did not preserve the execution mask");
ShaderComputeInputInfo compute{}; ShaderComputeInputInfo compute{};
@@ -1184,6 +1186,14 @@ void TestSrtFlatteningAndRuntimeMemoization() {
flat == std::vector<uint32_t>{0xdeadbeefu} && memory.reads == 1, flat == std::vector<uint32_t>{0xdeadbeefu} && memory.reads == 1,
"descriptor and flat SRT evaluation did not share one memoized read"); "descriptor and flat SRT evaluation did not share one memoized read");
memory.reads = 0;
memory.words[1] = 0x12345678u;
Check(EvaluateRuntimeSources(fixture.program, std::span{&request, 1}, runtime,
descriptors, flat, {}, active_sources) &&
descriptors[0].dwords[0] == 0x12345678u &&
flat == std::vector<uint32_t>{0x12345678u} && memory.reads == 1,
"repeated runtime evaluation reused stale scalar memory");
memory.reads = 0; memory.reads = 0;
memory.fail_after = 0; memory.fail_after = 0;
descriptors = {{{1u}, 1u}}; descriptors = {{{1u}, 1u}};
@@ -1196,6 +1206,24 @@ void TestSrtFlatteningAndRuntimeMemoization() {
active_sources == std::vector<uint8_t>{3u}, active_sources == std::vector<uint8_t>{3u},
"runtime evaluation failure was not transactional"); "runtime evaluation failure was not transactional");
memory.fail_after = UINT32_MAX;
Check(EvaluateRuntimeSources(fixture.program, std::span{&request, 1}, runtime,
descriptors, flat, {}, active_sources) &&
descriptors[0].dwords[0] == 0x12345678u && memory.reads == 1,
"failed runtime evaluation left a value marked as visiting");
auto detached = ExtractResourcePlan(fixture.program);
Check(EvaluateRuntimeSources(detached, std::span{&request, 1}, runtime,
descriptors, flat, {}, active_sources),
"detached resource plan did not evaluate");
auto moved = std::move(detached);
memory.reads = 0;
memory.words[1] = 0x87654321u;
Check(EvaluateRuntimeSources(moved, std::span{&request, 1}, runtime,
descriptors, flat, {}, active_sources) &&
descriptors[0].dwords[0] == 0x87654321u && memory.reads == 1,
"moving a cached resource plan lost its evaluation state");
ShaderComputeInputInfo compute{}; ShaderComputeInputInfo compute{};
CollectShaderInfo(fixture.program, {.compute = &compute}); CollectShaderInfo(fixture.program, {.compute = &compute});
AllocateBindings(fixture.program); AllocateBindings(fixture.program);