From c191342fa1858309aaa884d9696666b97bba3420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C5=A0l=C3=A9gl?= Date: Thu, 15 Oct 2026 12:21:52 +0200 Subject: [PATCH] fix(shader): repeat phi folding until no phi folds ConstantFolder::Fold visits each instruction once, in block order. A loop header phi whose back-edge value is a later phi that folds to it in the same pass, such as the header phi of an inner loop in which an S# is copied out and back, is visited before that phi folds and stays a phi of its one definition and itself. Later folds run only after read-lane or masked-select elimination changed the program, and since #4150 the one after masked-select elimination only runs in programs where a PackedAncillary builtin still has live uses. These phis reach resource tracking, which compares descriptor dwords as IR values, so the phi of an S# and the S# itself become two samplers. Two PPSA11386 (007 First Light) pixel shaders each had 21 direct samplers for 6 distinct S# values; at two heap slots each they exceed the 32 sampler slots, and the shaders stopped at their first draw with "shader sampler pairs exceed runtime heap capacity". After its pass over the instructions, Fold now folds the phis of its blocks again until a pass folds none. Each fold turns a phi into an Identity, so the loop ends. Replayed on an older local branch with #4150's restriction in place, the captured requests plan 6 samplers each, one per distinct S#, and recompile. agc_shader_memory compiles an S# copied out and back in three nested loops and sampled before and inside them, with and without a masked write: one sampler and one sampled pair each, two of each before. Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceAgcDriver/tests/ShaderMemory.cpp | 14 ++++++++++++++ .../Optimization/src/ConstantFolder.cpp | 17 +++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index a3294a30fe1..fb14ab026e0 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -1941,6 +1941,20 @@ void verifyDescriptorPhis() { sequential.context.memory = sequentialMemory.Regions(); static_cast(Recompile(sequential, *sequentialCapture)); + const auto planRestoredSampler = [&](std::span code, const char* message) { + auto request = makeRequest(code); + const auto plan = GetResourcePlan(request); + require(plan->info.samplers.size() == 1u && plan->info.sampledPairs.size() == 1u, message); + AgcDriver::ShaderMemory memory({}); + const auto capture = memory.Capture(request); + request.context.memory = memory.Regions(); + static_cast(Recompile(request, *capture)); + }; + std::vector restoredSamplerCode{0xf40c0200u, 0xfa000000u, 0xf4080500u, 0xfa000020u, 0xf4080600u, 0xfa000040u, 0xbe930380u, 0xbf8cc07fu, 0xf09c0f08u, 0x00a20400u, 0xbe900380u, 0xbe910380u, 0xbe9c0414u, 0xbe9e0416u, 0xbf068011u, 0xbf850001u, 0x80128112u, 0xbe94041cu, 0xbe96041eu, 0x80118111u, 0xbf0a8211u, 0xbf85fff6u, 0xf09c0f08u, 0x00a20000u, 0x80108110u, 0xbf0a8210u, 0xbf85fff0u, 0x80138113u, 0xbf0a8213u, 0xbf85ffecu, 0xbf8c3f70u, 0xe0700000u, 0x80060000u, 0xe0700000u, 0x80060400u, 0x7d880090u, 0xbea23c6au, 0x4a020081u, 0xe0700000u, 0x80060100u, 0xbf810000u}; + planRestoredSampler(restoredSamplerCode, "descriptor Phi: an S# copied out and back in three nested loops was planned as more than one sampler"); + restoredSamplerCode.erase(restoredSamplerCode.end() - 6, restoredSamplerCode.end() - 1); + planRestoredSampler(restoredSamplerCode, "descriptor Phi: an S# copied out and back in three nested loops without a masked write was planned as more than one sampler"); + const auto entryWrites = [&](std::initializer_list words) { auto code = loopCode; code.insert(code.begin() + 8, words); diff --git a/core/shader/recompiler/Optimization/src/ConstantFolder.cpp b/core/shader/recompiler/Optimization/src/ConstantFolder.cpp index 3505f640f4d..0e00f936f1e 100644 --- a/core/shader/recompiler/Optimization/src/ConstantFolder.cpp +++ b/core/shader/recompiler/Optimization/src/ConstantFolder.cpp @@ -828,6 +828,23 @@ void ConstantFolder::Fold(IrProgram& program, std::span blocks) const auto success = tryFoldValue(program, *inst); } } + std::vector phis; + for (IrBlock* block : blocks) { + for (IrValue* inst : block->Instructions()) { + if (inst->Opcode() == IrOpcode::Phi) { + phis.push_back(inst); + } + } + } + bool folded = true; + while (folded) { + folded = false; + for (IrValue* phi : phis) { + if (phi->Opcode() == IrOpcode::Phi && foldPhi(*phi)) { + folded = true; + } + } + } std::vector words; for (IrBlock* block : blocks) { for (IrValue* inst : block->Instructions()) {