From 5a9d731540e0f8136c6937d60011f8e4978152b6 Mon Sep 17 00:00:00 2001 From: Alex Collini Date: Sun, 11 Oct 2026 10:02:26 +0200 Subject: [PATCH] fix(recompiler): evaluate an out-of-range constant buffer read as zero s_buffer_load returns zero for dwords outside the V#'s range. The SRT evaluator refused such a read instead, so evaluating a descriptor failed as soon as one arm of a split T# read through a null or short V#, even when that arm is never taken. The evaluator now returns zero for those reads, as the hardware does. PPSA26344 compute shader 0x800037f200 reads one arm's T# through a null V# and stopped at "SrtWalker::EvaluateRuntimeSources failed to evaluate runtime sources". Co-Authored-By: Claude Opus 5.5 --- .../libSceAgcDriver/tests/ShaderMemory.cpp | 58 +++++++++++++++++++ .../src/SrtWalker/SrtEvaluator.cpp | 3 +- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index 3000968e3..29c446c1e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -464,6 +464,63 @@ void verifySignedSrtComparison() { } } +void verifyOutOfRangeConstantReads() { + std::vector code{0xf40c0200u, 0xfa000000u, 0xf4080500u, 0xfa000020u, 0xf4080600u, 0xfa000040u, 0xf4000400u, 0xfa000050u, 0xf4080a00u, 0xfa000080u, 0xbf8cc07fu, 0xf42c0814u, 0xfa000000u, 0xbf8cc07fu, 0xbf068010u, 0xbf850008u}; + for (std::uint32_t dword = 0; dword < 8u; dword++) { + code.push_back(0xbe800300u | ((8u + dword) << 16u) | (32u + dword)); + } + code.insert(code.end(), {0xf09c0f08u, 0x00a20000u, 0xbf8c3f70u, 0xe0700000u, 0x80060000u, 0xbf810000u}); + using namespace ShaderRecompiler; + constexpr std::uint32_t Format8888UNorm = 56; + constexpr std::uint32_t Type2D = 9; + struct alignas(256) Texture { std::array bytes{}; }; + static Texture textures[2]; + const auto imageDescriptor = [&](const Texture& texture) { + const auto base = static_cast(reinterpret_cast(texture.bytes.data())); + return std::array{static_cast(base >> 8u), static_cast((base >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (3u << 30u), 3u << 14u, 0xfacu | (Type2D << 28u), 0u, 0u, 0u, 0u}; + }; + std::array output{}; + const auto outputAddress = static_cast(reinterpret_cast(output.data())); + std::array srt{}; + const auto first = imageDescriptor(textures[0]); + const auto second = imageDescriptor(textures[1]); + std::copy(first.begin(), first.end(), srt.begin()); + const std::array outputV{static_cast(outputAddress), static_cast((outputAddress >> 32u) & 0xffffu), 16u, 0xfacu}; + std::copy(outputV.begin(), outputV.end(), srt.begin() + 16); + srt[20] = 1u; + std::copy(second.begin(), second.end(), srt.begin() + 24); + const auto srtAddress = static_cast(reinterpret_cast(srt.data())); + const std::array userData{static_cast(srtAddress), static_cast(srtAddress >> 32u)}; + const std::array capabilities{29u, spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess}; + const std::array extensions{"SPV_KHR_physical_storage_buffer", "SPV_KHR_8bit_storage"}; + RecompileRequest request{}; + request.shader = {ShaderStage::Compute, 0x23000u, code, 0, {}}; + request.context.waveSize = 32u; + request.context.userDataBaseRegister = 0; + request.context.userData = userData; + request.context.compute = ShaderComputeStageInfo{{32u, 1u, 1u}, 0u, {false, false, false}, false, 1u}; + request.target.vulkanVersion = 0x00401000u; + request.target.spirvVersion = 0x00010300u; + request.target.subgroupSize = 32; + request.target.supportedCapabilities = capabilities; + request.target.supportedExtensions = extensions; + request.target.bdaAbiVersion = BdaAbi::Version; + request.target.fragmentShaderBarycentricEnabled = false; + request.layout.pushConstantSizeBytes = 128; + const auto plan = GetResourcePlan(request); + require(plan->info.images.size() == 2u && plan->info.samplers.size() == 1u, "out-of-range s_buffer_load: the two T#s were not planned as one image each"); + AgcDriver::ShaderMemory memory({}); + const auto capture = memory.Capture(request); + request.context.memory = memory.Regions(); + static_cast(Recompile(request, *capture)); + const auto holds = [&](const std::array& words) { + return std::ranges::any_of(capture->snapshot.images, [&](const DescriptorValue& value) { + return std::equal(words.begin(), words.end(), value.dwords.begin()); + }); + }; + require(holds(first) && holds(std::array{}), "out-of-range s_buffer_load: a T# read through a null V# was not evaluated as zero"); +} + // The pure flat slots of a hand-built plan (Detail::ComputePureFlatSlots): a slot is pure unless // a descriptor dword, a condition, a uniform value or another slot's address cone reaches it. void verifyPureFlatSlots() { @@ -2711,6 +2768,7 @@ int main(int argc, char** argv) { verifyEvaluatedValues(); verifyFrontendPair(); verifySignedSrtComparison(); + verifyOutOfRangeConstantReads(); verifySignedMinimum(); verifyPureFlatSlots(); verifyBindlessTable(); diff --git a/core/shader/recompiler/Optimization/src/SrtWalker/SrtEvaluator.cpp b/core/shader/recompiler/Optimization/src/SrtWalker/SrtEvaluator.cpp index d6ce62ce7..36baa22dc 100644 --- a/core/shader/recompiler/Optimization/src/SrtWalker/SrtEvaluator.cpp +++ b/core/shader/recompiler/Optimization/src/SrtWalker/SrtEvaluator.cpp @@ -143,7 +143,8 @@ bool Evaluator::EvaluateRawRead(IrValue& inst, std::uint64_t& result) { 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; + result = 0; + return true; } address = ((base & ~std::uint64_t {3}) + byteOffset) & ~std::uint64_t {3}; } else {