From a5717b6613de768249477961ea938f74ca4a9dad Mon Sep 17 00:00:00 2001 From: popcoder565 <339285150+popcoder565@users.noreply.github.com> Date: Wed, 7 Oct 2026 15:49:02 -0400 Subject: [PATCH 1/5] feat(shader): decode halt, trap return, code end, GWS and ordered count instructions s_sethalt with SIMM16[0] clear is a no-op; s_sethalt 1, s_sendmsghalt, a reached s_code_end, s_rfe_b64, the ds_gws_* instructions and ds_ordered_count throw with the reason at translation. Linear decoding stops at s_code_end like at s_endpgm, so code padded with s_code_end after a final backward branch decodes. Encodings from LLVM SOPInstructions.td and DSInstructions.td, checked against the gfx10 MC tests; semantics from the RDNA 2 ISA guide. --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 6 + .../tests/WaveControlDecode.cpp | 137 ++++++++++++++++++ .../tests/execution/ScalarSethalt.cpp | 92 ++++++++++++ .../include/RdnaDecoder/RdnaOpcode.hpp | 11 ++ .../src/RdnaInstructionDecoder.cpp | 2 +- .../RdnaDecoder/src/RdnaMemoryOpDecoder.cpp | 27 +++- .../RdnaDecoder/src/RdnaScalarOpDecoder.cpp | 9 +- .../MemoryInstructionDispatch.cpp | 25 ++++ .../Translation/src/ScalarInstructions.cpp | 12 ++ docs/dev/TechnicalDebt.md | 1 + 10 files changed, 318 insertions(+), 4 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/WaveControlDecode.cpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/ScalarSethalt.cpp diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 40c7b7629..30fe9822a 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -293,6 +293,12 @@ target_link_libraries(agc_vop1_control_decode_tests PRIVATE shader_recompiler_ag set_target_properties(agc_vop1_control_decode_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_vop1_control_decode_tests) +add_test_executable(agc_wave_control_decode_tests EXCLUDE_FROM_ALL tests/WaveControlDecode.cpp) +target_include_directories(agc_wave_control_decode_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_wave_control_decode_tests PRIVATE shader_recompiler_agc_driver libc) +set_target_properties(agc_wave_control_decode_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_wave_control_decode_tests) + add_test_executable(agc_dcc_key_sharing_tests EXCLUDE_FROM_ALL tests/DccKeySharing.cpp) target_include_directories(agc_dcc_key_sharing_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include) target_link_libraries(agc_dcc_key_sharing_tests PRIVATE libc) diff --git a/core/libs/prx/libSceAgcDriver/tests/WaveControlDecode.cpp b/core/libs/prx/libSceAgcDriver/tests/WaveControlDecode.cpp new file mode 100644 index 000000000..e75cde7a9 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/WaveControlDecode.cpp @@ -0,0 +1,137 @@ +#include "RdnaDecoder/RdnaInstructionDecoder.hpp" +#include "Recompiler.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using namespace ShaderRecompiler; + +void require(bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +bool isVector(const RdnaOperand& operand, std::uint32_t reg) { + return operand.kind == RdnaOperandKind::VectorRegister && operand.reg == reg; +} + +void verifyDecode() { + const std::vector code{ + 0xbf8d0000u, 0xbf8d0001u, 0xbf911234u, 0xbe802204u, + 0xd8661234u, 0x00000001u, 0xd86a0000u, 0x00000000u, 0xd86e0000u, 0x00000002u, + 0xd8720000u, 0x00000000u, 0xd8620000u, 0x00000000u, 0xd8760000u, 0x00000003u, + 0xd8fe0004u, 0x05000001u, + 0xbf9f0000u, 0xbf9f0000u, 0xffffffffu, + }; + const auto program = RdnaInstructionDecoder{}.Decode(code); + const auto& decoded = program.instructions; + require(decoded.size() == 12u, "decoding did not stop at the first s_code_end: " + std::to_string(decoded.size()) + " instructions"); + + require(decoded[0].op == RdnaOpcode::SSethalt && decoded[0].source0.value == 0u && decoded[0].sourceCount == 1u, "s_sethalt 0"); + require(decoded[1].op == RdnaOpcode::SSethalt && decoded[1].source0.value == 1u, "s_sethalt 1"); + require(decoded[2].op == RdnaOpcode::SSendmsghalt && decoded[2].source0.value == 0x1234u && decoded[2].sourceCount == 1u, "s_sendmsghalt 0x1234"); + require(decoded[3].op == RdnaOpcode::SRfeB64 && decoded[3].destination.kind == RdnaOperandKind::Null && decoded[3].sourceCount == 1u && + decoded[3].source0.kind == RdnaOperandKind::ScalarRegister && decoded[3].source0.reg == 4u, "s_rfe_b64 s[4:5]"); + + const std::array, 6> gws{{ + {RdnaOpcode::DsGwsInit, 1u, 0x1234u, "ds_gws_init v1 offset:4660 gds"}, + {RdnaOpcode::DsGwsSemaV, 0u, 0u, "ds_gws_sema_v gds"}, + {RdnaOpcode::DsGwsSemaBr, 1u, 0u, "ds_gws_sema_br v2 gds"}, + {RdnaOpcode::DsGwsSemaP, 0u, 0u, "ds_gws_sema_p gds"}, + {RdnaOpcode::DsGwsSemaReleaseAll, 0u, 0u, "ds_gws_sema_release_all gds"}, + {RdnaOpcode::DsGwsBarrier, 1u, 0u, "ds_gws_barrier v3 gds"}, + }}; + const std::array dataRegisters{1u, 0u, 2u, 0u, 0u, 3u}; + for (std::size_t index = 0; index < gws.size(); ++index) { + const auto& [opcode, sources, offset, name] = gws[index]; + const auto& instruction = decoded[4u + index]; + require(instruction.op == opcode && instruction.gds && instruction.sourceCount == sources && instruction.memoryOffset == offset && + instruction.destination.kind == RdnaOperandKind::None && instruction.wordCount == 2u, name); + if (sources != 0u) require(isVector(instruction.source0, dataRegisters[index]), std::string(name) + " reads the wrong data register"); + } + + const auto& ordered = decoded[10]; + require(ordered.op == RdnaOpcode::DsOrderedCount && ordered.gds && ordered.memoryOffset == 4u && isVector(ordered.destination, 5u) && + isVector(ordered.source0, 1u) && ordered.sourceCount == 1u, "ds_ordered_count v5, v1 offset:4 gds"); + require(decoded[11].op == RdnaOpcode::SCodeEnd, "s_code_end"); +} + +std::string recompile(std::span code) { + const std::array userData{0x10000000u, 0x00100000u, 0x40u, 0x00027facu}; + const std::array capabilities{1u, 61u}; + const std::array extensions{"SPV_KHR_storage_buffer_storage_class"}; + RecompileRequest request{}; + request.shader = {ShaderStage::Compute, 0x20000u, code, 0, {}}; + request.context.waveSize = 64; + request.context.userDataBaseRegister = 0; + request.context.userData = userData; + request.context.compute = ShaderComputeStageInfo{{64u, 1u, 1u}, 0u, {false, false, false}, false, 1u}; + request.target.vulkanVersion = 0x00403000u; + request.target.spirvVersion = 0x00010600u; + request.target.subgroupSize = 64u; + request.target.bdaAbiVersion = 1; + request.target.supportedCapabilities = capabilities; + request.target.supportedExtensions = extensions; + request.target.maxWorkgroupSize = {1024u, 1024u, 64u}; + request.target.maxWorkgroupInvocations = 1024; + request.target.maxWorkgroupSharedMemoryBytes = 49152; + request.layout = {0, 0, 0, 128}; + request.useCache = false; + try { + const auto result = Recompile(request); + require(!result.spirv.empty(), "no SPIR-V"); + } catch (const std::exception& error) { + return error.what(); + } + return {}; +} + +std::vector withStore(std::initializer_list words) { + std::vector code(words); + code.insert(code.end(), {0x7e020281u, 0xe0700000u, 0x80000100u, 0xbf810000u}); + return code; +} + +void verifyTranslation() { + require(recompile(withStore({0xbf8d0000u})).empty(), "s_sethalt 0 is refused: " + recompile(withStore({0xbf8d0000u}))); + const std::vector trailingLoop{0x7e020281u, 0xbf820003u, 0xe0700000u, 0x80000100u, 0xbf810000u, 0xbf82fffcu, 0xbf9f0000u, 0xbf9f0000u, 0xffffffffu}; + require(recompile(trailingLoop).empty(), "a program padded with s_code_end after its last branch is refused: " + recompile(trailingLoop)); + + const std::vector, std::string_view>> refused{ + {"s_sethalt 1", withStore({0xbf8d0001u}), "s_sethalt 1 at pc 0 halts the wave"}, + {"s_sendmsghalt", withStore({0xbf910001u}), "s_sendmsghalt 1 at pc 0 halts the wave"}, + {"s_code_end", {0x7e020281u, 0xbf9f0000u}, "s_code_end at pc 4 is reached"}, + {"s_rfe_b64", withStore({0xbe802204u}), "s_rfe_b64 at pc 0 returns from a trap handler"}, + {"ds_gws_init", withStore({0xd8660000u, 0x00000001u}), "ds_gws_init at pc 0: global wave sync"}, + {"ds_gws_sema_v", withStore({0xd86a0000u, 0x00000000u}), "ds_gws_sema_v at pc 0: global wave sync"}, + {"ds_gws_sema_br", withStore({0xd86e0000u, 0x00000002u}), "ds_gws_sema_br at pc 0: global wave sync"}, + {"ds_gws_sema_p", withStore({0xd8720000u, 0x00000000u}), "ds_gws_sema_p at pc 0: global wave sync"}, + {"ds_gws_sema_release_all", withStore({0xd8620000u, 0x00000000u}), "ds_gws_sema_release_all at pc 0: global wave sync"}, + {"ds_gws_barrier", withStore({0xd8760000u, 0x00000003u}), "ds_gws_barrier at pc 0: global wave sync"}, + {"ds_ordered_count", withStore({0xd8fe0000u, 0x05000001u}), "ds_ordered_count at pc 0: GDS counters"}, + }; + for (const auto& [name, code, message] : refused) { + const auto error = recompile(code); + require(error.find(message) != std::string::npos, std::string(name) + ": expected a refusal with '" + std::string(message) + "', got '" + error + "'"); + } +} + +} + +int main() { + try { + verifyDecode(); + verifyTranslation(); + std::puts("wave control decode tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + } + return 1; +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSethalt.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSethalt.cpp new file mode 100644 index 000000000..a27c833e7 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSethalt.cpp @@ -0,0 +1,92 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0301000, 0x80000401, 0xbf8c3f70, 0xbf8d0000, 0x4a0a0881, 0xbf820003, + 0xe0701000, 0x80010503, 0xbf810000, 0xbf82fffc, 0xbf9f0000, 0xbf9f0000, 0xbf9f0000, 0xbf9f0000, +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { words[0] = tid * 0x01010101u + 7u; } + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("scalar sethalt: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + Expect(tid, out[0], in[0] + 1u, "store after s_sethalt 0"); + Expect(tid, out[1], 0xdeadbeefu, "word after the store"); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("scalar sethalt tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp index 9e9fba1df..02bbcce3c 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -1042,6 +1042,17 @@ enum class RdnaOpcode : std::uint16_t { Exp, VMulHiI32I24, VMulHiU32U24, + SSethalt, + SSendmsghalt, + SCodeEnd, + SRfeB64, + DsGwsInit, + DsGwsSemaV, + DsGwsSemaBr, + DsGwsSemaP, + DsGwsSemaReleaseAll, + DsGwsBarrier, + DsOrderedCount, Count }; diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaInstructionDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaInstructionDecoder.cpp index f1d527ac9..9181fc4fb 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaInstructionDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaInstructionDecoder.cpp @@ -168,7 +168,7 @@ void DecodeRdnaProgram(std::span code, RdnaProgram& program furthestBranchTarget = std::max(furthestBranchTarget, targetIndex); } - if (instruction.op == RdnaOpcode::SEndpgm && furthestBranchTarget < wordIndex) { + if ((instruction.op == RdnaOpcode::SEndpgm || instruction.op == RdnaOpcode::SCodeEnd) && furthestBranchTarget < wordIndex) { return; } } diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp index 17c93986a..5bc29e580 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp @@ -234,6 +234,12 @@ constexpr MemoryOpcodeInfo dsOpcodes[] = { {0x11u, RdnaOpcode::DsCmpstF32, 1, 32, false, false, false}, {0x14u, RdnaOpcode::DsNop, 1, 32, false, false, false}, {0x15u, RdnaOpcode::DsAddF32, 1, 32, false, false, false}, + {0x18u, RdnaOpcode::DsGwsSemaReleaseAll, 1, 32, false, false, false}, + {0x19u, RdnaOpcode::DsGwsInit, 1, 32, false, false, false}, + {0x1au, RdnaOpcode::DsGwsSemaV, 1, 32, false, false, false}, + {0x1bu, RdnaOpcode::DsGwsSemaBr, 1, 32, false, false, false}, + {0x1cu, RdnaOpcode::DsGwsSemaP, 1, 32, false, false, false}, + {0x1du, RdnaOpcode::DsGwsBarrier, 1, 32, false, false, false}, {0x22u, RdnaOpcode::DsRsubRtnU32, 1, 32, false, false, false}, {0x2cu, RdnaOpcode::DsMskorRtnB32, 1, 32, false, false, false}, {0x30u, RdnaOpcode::DsCmpstRtnB32, 1, 32, false, false, false}, @@ -316,6 +322,7 @@ constexpr MemoryOpcodeInfo dsOpcodes[] = { {0x3cu, RdnaOpcode::DsReadU16, 1, 16, false, false, false}, {0x3du, RdnaOpcode::DsConsume, 1, 32, false, false, false}, {0x3eu, RdnaOpcode::DsAppend, 1, 32, false, false, false}, + {0x3fu, RdnaOpcode::DsOrderedCount, 1, 32, false, false, false}, {0x4du, RdnaOpcode::DsWriteB64, 2, 32, false, false, false}, {0x4eu, RdnaOpcode::DsWrite2B64, 4, 32, false, false, false}, {0x4fu, RdnaOpcode::DsWrite2st64B64, 4, 32, false, false, false}, @@ -469,6 +476,18 @@ bool isDsWriteOpcode(RdnaOpcode opcode) { } } +bool isDsGwsOpcode(RdnaOpcode opcode) { + switch (opcode) { + case RdnaOpcode::DsGwsInit: + case RdnaOpcode::DsGwsSemaV: + case RdnaOpcode::DsGwsSemaBr: + case RdnaOpcode::DsGwsSemaP: + case RdnaOpcode::DsGwsSemaReleaseAll: + case RdnaOpcode::DsGwsBarrier: return true; + default: return false; + } +} + bool isDsAtomicOpcode(RdnaOpcode opcode) { switch (opcode) { case RdnaOpcode::DsAddU32: @@ -565,7 +584,10 @@ std::uint32_t dsSourceCount(RdnaOpcode opcode) { case RdnaOpcode::DsBpermuteB32: return 2u; case RdnaOpcode::DsReadAddtidB32: case RdnaOpcode::DsConsume: - case RdnaOpcode::DsAppend: return 0u; + case RdnaOpcode::DsAppend: + case RdnaOpcode::DsGwsSemaV: + case RdnaOpcode::DsGwsSemaP: + case RdnaOpcode::DsGwsSemaReleaseAll: return 0u; default: return isDsWriteOpcode(opcode) || isDsAtomicOpcode(opcode) ? 2u : 1u; } } @@ -960,6 +982,9 @@ RdnaInstruction DecodeRdnaDs(std::uint32_t programCounter, std::span Date: Wed, 7 Oct 2026 16:33:33 -0400 Subject: [PATCH 2/5] test(agc): register agc_wave_control_decode beside its target The test is added to ctest in its own BUILD_TESTING block, as agc_vop1_sdwa_move_decode is, instead of in the shared test list that #1187 also extends. --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 30fe9822a..253c77920 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -298,6 +298,14 @@ target_include_directories(agc_wave_control_decode_tests PRIVATE ${LIBS_INCLUDE_ target_link_libraries(agc_wave_control_decode_tests PRIVATE shader_recompiler_agc_driver libc) set_target_properties(agc_wave_control_decode_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_wave_control_decode_tests) +if(BUILD_TESTING) + set_target_properties(agc_wave_control_decode_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_wave_control_decode COMMAND agc_wave_control_decode_tests) + if(WIN32) + set_tests_properties(agc_wave_control_decode PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() add_test_executable(agc_dcc_key_sharing_tests EXCLUDE_FROM_ALL tests/DccKeySharing.cpp) target_include_directories(agc_dcc_key_sharing_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include) From 67b28480d82fb88c15b7e92b37112923fa7482ae Mon Sep 17 00:00:00 2001 From: popcoder565 <339285150+popcoder565@users.noreply.github.com> Date: Wed, 7 Oct 2026 22:09:33 -0400 Subject: [PATCH 3/5] chore: move the wave control decode test and s_sethalt debt entry off shared lines #1372 adds its test after the vop1 decode tests and #1314 its debt entry after s_setkill, the places where this branch added its own. The wave control decode test now precedes the vop1 decode tests and the s_sethalt entry follows sceRtcParseDateTime, so either pull request can merge first without a conflict. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 10 +++++----- docs/dev/TechnicalDebt.md | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 253c77920..ae82d1d4e 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -288,11 +288,6 @@ target_link_libraries(agc_masked_select_tests PRIVATE shader_recompiler_agc_driv set_target_properties(agc_masked_select_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_masked_select_tests) -add_test_executable(agc_vop1_control_decode_tests EXCLUDE_FROM_ALL tests/Vop1ControlDecode.cpp) -target_link_libraries(agc_vop1_control_decode_tests PRIVATE shader_recompiler_agc_driver libc) -set_target_properties(agc_vop1_control_decode_tests PROPERTIES CXX_EXTENSIONS OFF) -configure_windows_unwind(agc_vop1_control_decode_tests) - add_test_executable(agc_wave_control_decode_tests EXCLUDE_FROM_ALL tests/WaveControlDecode.cpp) target_include_directories(agc_wave_control_decode_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(agc_wave_control_decode_tests PRIVATE shader_recompiler_agc_driver libc) @@ -307,6 +302,11 @@ if(BUILD_TESTING) endif() endif() +add_test_executable(agc_vop1_control_decode_tests EXCLUDE_FROM_ALL tests/Vop1ControlDecode.cpp) +target_link_libraries(agc_vop1_control_decode_tests PRIVATE shader_recompiler_agc_driver libc) +set_target_properties(agc_vop1_control_decode_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_vop1_control_decode_tests) + add_test_executable(agc_dcc_key_sharing_tests EXCLUDE_FROM_ALL tests/DccKeySharing.cpp) target_include_directories(agc_dcc_key_sharing_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include) target_link_libraries(agc_dcc_key_sharing_tests PRIVATE libc) diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 5c56e6295..6343422b2 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -61,8 +61,8 @@ Throughout the project, every function at every stage either **does exactly what - [sceVideoOutIsOutputSupported](../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - output mode encoding unknown: every mode except the default reports not supported (0), and `sceVideoOutConfigureOutput` returns `VIDEO_OUT_ERROR_UNAVAILABLE_OUTPUT_MODE` for it, instead of returning `VIDEO_OUT_ERROR_UNSUPPORTED_OUTPUT_MODE` for values the console does not define. PPSA12544 probes `0xd000000a` and selects 7680 pixels when it is supported, 3840 otherwise - [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - the open param's first word is unknown (PPSA21564 passes 16; it is not the byte size, since the affinity mask is at offset 16); only 16 is accepted. Whether the param continues past offset 24 is also unknown - [s_setkill](../../core/shader/recompiler/RdnaDecoder/src/RdnaScalarOpDecoder.cpp) (core/shader/recompiler) - decoded as `s_endpgm` for any immediate: measured on RDNA2 hardware (gfx1035), a store before it is visible and nothing after it runs ([#771](https://github.com/boykopovar/AnyPS5/pull/771)); whether the KILL status bit it sets is observable differently from `s_endpgm` is unverified -- [s_sethalt](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) (core/shader/recompiler) - with SIMM16[0] clear it is a no-op: the RDNA 2 ISA guide describes that value as resuming a halted wave, and a wave that executes it is not halted; not measured on hardware. Only SIMM16[0] is read; with it set the instruction throws - [sceRtcParseDateTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - accepted format assumed to be the ISO 8601 subset `YYYY-MM-DDTHH:MM:SS[.ffffff]` (T/t/space separator, optional fractional seconds) with the sibling RFC 3339 zone suffix (`Z` or `±HH:MM`), plus the RFC 2822 shape `Ddd, DD Mon YYYY HH:MM:SS[ GMT| ±HHMM]` and the asctime shape `Ddd Mon DD HH:MM:SS YYYY` (day zero- or space-padded) that shadPS4 parses at fixed positions; the weekday name is not checked against the date, a `±HHMM` offset is subtracted to get UTC like the RFC 3339 path (shadPS4 adds it), a string without a zone is taken as UTC, and anything outside these shapes throws instead of returning a parse error, unverified against hardware +- [s_sethalt](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) (core/shader/recompiler) - with SIMM16[0] clear it is a no-op: the RDNA 2 ISA guide describes that value as resuming a halted wave, and a wave that executes it is not halted; not measured on hardware. Only SIMM16[0] is read; with it set the instruction throws - [sceAudio3dPortOpen](../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - buffer modes 0 and 1 (also selected by the 0x10 and 0x18 parameter sizes) and 3 beds depend on an SDK version check in the module ([prosper](https://github.com/mattias800/prosper)) and throw. A repeated `sceAudio3dInitialize` (`NOT_READY` in shadPS4, 0 in prosper) and `sceAudio3dPortAdvance` on a full queue (`NOT_READY` in shadPS4, unhandled in KytyPS5) throw too - [sceAudio3dObjectReserve](../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - object ids, the `max_objects` limit and the checks of `sceAudio3dObjectReserve` and `sceAudio3dObjectUnreserve` follow [shadPS4](https://github.com/shadps4-emu/shadPS4) (PS4), the only reference with a body: ids count up from 1 per port, skipping 0, 0xFFFFFFFF and ids in use, and a failed reserve writes 0xFFFFFFFF - [AMPR WriteAddressFromCounterPairOnCompletion](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - the pair of an even counter n is assumed to give counter n in the low 32 bits and n + 1 in the high 32 bits. Wait compare values, wait flush and counter index ranges follow the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation; the flush is accepted and has no effect, as commands run in order From f11080ceefc5d0a1d9a5fc1bf39689c10208a8f4 Mon Sep 17 00:00:00 2001 From: popcoder565 <339285150+popcoder565@users.noreply.github.com> Date: Wed, 7 Oct 2026 22:55:13 -0400 Subject: [PATCH 4/5] chore: move the s_sethalt debt entry below the libSceAudio3d entries #1398 and #1399 both rewrite the sceRtcParseDateTime entry that the s_sethalt entry followed. It now follows the libSceAudio3d entries, which no open pull request edits, so either can merge first without a conflict. Co-Authored-By: Claude Opus 5.5 --- docs/dev/TechnicalDebt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 6343422b2..71aa20d18 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -62,9 +62,9 @@ Throughout the project, every function at every stage either **does exactly what - [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - the open param's first word is unknown (PPSA21564 passes 16; it is not the byte size, since the affinity mask is at offset 16); only 16 is accepted. Whether the param continues past offset 24 is also unknown - [s_setkill](../../core/shader/recompiler/RdnaDecoder/src/RdnaScalarOpDecoder.cpp) (core/shader/recompiler) - decoded as `s_endpgm` for any immediate: measured on RDNA2 hardware (gfx1035), a store before it is visible and nothing after it runs ([#771](https://github.com/boykopovar/AnyPS5/pull/771)); whether the KILL status bit it sets is observable differently from `s_endpgm` is unverified - [sceRtcParseDateTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - accepted format assumed to be the ISO 8601 subset `YYYY-MM-DDTHH:MM:SS[.ffffff]` (T/t/space separator, optional fractional seconds) with the sibling RFC 3339 zone suffix (`Z` or `±HH:MM`), plus the RFC 2822 shape `Ddd, DD Mon YYYY HH:MM:SS[ GMT| ±HHMM]` and the asctime shape `Ddd Mon DD HH:MM:SS YYYY` (day zero- or space-padded) that shadPS4 parses at fixed positions; the weekday name is not checked against the date, a `±HHMM` offset is subtracted to get UTC like the RFC 3339 path (shadPS4 adds it), a string without a zone is taken as UTC, and anything outside these shapes throws instead of returning a parse error, unverified against hardware -- [s_sethalt](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) (core/shader/recompiler) - with SIMM16[0] clear it is a no-op: the RDNA 2 ISA guide describes that value as resuming a halted wave, and a wave that executes it is not halted; not measured on hardware. Only SIMM16[0] is read; with it set the instruction throws - [sceAudio3dPortOpen](../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - buffer modes 0 and 1 (also selected by the 0x10 and 0x18 parameter sizes) and 3 beds depend on an SDK version check in the module ([prosper](https://github.com/mattias800/prosper)) and throw. A repeated `sceAudio3dInitialize` (`NOT_READY` in shadPS4, 0 in prosper) and `sceAudio3dPortAdvance` on a full queue (`NOT_READY` in shadPS4, unhandled in KytyPS5) throw too - [sceAudio3dObjectReserve](../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - object ids, the `max_objects` limit and the checks of `sceAudio3dObjectReserve` and `sceAudio3dObjectUnreserve` follow [shadPS4](https://github.com/shadps4-emu/shadPS4) (PS4), the only reference with a body: ids count up from 1 per port, skipping 0, 0xFFFFFFFF and ids in use, and a failed reserve writes 0xFFFFFFFF +- [s_sethalt](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) (core/shader/recompiler) - with SIMM16[0] clear it is a no-op: the RDNA 2 ISA guide describes that value as resuming a halted wave, and a wave that executes it is not halted; not measured on hardware. Only SIMM16[0] is read; with it set the instruction throws - [AMPR WriteAddressFromCounterPairOnCompletion](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - the pair of an even counter n is assumed to give counter n in the low 32 bits and n + 1 in the high 32 bits. Wait compare values, wait flush and counter index ranges follow the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation; the flush is accepted and has no effect, as commands run in order - [AMPR command sizes](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - commands use the emulator's own encoding with an 8-byte header, so `Nop(n)` takes 8 + 4n bytes where the console record is n dwords including its header (`NopWithData(n)`: (n + 1) dwords). The measure functions agree with the appended sizes, but a title that pads to an exact offset or the end of the buffer with `Nop(remaining / 4)` gets `SCE_AMPR_ERROR_BUFFER_FULL` or a shifted offset. The at-start flag of the `_04_00` writes is ignored; commands run in order, each after the previous one completes - [AMPR counter access](../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libSceAmpr) - signatures and checks of `WaitOnCounter_04_00`, `WriteCounter_04_00`, `ConstructNop` and `ConstructMarker` taken from the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation, which hands counter commands to the console and does not show their effect. The counters are taken as 32 bits each: the 8-byte access of counter n spans n (low) and n + 1 (high), the 2- and 1-byte accesses select a half or a byte of the counter by its offset, waits compare at the access width (signed and wrapped compares included), the AND mask applies to both sides and the atomic writes wrap within the access. The `ConstructNop` type is not recorded From 2e0b7bb90eb290e821f3176ded729cdb431e9f50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Thu, 8 Oct 2026 11:49:24 +0200 Subject: [PATCH 5/5] docs: record the s_sethalt 0 measurement in TechnicalDebt Co-Authored-By: Claude --- docs/dev/TechnicalDebt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 71aa20d18..b888703ec 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -64,7 +64,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceRtcParseDateTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - accepted format assumed to be the ISO 8601 subset `YYYY-MM-DDTHH:MM:SS[.ffffff]` (T/t/space separator, optional fractional seconds) with the sibling RFC 3339 zone suffix (`Z` or `±HH:MM`), plus the RFC 2822 shape `Ddd, DD Mon YYYY HH:MM:SS[ GMT| ±HHMM]` and the asctime shape `Ddd Mon DD HH:MM:SS YYYY` (day zero- or space-padded) that shadPS4 parses at fixed positions; the weekday name is not checked against the date, a `±HHMM` offset is subtracted to get UTC like the RFC 3339 path (shadPS4 adds it), a string without a zone is taken as UTC, and anything outside these shapes throws instead of returning a parse error, unverified against hardware - [sceAudio3dPortOpen](../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - buffer modes 0 and 1 (also selected by the 0x10 and 0x18 parameter sizes) and 3 beds depend on an SDK version check in the module ([prosper](https://github.com/mattias800/prosper)) and throw. A repeated `sceAudio3dInitialize` (`NOT_READY` in shadPS4, 0 in prosper) and `sceAudio3dPortAdvance` on a full queue (`NOT_READY` in shadPS4, unhandled in KytyPS5) throw too - [sceAudio3dObjectReserve](../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - object ids, the `max_objects` limit and the checks of `sceAudio3dObjectReserve` and `sceAudio3dObjectUnreserve` follow [shadPS4](https://github.com/shadps4-emu/shadPS4) (PS4), the only reference with a body: ids count up from 1 per port, skipping 0, 0xFFFFFFFF and ids in use, and a failed reserve writes 0xFFFFFFFF -- [s_sethalt](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) (core/shader/recompiler) - with SIMM16[0] clear it is a no-op: the RDNA 2 ISA guide describes that value as resuming a halted wave, and a wave that executes it is not halted; not measured on hardware. Only SIMM16[0] is read; with it set the instruction throws +- [s_sethalt](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) (core/shader/recompiler) - with SIMM16[0] clear it is a no-op: the RDNA 2 ISA guide describes that value as resuming a halted wave, and a wave that executes it is not halted; measured on a gfx1035 (RDNA 2) wave: `s_sethalt 0` followed by a VALU op completes and gives the normal result. Only SIMM16[0] is read; with it set the instruction throws - [AMPR WriteAddressFromCounterPairOnCompletion](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - the pair of an even counter n is assumed to give counter n in the low 32 bits and n + 1 in the high 32 bits. Wait compare values, wait flush and counter index ranges follow the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation; the flush is accepted and has no effect, as commands run in order - [AMPR command sizes](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - commands use the emulator's own encoding with an 8-byte header, so `Nop(n)` takes 8 + 4n bytes where the console record is n dwords including its header (`NopWithData(n)`: (n + 1) dwords). The measure functions agree with the appended sizes, but a title that pads to an exact offset or the end of the buffer with `Nop(remaining / 4)` gets `SCE_AMPR_ERROR_BUFFER_FULL` or a shifted offset. The at-start flag of the `_04_00` writes is ignored; commands run in order, each after the previous one completes - [AMPR counter access](../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libSceAmpr) - signatures and checks of `WaitOnCounter_04_00`, `WriteCounter_04_00`, `ConstructNop` and `ConstructMarker` taken from the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation, which hands counter commands to the console and does not show their effect. The counters are taken as 32 bits each: the 8-byte access of counter n spans n (low) and n + 1 (high), the 2- and 1-byte accesses select a half or a byte of the counter by its offset, waits compare at the access width (signed and wrapped compares included), the AND mask applies to both sides and the atomic writes wrap within the access. The `ConstructNop` type is not recorded