diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 583e96a21..4d1478a8a 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1225,6 +1225,14 @@ if(BUILD_TESTING) set_tests_properties(scalar_scratch_load PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(aperture_sources_tests ${LIBS_INCLUDE_DIR}/tests/ApertureSources.cpp) + target_link_libraries(aperture_sources_tests PRIVATE shader_recompiler_agc_driver libc) + configure_windows_unwind(aperture_sources_tests) + add_test(NAME aperture_sources COMMAND aperture_sources_tests) + if(WIN32) + set_tests_properties(aperture_sources PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(scalar_call_tests ${LIBS_INCLUDE_DIR}/tests/ScalarCall.cpp) target_link_libraries(scalar_call_tests PRIVATE shader_recompiler_agc_driver libc) configure_windows_unwind(scalar_call_tests) diff --git a/core/libs/tests/ApertureSources.cpp b/core/libs/tests/ApertureSources.cpp new file mode 100644 index 000000000..f7e05eda1 --- /dev/null +++ b/core/libs/tests/ApertureSources.cpp @@ -0,0 +1,89 @@ +#include "IntermediateRepresentation/IrProgram.hpp" +#include "RdnaDecoder/RdnaInstructionDecoder.hpp" +#include "RdnaDecoder/RdnaScalarOpDecoder.hpp" +#include "Translation/TranslationContext.hpp" +#include +#include +#include +#include + +using namespace ShaderRecompiler; + +namespace { + +struct ApertureSource { + std::uint32_t code; + RdnaOperandKind kind; + const char* name; +}; + +const std::array ApertureSources{{ + {235u, RdnaOperandKind::SrcSharedBase, "src_shared_base"}, + {236u, RdnaOperandKind::SrcSharedLimit, "src_shared_limit"}, + {237u, RdnaOperandKind::SrcPrivateBase, "src_private_base"}, + {238u, RdnaOperandKind::SrcPrivateLimit, "src_private_limit"}, +}}; + +void Require(bool value, const std::string& message) { + if (!value) throw std::runtime_error(message); +} + +// The refusal must name the source it refused: a bare "not modelled" cannot be told from a decode gap. +template +void ExpectRefusal(const char* source, Fn translate) { + try { + translate(); + } catch (const std::exception& error) { + if (std::string(error.what()).find(source) == std::string::npos) { + throw std::runtime_error(std::string("the refusal does not name ") + source + ": " + error.what()); + } + return; + } + throw std::runtime_error(std::string("reading ") + source + " was accepted"); +} + +// s_mov_b32 s0, : SOP1, format 0x7d, opcode 0x03, the source code in the low byte. +RdnaInstruction ScalarMove(std::uint32_t source) { + const std::array words{0x80000000u | (0x7du << 23u) | (0x03u << 8u) | source, 0u}; + return DecodeRdnaSop1(0u, words, 0u); +} + +void Translate(const RdnaInstruction& instruction) { + IrProgram program; + auto& block = program.CreateBlock(); + program.SetEntryBlock(block); + program.BlockOrder() = {&block}; + program.Metadata().blockInfo.resize(1); + TranslationContext context(program, block, 256); + context.TranslateInstruction(instruction); +} + +void CheckDecode() { + for (const auto& source : ApertureSources) { + const auto operand = DecodeRdnaScalarSource(source.code, 0u); + Require(operand.kind == source.kind, + "scalar source " + std::to_string(source.code) + " decoded to the wrong operand kind"); + Require(RdnaOperandToString(operand) == source.name, + "scalar source " + std::to_string(source.code) + " is named " + RdnaOperandToString(operand)); + } +} + +// Translating the move reads the source as a value, which is the path a title that carries the +// aperture into an address does not take and one that reads it does. +void CheckReadRefuses() { + for (const auto& source : ApertureSources) { + const RdnaInstruction move = ScalarMove(source.code); + Require(move.op == RdnaOpcode::SMovB32, "the scalar move decoded to another opcode"); + Require(move.source0.kind == source.kind, "the scalar move decodes source0 as the wrong operand kind"); + ExpectRefusal(source.name, [&] { Translate(move); }); + } +} + +} + +int main() { + CheckDecode(); + CheckReadRefuses(); + std::puts("aperture sources tests passed"); + return 0; +} diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaInstruction.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaInstruction.hpp index f038fdc0f..85dc6da28 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaInstruction.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaInstruction.hpp @@ -53,7 +53,11 @@ enum class RdnaOperandKind { VccZ, ExecZ, M0, - PopsExitingWaveId + PopsExitingWaveId, + SrcSharedBase, + SrcSharedLimit, + SrcPrivateBase, + SrcPrivateLimit }; enum class RdnaImageDimension : std::uint32_t { diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaInstructionDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaInstructionDecoder.cpp index f03944673..fc5e2b9d6 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaInstructionDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaInstructionDecoder.cpp @@ -217,6 +217,10 @@ RdnaOperand DecodeRdnaScalarSource(std::uint32_t code, std::uint32_t programCoun case 125u: operand.kind = RdnaOperandKind::Null; return operand; case 126u: operand.kind = RdnaOperandKind::ExecLo; return operand; case 127u: operand.kind = RdnaOperandKind::ExecHi; return operand; + case 235u: operand.kind = RdnaOperandKind::SrcSharedBase; return operand; + case 236u: operand.kind = RdnaOperandKind::SrcSharedLimit; return operand; + case 237u: operand.kind = RdnaOperandKind::SrcPrivateBase; return operand; + case 238u: operand.kind = RdnaOperandKind::SrcPrivateLimit; return operand; case 239u: operand.kind = RdnaOperandKind::PopsExitingWaveId; return operand; case 248u: operand.kind = RdnaOperandKind::FloatInlineConstant; @@ -315,6 +319,10 @@ std::string RdnaOperandToString(const RdnaOperand& operand) { case RdnaOperandKind::ExecZ: return "execz"; case RdnaOperandKind::M0: return "m0"; case RdnaOperandKind::PopsExitingWaveId: return "pops_exiting_wave_id"; + case RdnaOperandKind::SrcSharedBase: return "src_shared_base"; + case RdnaOperandKind::SrcSharedLimit: return "src_shared_limit"; + case RdnaOperandKind::SrcPrivateBase: return "src_private_base"; + case RdnaOperandKind::SrcPrivateLimit: return "src_private_limit"; } throw std::invalid_argument("unsupported operand kind for string conversion"); diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp index f794274c6..77ea8f032 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp @@ -436,6 +436,10 @@ RdnaOperand scalarSource(std::uint32_t code) { case 125u: operand.kind = RdnaOperandKind::Null; return operand; case 126u: operand.kind = RdnaOperandKind::ExecLo; return operand; case 127u: operand.kind = RdnaOperandKind::ExecHi; return operand; + case 235u: operand.kind = RdnaOperandKind::SrcSharedBase; return operand; + case 236u: operand.kind = RdnaOperandKind::SrcSharedLimit; return operand; + case 237u: operand.kind = RdnaOperandKind::SrcPrivateBase; return operand; + case 238u: operand.kind = RdnaOperandKind::SrcPrivateLimit; return operand; case 239u: operand.kind = RdnaOperandKind::PopsExitingWaveId; return operand; case 248u: operand.kind = RdnaOperandKind::FloatInlineConstant; operand.value = floatBits(0.15915494309189535f); return operand; case 251u: operand.kind = RdnaOperandKind::VccZ; return operand; diff --git a/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp b/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp index 63d6d727b..2ddafd996 100644 --- a/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp +++ b/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp @@ -1,7 +1,9 @@ +#include "RdnaDecoder/RdnaInstructionDecoder.hpp" #include "Translation/TranslationContext.hpp" #include #include #include +#include namespace ShaderRecompiler { @@ -418,6 +420,11 @@ IrU32 TranslationContext::readScalarCode(std::uint32_t code) { const std::array mask = ballotMask(IrU1(ir.GetExec())); return mask[code - 126u]; } + case 235u: + case 236u: + case 237u: + case 238u: + throw std::runtime_error("TranslationContext::readScalarCode: the scalar aperture source " + RdnaOperandToString(DecodeRdnaScalarSource(code, 0u)) + " is not modelled"); default: return IrU32(ir.Constant(0u)); } } @@ -447,6 +454,11 @@ IrU32 TranslationContext::readRawU32(const RdnaOperand& operand) { const IrU32 zero(ir.Constant(0u)); return IrU32(ir.Select(ir.IEqual(mask.Value(), zero.Value()), ir.Constant(1u), zero.Value())); } + case RdnaOperandKind::SrcSharedBase: + case RdnaOperandKind::SrcSharedLimit: + case RdnaOperandKind::SrcPrivateBase: + case RdnaOperandKind::SrcPrivateLimit: + throw std::runtime_error("TranslationContext::readRawU32: the scalar aperture source " + RdnaOperandToString(operand) + " is not modelled"); default: throw std::runtime_error("TranslationContext::readRawU32 invalid decoded operand used as a raw U32 source"); } } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 5a5353560..851a0a415 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -448,6 +448,7 @@ Throughout the project, every function at every stage either **does exactly what - [Font writing lines](../../core/libs/prx/libSceFont/src/WritingLine.cpp) (libSceFont) - `sceFontCreateWritingLine`, `sceFontWritingLineWritesOrder`, `sceFontWritingLineRefersRenderStep`, `sceFontWritingLineGetRenderMetrics` and `sceFontWritingLineClear` follow [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/libFont.cpp): runs are placed left to right, each at the line's advance so far, with no spacing or adjustment. Only horizontal and left-to-right lines, writing attribute 0 and non-null run metrics are modelled; other values, the metrics of an empty line and unknown or null handles throw, since no reference gives their result or error code. `sceFontWritingLineGetOrderingSpace` still throws (KytyPS5's values are placeholders). Lines are allocated on the host, not in the font memory the title passes to the library: FreeType needs about 1 MiB per CJK face, which the title's font memory is not sized for. - [s_swappc_b64](../../core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp) writes the link pair with the same model as `s_getpc_b64`: the guest return address `pc + 4` as a byte offset over `GetShaderBase`. Any other use of the link pair (arithmetic, compare, hash, store, or a second `s_swappc_b64` sharing it) throws; constant-offset folding over the link is not modeled. The special encoding `s_swappc_b64` with SDST 125 still decodes as `s_setpc_b64`, which matches the hardware: measured on RDNA2 (gfx1036) in wave32 and wave64, it branches to the source pair and leaves a marker SGPR pair untouched, and with an SGPR destination the link is `PC + 4`. - The [s_swappc_b64 fetch-shader call at vertex/local/tessellation/mesh entry](../../core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp) assumes the called fetch-shader blob ends in `s_setpc_b64` to the link pair (the GNM fetch-shader shape shadPS4's `EmitFetch` copies); the blob is not part of the shader program, so its return closes the pairing by assumption and any in-program reader of the link pair throws. Attribute fetches are inlined from the request's parsed fetch-shader plan through the same `GetAttribute` path as the embedded fetch analyzer, honoring the buffer resource `dst_sel`; an attribute writing more than four registers, or an entry call without a parsed plan, throws. The vertex/instance offset accumulator inside the fetch blob is not replayed for external blobs. +- The scalar aperture sources [src_shared_base, src_shared_limit, src_private_base, src_private_limit](../../core/shader/recompiler/RdnaDecoder/src/RdnaInstructionDecoder.cpp) (core/shader/recompiler) decode to their own operand kinds (scalar source codes 235 to 238, named as in LLVM's `SIRegisterInfo.td`), but their value is not modelled: reading one refuses with the source it refused named, because the wave's shared and private aperture bases and limits are unknown. Both the scalar-code read and the raw-operand read refuse; a shader that only carries the value into an address still prepares, one that reads it does not - [VertexBufferReadSize](../../core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp) and the [vertex staging copy](../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) - a vertex fetch whose index is at or past the descriptor's `num_records` no longer throws for `OOB_SELECT` 0 and 1. The copy still spans the whole fetch, but the bytes from `num_records * stride` onward read as zeros, so the out-of-range vertex is `(0, 0, 0, 0)`. Measured with `buffer_load_format_xyzw` (`idxen`) through the [hardware oracle](HW_ORACLE.md) on RDNA2 gfx1036 for PPSA01325's word 3 `0x0004dfac` (`OOB_SELECT` 0) in #2241: indices 3, 4 and 5 over 3 records each return `00000000 00000000 00000000 00000000`, W included, unlike Vulkan `robustBufferAccess`, which returns `(0, 0, 0, 1)`. The zeros come from the record check, not from memory: stored values placed past the records are ignored, and a `num_records` 6 control returns them. `RESOURCE_LEVEL` 1, `OOB_SELECT` 1 and FORMAT 74 `32_32_32_FLOAT` give the same zeros, in wave32 and wave64; gfx1013 (the console) is not measured. `OOB_SELECT` 2 returns the stored values past the records and `OOB_SELECT` 3 compares `num_records` against the byte offset, both measured on the same setup in #2241; the two keep throwing past `num_records` here, since each needs its own rule. Zero-stride descriptors (`stride` 0) keep their separate byte-count check and are not covered here. Each out-of-range fetch is copied alone and outside the draw-snapshot cache, which is neither read nor written for it: a reused snapshot would carry another draw's bytes into the zero region, and a stored zero-padded copy would hand zeros to a later in-range draw at the same address. The valid prefix is still flushed for GPU writes before it is read. - [sceImeKeyboardGetResourceId](../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) reports that no USB keyboard is connected (`0x80bc0004`, as [shadPS4](https://github.com/shadps4-emu/shadPS4) does) because IME keyboard events are not delivered to the handler, although [libSceKeyboard](../../core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp) exposes the host keyboard - [Player review dialog](../../core/libs/prx/libSceCdlgPlayerReview/Export.cpp) (libSceCdlgPlayerReview) shows nothing: `scePlayerReviewDialogOpen` finishes at once and the result reports that the user canceled, as `sceWebBrowserDialogOpen` does