diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index 8fef7ec53a5c..ada6ceb6562d 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -5,6 +5,7 @@ #include "Optimization/ResourceMaterializer.hpp" #include "Optimization/ResourceProgram.hpp" #include "Optimization/ShaderStageInputInfo.hpp" +#include "Optimization/SrtWalker/SrtDescriptorEvaluation.hpp" #include "Optimization/SrtWalker/SrtEvaluator.hpp" #include "Optimization/SrtWalker/SrtFlatSlotClasses.hpp" #include "SpirvBackend/SpirvAnalysis.hpp" @@ -380,6 +381,66 @@ void verifyEvaluatedValues() { require(!table.Find(&absent, found), "evaluated values: a value that was never inserted was found"); } +void verifySignedSrtComparison() { + using namespace ShaderRecompiler; + IrResourcePlan plan; + plan.srtPlanComplete = true; + plan.userDataBase = 8; + std::vector> values; + std::uint32_t id = 0; + const auto make = [&](IrOpcode opcode, IrType type, std::initializer_list arguments = {}) -> IrValue& { + auto value = std::make_unique(opcode, type, id++); + for (auto* argument : arguments) value->AddArgument(argument); + values.push_back(std::move(value)); + return *values.back(); + }; + const auto constant = [&](std::uint32_t bits) -> IrValue& { + auto& value = make(IrOpcode::Void, IrType::U32); + value.SetImmediateU32(bits); + return value; + }; + auto& registerValue = make(IrOpcode::Void, IrType::ScalarReg); + registerValue.SetRegister({RegisterBank::Scalar, 8u}); + auto& userData = make(IrOpcode::GetUserData, IrType::U32, {®isterValue}); + auto& zero = constant(0u); + auto& intMin = constant(0x80000000u); + auto& intMax = constant(0x7fffffffu); + auto& negative = constant(0xau); + auto& nonnegative = constant(0xbu); + auto& greaterThanMin = constant(0xcu); + auto& equalMin = constant(0xdu); + auto& belowMax = constant(0xeu); + auto& equalMax = constant(0xfu); + auto& isNegative = make(IrOpcode::SLessThan32, IrType::Bool, {&userData, &zero}); + auto& aboveMin = make(IrOpcode::SLessThan32, IrType::Bool, {&intMin, &userData}); + auto& belowMaxValue = make(IrOpcode::SLessThan32, IrType::Bool, {&userData, &intMax}); + DescriptorSource source; + source.dwordCount = 3u; + source.dwords[0] = &make(IrOpcode::SelectU32, IrType::U32, {&isNegative, &negative, &nonnegative}); + source.dwords[1] = &make(IrOpcode::SelectU32, IrType::U32, {&aboveMin, &greaterThanMin, &equalMin}); + source.dwords[2] = &make(IrOpcode::SelectU32, IrType::U32, {&belowMaxValue, &belowMax, &equalMax}); + plan.descriptorSources.push_back(source); + const std::array sourceIndex{0u}; + const std::array>, 4> cases{{ + {0x80000000u, {0xau, 0xdu, 0xeu}}, + {0xffffffffu, {0xau, 0xcu, 0xeu}}, + {0u, {0xbu, 0xcu, 0xeu}}, + {0x7fffffffu, {0xbu, 0xcu, 0xfu}}, + }}; + std::vector descriptors; + std::vector flat; + std::vector active; + for (const auto& [input, expected] : cases) { + const std::array userDataWords{input}; + const SrtRuntime runtime{userDataWords}; + require(Detail::EvaluateRuntimeSourcesImpl(plan, sourceIndex, runtime, descriptors, flat, false, {}, active), "signed SRT comparison: descriptor expression could not be evaluated"); + require(descriptors.size() == 1u && descriptors[0].dwordCount == expected.size(), "signed SRT comparison: descriptor shape changed"); + for (std::size_t index = 0; index < expected.size(); ++index) { + require(descriptors[0].dwords[index] == expected[index], "signed SRT comparison: signed boundary selected the wrong descriptor dword"); + } + } +} + // 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() { @@ -2488,6 +2549,7 @@ int main(int argc, char** argv) { verifyRegisterSources(); verifyEvaluatedValues(); verifyFrontendPair(); + verifySignedSrtComparison(); verifyPureFlatSlots(); verifyBindlessTable(); verifyDescriptorPhis(); diff --git a/core/shader/recompiler/Optimization/src/SrtWalker/SrtEvaluator.cpp b/core/shader/recompiler/Optimization/src/SrtWalker/SrtEvaluator.cpp index 6638d56b7676..e0f19af868fc 100644 --- a/core/shader/recompiler/Optimization/src/SrtWalker/SrtEvaluator.cpp +++ b/core/shader/recompiler/Optimization/src/SrtWalker/SrtEvaluator.cpp @@ -464,6 +464,12 @@ bool Evaluator::EvaluateInst(IrValue& inst, std::uint64_t& result) { return true; } return false; + case IrOpcode::SLessThan32: + if (binary()) { + result = std::bit_cast(static_cast(a)) < std::bit_cast(static_cast(b)) ? 1u : 0u; + return true; + } + return false; case IrOpcode::ULessThan32: if (binary()) { result = static_cast(a) < static_cast(b) ? 1u : 0u; diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 4c443e3d558d..ff0e3f805120 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -267,6 +267,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceKernelWriteThrottlingStatus](../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) reports no write throttling: the host SSD has no write budget, so the first qword of the 32-byte status is set to `UINT32_MAX` and the rest is zeroed. PPSA17221 reads only that qword: it multiplies it by 64 KiB and compares the product with byte thresholds up to 0x4AF00000 to pick one of five tiers. When the call fails the title assumes 0x4B000001 bytes, which selects the same top tier as `UINT32_MAX`. What the value measures on hardware (a remaining budget or a rate) and the meaning of the other three qwords are unknown - [Wide-character conversions](../../core/libs/prx/libc/src/Multibyte.cpp) (libc) - The wide `wcsto*` functions parse the ASCII prefix of the guest string, as the C locale accepts only ASCII white space and digits. [`wcsrtombs`](../../core/libs/prx/libc/src/Multibyte.cpp) uses the guest's single-byte C locale through `wcrtomb`; other encodings and shift states are not supported - [SPIRV-Tools optimization](../../core/shader/recompiler/SpirvBackend/src/SpirvOptimizer.cpp) skips CCP and Simplification for modules with storage-only 8- or 16-bit types because the pinned folder produces illegal narrow constants. This also disables unrelated constant folding; the restricted full pipeline mirrors the pinned performance recipe. +- [SRT signed comparison](../../core/shader/recompiler/Optimization/src/SrtWalker/SrtEvaluator.cpp) evaluates `SLessThan32` as signed 32-bit less-than, matching the existing SPIR-V backend and [SPIR-V `OpSLessThan`](https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpSLessThan). Signed boundary descriptor expressions are tested on the host; native RDNA2/PS5 execution and title behavior remain unverified. - On macOS the [write watch](../../core/libs/prx/libc/src/GuestWriteWatch.cpp) write-protects watched pages and records the first write to each from a SIGBUS/SIGSEGV handler, because macOS has no counterpart to `userfaultfd` write-protect or `MEM_WRITE_WATCH`. The protection is real, so: - a system call that writes into a protected page fails with `EFAULT` instead of faulting. `GuestArena::HostWrite` lifts the protection for the calls it brackets (the file reads); a host call that writes guest memory without it (sockets, `clock_gettime` and the like into a watched page) fails; - code that changes the protection of watched pages, or maps over them, has to call `GuestWriteWatchProtectionChanged`, and code that asks the kernel for a page's protection has to ask `GuestWriteWatchProtection` first, since a watched page is read-only between a collection and the next write. Nothing calls either yet: the macOS memory paths are not in the tree;