From c01d0bb24f9ad66ea08470496b60a0dd39b79635 Mon Sep 17 00:00:00 2001 From: milescoviello Date: Wed, 28 Oct 2026 10:12:08 +0000 Subject: [PATCH] fix(shader): move a cached program's use lists to the heap and drop its arena A program kept in a SourceEntry held its use-list arena until the entry was destroyed, so the buffers of values ReclaimUnusedValues dropped, and the buffers every use list outgrew, stayed allocated for as long as the program was cached. Before a program is cached, ReleaseUseArena now copies each value's use list into a heap vector and frees the arena. Values created after that draw from a new arena. Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceAgcDriver/tests/IrReclaim.cpp | 17 +++++++++++++++++ .../IntermediateRepresentation/IrProgram.hpp | 1 + .../IntermediateRepresentation/IrValue.hpp | 1 + .../src/IrProgram.cpp | 5 +++++ .../IntermediateRepresentation/src/IrValue.cpp | 5 +++++ core/shader/recompiler/Recompiler.cpp | 1 + 6 files changed, 30 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/tests/IrReclaim.cpp b/core/libs/prx/libSceAgcDriver/tests/IrReclaim.cpp index 9c8a8e5769..4256def9d9 100644 --- a/core/libs/prx/libSceAgcDriver/tests/IrReclaim.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/IrReclaim.cpp @@ -96,6 +96,23 @@ int main() { keptReader.AddArgument(&kept); replaced = std::move(replacement); if (kept.UseCount() != 1u || kept.OperandUses().front().user != &keptReader) throw std::runtime_error("move assignment invalidated the assigned program's use lists"); + IrProgram cached; + auto& source = cached.CreateValue(IrOpcode::IAdd32, IrType::U32); + std::vector users; + for (std::uint32_t number = 0; number < 40u; ++number) { + auto& user = cached.CreateValue(IrOpcode::IAdd32, IrType::U32); + user.AddArgument(&source); + users.push_back(&user); + } + cached.ReleaseUseArena(); + if (source.OperandUses().get_allocator().Arena() != nullptr || users.front()->OperandUses().get_allocator().Arena() != nullptr) throw std::runtime_error("releasing the use arena left a use list in it"); + if (source.UseCount() != users.size()) throw std::runtime_error("releasing the use arena lost a use"); + for (std::size_t index = 0; index < users.size(); ++index) { + if (source.OperandUses()[index].user != users[index] || users[index]->Argument(0) != &source) throw std::runtime_error("releasing the use arena reordered a use list"); + } + auto& late = cached.CreateValue(IrOpcode::IAdd32, IrType::U32); + late.AddArgument(&source); + if (late.OperandUses().get_allocator().Arena() == nullptr || source.UseCount() != users.size() + 1u || source.OperandUses().back().user != &late) throw std::runtime_error("a value created after releasing the use arena did not get a new one"); IrProgram moved = std::move(program); if (value.Id() != id || value.ImmediateU32() != 42u || moved.ReclaimUnusedValues() != 1u) throw std::runtime_error("moving a program invalidated its allocation owner"); IrProgram assigned; diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrProgram.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrProgram.hpp index a93cd962c5..bb66d00d95 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrProgram.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrProgram.hpp @@ -32,6 +32,7 @@ public: IrValue& CreateValue(IrOpcode opcode, IrType type, std::uint64_t flags); void SetEntryBlock(IrBlock& block); std::size_t ReclaimUnusedValues(); + void ReleaseUseArena(); [[nodiscard]] std::vector& BlockOrder(); [[nodiscard]] const std::vector& BlockOrder() const; diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrValue.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrValue.hpp index a452593474..699436a0e3 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrValue.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrValue.hpp @@ -184,6 +184,7 @@ public: [[nodiscard]] IrValue* Argument(std::size_t index) const; [[nodiscard]] IrBlock* PhiBlock(std::size_t index) const; [[nodiscard]] const IrUseList& OperandUses() const; + void MoveUsesToHeap(); [[nodiscard]] std::uint64_t ImmediateU64() const; [[nodiscard]] std::uint16_t ImmediateF16Bits() const; [[nodiscard]] float ImmediateF32() const; diff --git a/core/shader/recompiler/IntermediateRepresentation/src/IrProgram.cpp b/core/shader/recompiler/IntermediateRepresentation/src/IrProgram.cpp index 8d134e86f1..34a251932b 100644 --- a/core/shader/recompiler/IntermediateRepresentation/src/IrProgram.cpp +++ b/core/shader/recompiler/IntermediateRepresentation/src/IrProgram.cpp @@ -274,6 +274,11 @@ std::size_t IrProgram::ReclaimUnusedValues() { return removed; } +void IrProgram::ReleaseUseArena() { + for (const auto& value : values) value->MoveUsesToHeap(); + useArena = IrUseArena{}; +} + std::vector>& IrProgram::Blocks() { return blocks; } diff --git a/core/shader/recompiler/IntermediateRepresentation/src/IrValue.cpp b/core/shader/recompiler/IntermediateRepresentation/src/IrValue.cpp index b910a7bd6a..b549aa7393 100644 --- a/core/shader/recompiler/IntermediateRepresentation/src/IrValue.cpp +++ b/core/shader/recompiler/IntermediateRepresentation/src/IrValue.cpp @@ -125,6 +125,11 @@ const IrUseList& IrValue::OperandUses() const { return operandUses; } +void IrValue::MoveUsesToHeap() { + if (operandUses.get_allocator().Arena() == nullptr) return; + operandUses = IrUseList(operandUses.begin(), operandUses.end(), IrUseAllocator{}); +} + std::uint64_t IrValue::ImmediateU64() const { return immediateBits; } diff --git a/core/shader/recompiler/Recompiler.cpp b/core/shader/recompiler/Recompiler.cpp index 73da42c9aa..2dced2f200 100644 --- a/core/shader/recompiler/Recompiler.cpp +++ b/core/shader/recompiler/Recompiler.cpp @@ -536,6 +536,7 @@ std::shared_ptr getSource(const RecompileRequest& request, ShaderPr ResourceProgram resource(request, preparation, reclaim); source->plan = std::move(resource.plan); if (!hasPreparedArtifact && std::ranges::none_of(source->variants, [&](const auto& variant) { return sameLayout(variant->layout, request.layout); })) { + resource.program->ReleaseUseArena(); source->program = std::move(resource.program); } } catch (...) {