diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 40c7b7629..ae82d1d4e 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -288,6 +288,20 @@ 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_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) +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_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) 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