diff --git a/core/libs/prx/libSceAgc/CMakeLists.txt b/core/libs/prx/libSceAgc/CMakeLists.txt index 4e9a781f8..f1822c8ce 100644 --- a/core/libs/prx/libSceAgc/CMakeLists.txt +++ b/core/libs/prx/libSceAgc/CMakeLists.txt @@ -30,6 +30,7 @@ add_library(libSceAgc SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/DcbState/src/Registers.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DcbState/src/Workload.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/PacketInfo.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/Platform.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/RegisterDefaults.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/ShaderFusion.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/Suspend.cpp @@ -69,6 +70,19 @@ target_link_libraries(agc_command_tests PRIVATE libSceAgc libc) configure_windows_unwind(agc_command_tests) configure_windows_unwind(libSceAgc) +add_test_executable(agc_platform_tests EXCLUDE_FROM_ALL tests/Platform.cpp) +target_include_directories(agc_platform_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_platform_tests PRIVATE libSceAgc libc) +configure_windows_unwind(agc_platform_tests) +if(BUILD_TESTING) + set_target_properties(agc_platform_tests PROPERTIES EXCLUDE_FROM_ALL FALSE) + add_test(NAME agc_platform_tests COMMAND agc_platform_tests) + if(WIN32) + set_tests_properties(agc_platform_tests PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + add_test_executable(agc_register_command_tests EXCLUDE_FROM_ALL tests/RegisterCommands.cpp Command/src/Packet.cpp @@ -121,6 +135,21 @@ if(BUILD_TESTING) endif() endif() +add_test_executable(agc_interpolant_mapping_tests EXCLUDE_FROM_ALL tests/InterpolantMapping.cpp) +target_include_directories(agc_interpolant_mapping_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_interpolant_mapping_tests PRIVATE libSceAgc libc) +set_target_properties(agc_interpolant_mapping_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_interpolant_mapping_tests) + +if(BUILD_TESTING) + set_target_properties(agc_interpolant_mapping_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_interpolant_mapping COMMAND agc_interpolant_mapping_tests) + if(WIN32) + set_tests_properties(agc_interpolant_mapping PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + if(BUILD_TESTING) add_test_executable(guest_shader_link_tests ${LIBS_INCLUDE_DIR}/tests/GuestShaderLink.cpp) target_include_directories(guest_shader_link_tests PRIVATE ${LIBS_INCLUDE_DIR}) diff --git a/core/libs/prx/libSceAgc/Misc/include/Platform.hpp b/core/libs/prx/libSceAgc/Misc/include/Platform.hpp new file mode 100644 index 000000000..fd07f976a --- /dev/null +++ b/core/libs/prx/libSceAgc/Misc/include/Platform.hpp @@ -0,0 +1,4 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_PLATFORM_HPP +#define CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_PLATFORM_HPP + +#endif diff --git a/core/libs/prx/libSceAgc/Misc/src/Platform.cpp b/core/libs/prx/libSceAgc/Misc/src/Platform.cpp new file mode 100644 index 000000000..de162d3a7 --- /dev/null +++ b/core/libs/prx/libSceAgc/Misc/src/Platform.cpp @@ -0,0 +1,17 @@ +#include "prx/libSceAgc/Misc/include/Platform.hpp" +#include "prx/libSceAgc/Command/include/Packet.hpp" + +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceAgcGetIsTrinityMode(bool* isTrinityMode) { + Agc::Command::CheckAddress(reinterpret_cast(isTrinityMode), alignof(bool), __func__); + *isTrinityMode = false; + return 0; +} + +} diff --git a/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp b/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp index 515458391..5ce4d1555 100644 --- a/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp +++ b/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp @@ -8,11 +8,48 @@ #include "prx/libSceAgc/Shader/include/ShaderUtils.hpp" #include "prx/libSceAgc/Shader/include/ShaderConstants.hpp" -extern "C" { +namespace { -APS5_EXPORT("HV4j+E0MBHE", sceAgcCreateInterpolantMapping); -int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps) { - constexpr auto fn = __func__; +constexpr std::uint32_t InterpolantRegisterCount = 32; +constexpr std::uint32_t InterpolantF16HiOnly = 2; + +using CreateInterpolantValueFn = std::uint32_t (*)(std::uint32_t psWord, const ShaderSemantic* gsSemantic); + +std::uint32_t CreateInterpolantValue(std::uint32_t psWord, const ShaderSemantic* gsSemantic) { + const std::uint32_t value = ((psWord & 0x00300000u) != 0) + ? CreateInterpolantF16Value(psWord, gsSemantic) + : CreateInterpolantNonF16Value(psWord, gsSemantic); + + return (gsSemantic == nullptr) + ? CreateInterpolantDefaultValue(value, psWord) + : CreateInterpolantMappingValue(value, psWord, ShaderSemanticWord(*gsSemantic)); +} + +std::uint32_t CreateInterpolantF16HiValue(std::uint32_t psWord, const ShaderSemantic* gsSemantic) { + std::uint32_t value = ((psWord << 4u) & 0x03000000u) | 0x00080000u; + if (gsSemantic == nullptr || (psWord & ShaderSemanticWord(*gsSemantic) & 0x00200000u) == 0) { + value |= 0x00000020u; + } + value = ApplyInterpolantDefaultValueHi(value, psWord); + value |= ((psWord >> 30u) & 0x3u) << 8u; + if (gsSemantic == nullptr) { + return value; + } + + value |= (ShaderSemanticWord(*gsSemantic) >> 8u) & 0x1Fu; + if ((psWord & 0x01400000u) != 0) { + value |= 0x00000400u; + } + return value; +} + +std::uint32_t CreateInterpolantValueSplitF16(std::uint32_t psWord, const ShaderSemantic* gsSemantic) { + return (((psWord >> 20u) & 0x3u) == InterpolantF16HiOnly) + ? CreateInterpolantF16HiValue(psWord, gsSemantic) + : CreateInterpolantValue(psWord, gsSemantic); +} + +int CreateInterpolantMapping(const char* fn, ShaderRegister* regs, const Shader* gs, const Shader* ps, CreateInterpolantValueFn createValue) { if (regs == nullptr) { throw std::runtime_error(std::string(fn) + ": regs is null"); } @@ -26,6 +63,10 @@ int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* throw std::runtime_error(std::string(fn) + ": ps->input_semantics is null but num_input_semantics != 0"); } + if (ps->num_input_semantics > InterpolantRegisterCount) { + throw std::runtime_error(std::string(fn) + ": ps->num_input_semantics exceeds the interpolant register count"); + } + if (gs == nullptr) { throw std::runtime_error(std::string(fn) + ": gs is null"); } @@ -37,17 +78,7 @@ int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* for (std::uint32_t i = 0; i < ps->num_input_semantics; ++i) { const ShaderSemantic& psSemantic = ps->input_semantics[i]; const ShaderSemantic* gsSemantic = FindOutputSemantic(gs, psSemantic.semantic); - const std::uint32_t psWord = ShaderSemanticWord(psSemantic); - - std::uint32_t value = ((psWord & 0x00300000u) != 0) - ? CreateInterpolantF16Value(psWord, gsSemantic) - : CreateInterpolantNonF16Value(psWord, gsSemantic); - - value = (gsSemantic == nullptr) - ? CreateInterpolantDefaultValue(value, psWord) - : CreateInterpolantMappingValue(value, psWord, ShaderSemanticWord(*gsSemantic)); - - SetInterpolantRegister(regs, i, value); + SetInterpolantRegister(regs, i, createValue(ShaderSemanticWord(psSemantic), gsSemantic)); } FillIdentityInterpolants(regs, ps->num_input_semantics); @@ -55,3 +86,17 @@ int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* } } + +extern "C" { + +APS5_EXPORT("HV4j+E0MBHE", sceAgcCreateInterpolantMapping); +int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps) { + return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValue); +} + +APS5_EXPORT("dbOlWdppb4o", sceAgcUnknownCreateInterpolantMapping); +int APS5_VABI sceAgcUnknownCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps) { + return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValueSplitF16); +} + +} diff --git a/core/libs/prx/libSceAgc/Unimplemented.cpp b/core/libs/prx/libSceAgc/Unimplemented.cpp index e53a08cb3..dea38a6a4 100644 --- a/core/libs/prx/libSceAgc/Unimplemented.cpp +++ b/core/libs/prx/libSceAgc/Unimplemented.cpp @@ -70,10 +70,6 @@ int APS5_VABI sceAgcGetGsOversubscription() { return 0; } -int APS5_VABI sceAgcGetIsTrinityMode() { - return 0; -} - int APS5_VABI sceAgcGetSemaphoreLabel() { NotImplemented_nid_no_patch(__func__); return 0; @@ -84,8 +80,7 @@ int APS5_VABI sceAgcSetAmmSemaphoreMemory() { return 0; } -APS5_EXPORT("6nths4DHNrs", sceAgcUnknown_6nths4DHNrs); -int APS5_VABI sceAgcUnknown_6nths4DHNrs() { +int APS5_VABI sceAgcCbMemsetExclusive() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -96,38 +91,32 @@ int APS5_VABI sceAgcUnknown_7Wa3aeJgeVU() { return 0; } -APS5_EXPORT("EJBA4dbmvfg", sceAgcUnknown_EJBA4dbmvfg); -int APS5_VABI sceAgcUnknown_EJBA4dbmvfg() { +int APS5_VABI sceAgcAsyncWriteDataPatchSetDst() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("ICkECTBxrMw", sceAgcUnknown_ICkECTBxrMw); -int APS5_VABI sceAgcUnknown_ICkECTBxrMw() { +int APS5_VABI sceAgcGetGsPrimPayload() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("RTpj-tIlvZc", sceAgcUnknown_RTpj_MtIlvZc); -int APS5_VABI sceAgcUnknown_RTpj_MtIlvZc() { +int APS5_VABI sceAgcSetShaderInstrumentation() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("SwI6QxqwAC0", sceAgcUnknown_SwI6QxqwAC0); -int APS5_VABI sceAgcUnknown_SwI6QxqwAC0() { +int APS5_VABI sceAgcGetShaderInstrumentation() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("d4NZIlguzv0", sceAgcUnknown_d4NZIlguzv0); -int APS5_VABI sceAgcUnknown_d4NZIlguzv0() { +int APS5_VABI sceAgcAsyncWriteDataPatchSetAddressOrOffset() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("eAy8eGNsCuU", sceAgcUnknown_eAy8eGNsCuU); -int APS5_VABI sceAgcUnknown_eAy8eGNsCuU() { +int APS5_VABI sceAgcWriteDataPatchSetCachePolicy() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -138,14 +127,12 @@ int APS5_VABI sceAgcUnknown_rP5xLdOf26k() { return 0; } -APS5_EXPORT("tmy-+rBpspY", sceAgcUnknown_tmy_M_PrBpspY); -int APS5_VABI sceAgcUnknown_tmy_M_PrBpspY() { +int APS5_VABI sceAgcWriteDataPatchSetDst() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("y5K5tPktiL8", sceAgcUnknown_y5K5tPktiL8); -int APS5_VABI sceAgcUnknown_y5K5tPktiL8() { +int APS5_VABI sceAgcAsyncWriteDataPatchSetCachePolicy() { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp b/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp new file mode 100644 index 000000000..bee89bb8d --- /dev/null +++ b/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp @@ -0,0 +1,134 @@ +#include "SceShaders.hpp" +#include "prx/libc/include/Shutdown.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp" + +#include +#include +#include +#include +#include + +extern "C" int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps); +extern "C" int APS5_VABI sceAgcUnknownCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps); + +namespace { + +using Registers = std::array; + +void check(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +template +void expectFailure(TAction action) { + try { + action(); + } catch (const std::exception& error) { + check(error.what()[0] != '\0', "empty exception message"); + return; + } + throw std::runtime_error("expected an exception"); +} + +Registers filled() { + Registers regs{}; + std::memset(regs.data(), 0xcc, sizeof(regs)); + return regs; +} + +ShaderSemantic semantic(std::uint32_t id, std::uint32_t hardwareMapping, std::uint32_t f16) { + ShaderSemantic value{}; + value.semantic = id; + value.hardware_mapping = hardwareMapping; + value.is_f16 = f16; + return value; +} + +void checkIdentity(const Registers& regs, std::uint32_t first, const char* message) { + for (std::uint32_t i = first; i < regs.size(); ++i) { + check(regs[i].offset == ShaderRegs::SPI_PS_INPUT_CNTL_0 + i && regs[i].value == i, message); + } +} + +void testIdentity() { + auto regs = filled(); + check(sceAgcUnknownCreateInterpolantMapping(regs.data(), nullptr, nullptr) == 0, "mapping without shaders failed"); + checkIdentity(regs, 0, "mapping without a pixel shader is not the identity"); + Shader ps{}; + regs = filled(); + check(sceAgcUnknownCreateInterpolantMapping(regs.data(), nullptr, &ps) == 0, "mapping without inputs failed"); + checkIdentity(regs, 0, "mapping without inputs is not the identity"); +} + +void testMapping() { + std::array inputs{ + semantic(1, 0, 2), semantic(2, 0, 2), semantic(5, 0, 2), semantic(3, 0, 0), semantic(4, 0, 3)}; + inputs[0].is_flat_shaded = 1; + inputs[0].default_value = 1; + inputs[0].default_value_hi = 3; + inputs[1].default_value_hi = 1; + inputs[2].is_flat_shaded = 1; + inputs[2].default_value_hi = 2; + inputs[3].is_flat_shaded = 1; + inputs[3].default_value = 2; + std::array outputs{semantic(1, 7, 3), semantic(2, 4, 1), semantic(3, 9, 0), semantic(4, 2, 1)}; + Shader gs{}; + gs.output_semantics = outputs.data(); + gs.num_output_semantics = static_cast(outputs.size()); + Shader ps{}; + ps.input_semantics = inputs.data(); + ps.num_input_semantics = static_cast(inputs.size()); + + auto split = filled(); + check(sceAgcUnknownCreateInterpolantMapping(split.data(), &gs, &ps) == 0, "split f16 mapping failed"); + for (std::uint32_t i = 0; i < inputs.size(); ++i) { + check(split[i].offset == ShaderRegs::SPI_PS_INPUT_CNTL_0 + i, "interpolant register offset changed"); + } + check(split[0].value == 0x02680707u, "high f16 half provided by the gs is not mapped"); + check(split[1].value == 0x02280124u, "high f16 half missing from the gs does not use its default"); + check(split[2].value == 0x02480220u, "unmatched high f16 half does not use its default"); + check(split[3].value == 0x00000609u, "32-bit interpolant mapping changed"); + check(split[4].value == 0x03180002u, "full f16 interpolant mapping changed"); + checkIdentity(split, static_cast(inputs.size()), "unused interpolants are not the identity"); + + auto regular = filled(); + check(sceAgcCreateInterpolantMapping(regular.data(), &gs, &ps) == 0, "mapping failed"); + check(regular[3].value == split[3].value && regular[4].value == split[4].value, "mappings differ outside the high f16 half mode"); + checkIdentity(regular, static_cast(inputs.size()), "unused interpolants are not the identity"); +} + +void testRejections() { + std::array inputs{}; + Shader gs{}; + Shader ps{}; + ps.input_semantics = inputs.data(); + ps.num_input_semantics = static_cast(inputs.size()); + std::array regs{}; + std::memset(regs.data(), 0xcc, sizeof(regs)); + const auto saved = regs; + expectFailure([&] { sceAgcCreateInterpolantMapping(regs.data(), &gs, &ps); }); + expectFailure([&] { sceAgcUnknownCreateInterpolantMapping(regs.data(), &gs, &ps); }); + check(std::memcmp(regs.data(), saved.data(), sizeof(regs)) == 0, "rejected mapping wrote registers"); + ps.num_input_semantics = 1; + expectFailure([&] { sceAgcUnknownCreateInterpolantMapping(regs.data(), nullptr, &ps); }); + expectFailure([&] { sceAgcUnknownCreateInterpolantMapping(nullptr, &gs, &ps); }); +} + +} + +int main() { + try { + testIdentity(); + testMapping(); + testRejections(); + LibcRunShutdown_nid_postfix(); + std::puts("AGC interpolant mapping tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + try { LibcRunShutdown_nid_postfix(); } + catch (const std::exception& shutdown) { std::fprintf(stderr, "shutdown: %s\n", shutdown.what()); } + return 1; + } +} diff --git a/core/libs/prx/libSceAgc/tests/Platform.cpp b/core/libs/prx/libSceAgc/tests/Platform.cpp new file mode 100644 index 000000000..daa0634cf --- /dev/null +++ b/core/libs/prx/libSceAgc/tests/Platform.cpp @@ -0,0 +1,53 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libc/include/Shutdown.hpp" + +#include +#include +#include + +extern "C" int APS5_VABI sceAgcGetIsTrinityMode(bool* isTrinityMode); + +namespace { + +void check(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +template +void expectFailure(TAction action) { + try { + action(); + } catch (const std::runtime_error& error) { + check(error.what()[0] != '\0', "empty exception message"); + return; + } + throw std::runtime_error("expected an exception"); +} + +void testTrinityMode() { + std::array flags{true, true, true}; + check(sceAgcGetIsTrinityMode(&flags[1]) == 0, "Trinity mode query did not return 0"); + check(!flags[1], "base PS5 GPU reported as Trinity"); + check(flags[0] && flags[2], "Trinity mode query wrote past its one-byte flag"); +} + +void testRejections() { + expectFailure([] { sceAgcGetIsTrinityMode(nullptr); }); +} + +} + +int main() { + try { + testTrinityMode(); + testRejections(); + LibcRunShutdown_nid_postfix(); + std::puts("AGC platform tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + try { LibcRunShutdown_nid_postfix(); } + catch (const std::exception& shutdown) { std::fprintf(stderr, "shutdown: %s\n", shutdown.what()); } + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp index 00f21aa66..cf2941e62 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp @@ -128,7 +128,7 @@ private: static std::array& writeHistory(); static std::size_t& writeCursor(); static void dumpPackets(std::span commands, const std::uint32_t* guest = nullptr); - static void validate(std::span commands, std::uint32_t queue, const std::uint32_t* guest = nullptr); + static void validate(const Submission& submission, const std::uint32_t* guest = nullptr); static void reportSkip(const char* kind, const std::string& what); static std::string dumpRequest(std::uint64_t address, const ShaderRecompiler::RecompileRequest& request); static bool matchesFillKernel(std::span code, const std::vector& userData, const ShaderRecompiler::ShaderComputeStageInfo& compute); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp index ed7da06d9..cdc9f1683 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp @@ -25,6 +25,7 @@ struct Submission { std::shared_ptr shaders; std::map> flips; std::map> renderingWaits; + std::map conditionalEnds; bool suspend = false; bool waitFree = false; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp index 3add10cd8..c413e357e 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp @@ -96,6 +96,8 @@ bool WaitSatisfiedUnchecked(std::span packet); // holds will store), without reading memory. bool WaitComparesValue(std::span packet, std::uint64_t value); std::size_t WaitAwaitedBytes(std::span packet); +std::size_t ConditionalWords(std::span packet); +std::uint32_t ReadCondition(std::span packet); // A label write (RELEASE_MEM with a data select, WRITE_DATA to memory): the destination and the // bytes it stores, so the write can be recorded on the GPU behind the work it signals. Packets // without a memory destination decode to nothing. No allocation per label (tens of thousands per diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp index 492086cc3..dd7c023ba 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp @@ -47,8 +47,12 @@ void Driver::dumpPackets(std::span commands, const std::uin } } -void Driver::validate(std::span commands, std::uint32_t queue, const std::uint32_t* guest) { +void Driver::validate(const Submission& submission, const std::uint32_t* guest) { + const std::span commands = submission.commands; + const auto queue = submission.queue; + std::vector guarded; for (std::size_t cursor = 0; cursor < commands.size();) { + std::erase_if(guarded, [cursor](std::size_t end) { return end <= cursor; }); const auto header = commands[cursor]; if (Pm4::FillerPacket(header)) { ++cursor; continue; } if ((header & 0xc0000000u) != 0xc0000000u) { @@ -65,6 +69,12 @@ void Driver::validate(std::span commands, std::uint32_t que require(count <= commands.size() - cursor, "truncated PM4 packet"); try { Pm4::Validate(commands.subspan(cursor, count), queue); + if (header == FlipPacketHeader && !guarded.empty()) throw std::runtime_error("a flip inside a conditional execution range (conditional flip reservation) is not implemented"); + if (((header >> 8u) & 0xffu) == 0x22u) { + const auto end = submission.conditionalEnds.find(cursor); + if (end == submission.conditionalEnds.end()) throw std::runtime_error("conditional execution range is not made of whole packets of its command buffer"); + guarded.push_back(end->second); + } } catch (const std::exception& error) { dumpPackets(commands, guest); throw std::runtime_error("AGC driver: " + Pm4::Name(header) + " at DWORD " + std::to_string(cursor) + ": " + error.what()); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp index 80355fc7e..06dcb02cb 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp @@ -111,6 +111,7 @@ void Driver::execute(const Submission& submission) { const auto count = Pm4::PacketWords(header); const auto packet = std::span(submission.commands).subspan(cursor, count); const auto opcode = (header >> 8u) & 0xffu; + auto nextCursor = cursor + count; std::shared_lock deviceUse(deviceReplacement, std::defer_lock); if (opcode != 0x3c && opcode != 0x93 && header != RenderingWaitPacketHeader && header != FlipPacketHeader) deviceUse.lock(); @@ -272,6 +273,11 @@ void Driver::execute(const Submission& submission) { } }); }); finishDrawPacket(drawn); + } else if (opcode == 0x22) { + recordQueuedLabelsBeforeRead(submission.queue); + const auto condition = Pm4::ReadCondition(packet); + if (condition == 0) nextCursor = submission.conditionalEnds.at(cursor); + if (traceGpu) std::fprintf(stderr, "[gpu] %.1f queue 0x%x COND_EXEC at DWORD %zu reads 0x%x at 0x%llx: %s %zu dwords\n", TraceMs(), submission.queue, cursor, condition, static_cast(packet[1] | (static_cast(packet[2]) << 32u)), condition == 0 ? "skips" : "executes", submission.conditionalEnds.at(cursor) - cursor - count); } else if (sampleDump && !wroteOnGpu) { dumpSampleCounters(packet[2] | (static_cast(packet[3]) << 32u)); } else if (opcode != 0x42 && opcode != 0x46 && opcode != 0x58) { @@ -284,7 +290,7 @@ void Driver::execute(const Submission& submission) { if (endOfPipeInterrupt && !interruptDeferred) AgcDriverDeliverEopInterrupt(submission.queue); } if (drawPacket || (sampleDump && wroteOnGpu)) Graphics::Recorder::CountRecordedWork(); - cursor += count; + cursor = nextCursor; } static const bool submitAtEnd = std::getenv("APS5_SUBMIT_AT_END") != nullptr; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp index 4dbe52c02..edd9b5b70 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp @@ -11,6 +11,7 @@ namespace AgcDriver::DriverDetail { void Driver::copyCommands(Submission& submission, const std::uint32_t* guest, std::size_t words) { submission.commands.clear(); + submission.conditionalEnds.clear(); std::size_t budget = std::size_t{1} << 26u; copySegment(submission, guest, words, budget); } @@ -18,34 +19,57 @@ void Driver::copyCommands(Submission& submission, const std::uint32_t* guest, st bool Driver::copySegment(Submission& submission, const std::uint32_t* guest, std::size_t words, std::size_t& budget) { require(words <= budget, "command buffer jumps exceed the copy limit (a jump loop?)"); budget -= words; + std::vector> guarded; + const auto reach = [&](std::size_t cursor, std::size_t next) { + std::erase_if(guarded, [&](const auto& range) { + if (range.first != cursor) return false; + submission.conditionalEnds.emplace(range.second, submission.commands.size()); + return true; + }); + for (const auto& range : guarded) require(range.first >= next, "conditional execution range ends inside a packet"); + }; for (std::size_t cursor = 0; cursor < words;) { const auto header = guest[cursor]; - if (Pm4::FillerPacket(header)) { submission.commands.push_back(header); ++cursor; continue; } + if (Pm4::FillerPacket(header)) { + reach(cursor, cursor + 1); + submission.commands.push_back(header); + ++cursor; + continue; + } const auto count = (header & 0xc0000000u) == 0xc0000000u ? Pm4::PacketWords(header) : words - cursor; if ((header & 0xc0000000u) != 0xc0000000u || count > words - cursor) { submission.commands.insert(submission.commands.end(), guest + cursor, guest + words); return false; } + reach(cursor, cursor + count); const auto opcode = (header >> 8u) & 0xffu; if (opcode == 0x3fu) { require(count == 4, "invalid INDIRECT_BUFFER size"); const auto* target = reinterpret_cast(static_cast(guest[cursor + 1] & ~3u) | (static_cast(guest[cursor + 2] & 0xffffu) << 32u)); const std::size_t targetWords = guest[cursor + 3] & 0xfffffu; const bool chain = (guest[cursor + 3] & (1u << 20u)) != 0; + require(!chain || guarded.empty(), "a chained INDIRECT_BUFFER inside a conditional execution range is not implemented"); GuestMemory::CheckRange(target, targetWords * sizeof(std::uint32_t), alignof(std::uint32_t)); - if (copySegment(submission, target, targetWords, budget)) return true; + if (copySegment(submission, target, targetWords, budget)) { + require(guarded.empty(), "a REWIND inside a conditional execution range is not implemented"); + return true; + } if (chain) return false; cursor += count; continue; } + if (opcode == 0x22u && count == 5) guarded.emplace_back(cursor + count + Pm4::ConditionalWords(std::span(guest + cursor, count)), submission.commands.size()); submission.commands.insert(submission.commands.end(), guest + cursor, guest + cursor + count); cursor += count; if (opcode == 0x59u) { + require(guarded.empty(), "a REWIND inside a conditional execution range is not implemented"); submission.rewindTail = guest + cursor; submission.rewindWords = words - cursor; return true; } } + reach(words, words); + require(guarded.empty(), "conditional execution range exceeds its command buffer"); return false; } @@ -104,7 +128,7 @@ void Driver::executeRewindTail(const Submission& stalled) { Submission tail{}; tail.queue = stalled.queue; copyCommands(tail, stalled.rewindTail, stalled.rewindWords); - validate(tail.commands, tail.queue, stalled.rewindTail); + validate(tail, stalled.rewindTail); waitForFlipRoom(tail); { std::lock_guard lock(mutex); @@ -135,7 +159,7 @@ void Driver::Submit(const Packet* packet, std::uint32_t queue) { copyCommands(submission, descriptor.addr, descriptor.dw_num); } const auto copied = profile ? std::chrono::steady_clock::now() : start; - validate(submission.commands, queue, descriptor.addr); + validate(submission, descriptor.addr); waitForFlipRoom(submission); static const bool trace = std::getenv("APS5_TRACE_GPU") != nullptr; if (trace) std::fprintf(stderr, "[gpu] %.1f submit queue=0x%x dwords=%zu at %p\n", TraceMs(), queue, submission.commands.size(), static_cast(descriptor.addr)); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index 9dfdfad7b..a07acee1a 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -166,7 +166,7 @@ std::string_view UnsupportedReason(std::uint32_t header) { } } switch (opcode) { - case 0x11: case 0x12: case 0x13: case 0x15: case 0x16: case 0x26: case 0x27: + case 0x11: case 0x12: case 0x13: case 0x15: case 0x16: case 0x22: case 0x26: case 0x27: case 0x2a: case 0x2d: case 0x2f: case 0x35: case 0x37: case 0x40: case 0x42: case 0x46: case 0x50: case 0x58: case 0x63: case 0x64: case 0x69: case 0x76: case 0x79: case 0x7a: case 0x81: case 0x83: case 0x9f: return {}; @@ -176,7 +176,6 @@ std::string_view UnsupportedReason(std::uint32_t header) { case 0x3a: case 0x8d: return "graphics draw, shader stages and guest render-target materialization are not implemented"; case 0x20: return "GPU query predication is not implemented"; - case 0x22: return "conditional command execution and conditional flip reservation are not implemented"; case 0x33: case 0x3f: return "nested command buffers, branching and nested flip reservation are not implemented"; case 0x3c: case 0x93: return {}; case 0x39: @@ -194,8 +193,11 @@ std::string_view UnsupportedReason(std::uint32_t header) { } } +constexpr std::uint32_t ConditionCachePolicy = 3u << 25u; +constexpr std::uint32_t ConditionalWordsMask = 0x3fffu; constexpr std::uint32_t DmaSourceCachePolicy = 3u << 13u; constexpr std::uint32_t DmaDestinationCachePolicy = 3u << 25u; +constexpr std::uint32_t WriteDataCachePolicy = 3u << 25u; void Validate(std::span packet, std::uint32_t queue) { require(!packet.empty(), "truncated PM4 header"); @@ -296,6 +298,13 @@ void Validate(std::span packet, std::uint32_t queue) { require(packet.size() == 3 || packet.size() == 4, "invalid indirect dispatch size"); require((packet.back() & ~0xa020u) == 0x41u, "indirect dispatch modifiers are not implemented"); break; + case 0x22: + size(5); + require((packet[1] & 3u) == 0, "COND_EXEC reserved address bits are not implemented"); + require(packet[2] <= 0xffffu, "COND_EXEC address bits above 48 are not implemented"); + require((packet[3] & ~(queue == 0 ? 0u : ConditionCachePolicy)) == 0, "COND_EXEC reserved control fields are not implemented"); + require((packet[4] & ~ConditionalWordsMask) == 0, "COND_EXEC reserved count bits are not implemented"); + break; case 0x42: size(2); require(packet[1] == 0, "unsupported PFP_SYNC_ME payload"); break; case 0x46: { require((packet[1] & ~0x73fu) == 0, "unsupported EVENT_WRITE flags or reserved bits"); @@ -380,7 +389,7 @@ void Validate(std::span packet, std::uint32_t queue) { break; case 0x37: { require(packet.size() >= 5, "WRITE_DATA has no data"); - require((packet[1] & ~0x40110f00u) == 0, "WRITE_DATA engine, cache or reserved fields are not implemented"); + require((packet[1] & ~(0x40110f00u | WriteDataCachePolicy)) == 0, "WRITE_DATA engine or reserved fields are not implemented"); const auto destination = (packet[1] >> 8u) & 0xfu; require(destination == 1 || destination == 2 || (queue != 0 && destination == 5), "WRITE_DATA register or GDS destination is not implemented"); require((packet[2] & 3u) == 0, "misaligned WRITE_DATA destination"); @@ -462,6 +471,19 @@ std::size_t WaitAwaitedBytes(std::span packet) { return wide && (packet.size() < 8 || packet[7] != 0) ? 8 : 4; } +std::size_t ConditionalWords(std::span packet) { + require(packet.size() == 5 && ((packet[0] >> 8u) & 0xffu) == 0x22u, "expected COND_EXEC packet"); + return packet[4] & ConditionalWordsMask; +} + +std::uint32_t ReadCondition(std::span packet) { + require(packet.size() == 5 && ((packet[0] >> 8u) & 0xffu) == 0x22u, "expected COND_EXEC packet"); + std::uint32_t value = 0; + const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::Wait); + GuestMemory::Read(address(packet[1], packet[2]), std::as_writable_bytes(std::span(&value, 1)), 4); + return value; +} + std::optional DecodeLabelWrite(std::span packet) { const auto opcode = (packet[0] >> 8u) & 0xffu; if (opcode == 0x49 && packet.size() >= 7) { @@ -537,7 +559,7 @@ bool UsesGpuCacheBarrier(std::span packet) { bool AccessesMemory(std::uint32_t header) { switch ((header >> 8u) & 0xffu) { - case 0x27: case 0x3c: case 0x93: case 0x49: case 0x16: case 0x2d: case 0x35: case 0x24: case 0x25: case 0x2c: case 0x38: case 0x37: case 0x40: case 0x50: case 0x63: case 0x64: case 0x83: case 0x9f: return true; + case 0x22: case 0x27: case 0x3c: case 0x93: case 0x49: case 0x16: case 0x2d: case 0x35: case 0x24: case 0x25: case 0x2c: case 0x38: case 0x37: case 0x40: case 0x50: case 0x63: case 0x64: case 0x83: case 0x9f: return true; default: return false; } } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 98d526ccb..b040bbb41 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -803,7 +803,6 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u state->capabilities.push_back(5283); state->spirvExtensions.push_back("SPV_EXT_mesh_shader"); } - APS5_LOG_OUT("Vulkan features tessellationAvailable=%u mesh=%u depthClip=%u depthRangeUnrestricted=%u", static_cast(state->tessellationShader), static_cast(state->meshShader), static_cast(state->depthClipControl), static_cast(state->depthRangeUnrestricted)); const float priority = 1.0f; VkDeviceQueueCreateInfo queueInfo{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}; queueInfo.queueFamilyIndex = family; @@ -824,6 +823,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u enabled.tessellationShader = available.tessellationShader; state->tessellationShader = enabled.tessellationShader == VK_TRUE; if (state->tessellationShader) state->capabilities.push_back(3); + APS5_LOG_OUT("Vulkan features tessellationAvailable=%u mesh=%u depthClip=%u depthRangeUnrestricted=%u", static_cast(state->tessellationShader), static_cast(state->meshShader), static_cast(state->depthClipControl), static_cast(state->depthRangeUnrestricted)); require(available.samplerAnisotropy && available.textureCompressionBC, "device lacks sampler anisotropy or BC texture compression support required for texture sampling"); enabled.samplerAnisotropy = VK_TRUE; enabled.textureCompressionBC = VK_TRUE; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp index 205551744..69d086ca4 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp @@ -411,6 +411,7 @@ private: std::vector storageKeys; std::vector storageFirstLayer; std::vector storageWritten; + std::vector storageAtomic; std::vector> samplers; bool reusable = false; std::vector directRegions; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp index f1ef0b0da..198939b7b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp @@ -114,6 +114,7 @@ public: // so successive mip writes of a chain share one image and one write-back. VkImageView View(std::uint32_t mip); VkImageView FirstLayerView(std::uint32_t mip); + VkImageView AtomicView(std::uint32_t mip, bool firstLayer); // Render targets live in the same images: draws attach mip 0 through a view of the color // buffer's format and mark the image dirty like a storage write. bool Attachable() const { return attachable; } @@ -394,7 +395,7 @@ private: bool clearByKeysFill(DccKeys keys, std::uint8_t key); bool overlaps(std::uint64_t address, std::size_t bytes) const; bool pendingUnitInside(std::uint64_t address, std::size_t bytes) const; - VkImageView createView(std::uint32_t mip, bool firstLayer = false) const; + VkImageView createView(std::uint32_t mip, bool firstLayer, VkFormat format) const; void release() noexcept; Context context; @@ -442,6 +443,7 @@ private: std::uint32_t defaultMip = 0; std::map extraViews; std::map firstLayerViews; + std::map, VkImageView> atomicViews; bool attachable = false; std::map, VkImageView> attachmentViews; VkFormat storageFormat = VK_FORMAT_UNDEFINED; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index 0dafb11f8..c1fff993b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -1056,7 +1056,7 @@ void ShaderResources::buildComplete() { break; case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: write.pImageInfo = images.data() + images.size(); - for (const auto index : binding.imageAllocations) images.push_back({VK_NULL_HANDLE, storageFirstLayer[index] ? storageTextures[index]->FirstLayerView(storageMips[index]) : storageTextures[index]->View(storageMips[index]), VK_IMAGE_LAYOUT_GENERAL}); + for (const auto index : binding.imageAllocations) images.push_back({VK_NULL_HANDLE, storageAtomic[index] ? storageTextures[index]->AtomicView(storageMips[index], storageFirstLayer[index]) : storageFirstLayer[index] ? storageTextures[index]->FirstLayerView(storageMips[index]) : storageTextures[index]->View(storageMips[index]), VK_IMAGE_LAYOUT_GENERAL}); break; case VK_DESCRIPTOR_TYPE_SAMPLER: write.pImageInfo = images.data() + images.size(); @@ -1201,6 +1201,7 @@ std::vector ShaderResources::ContentKey(const CompiledShader& sha packBits(binding.imageWritten); packBits(binding.samplerDepthCompare); packBits(binding.imageDepthCompare); + packBits(binding.imageAtomic); // Read-only elements are bound without a write set: an object built for one written set // must not serve a build with another (the variant implies it, this makes it explicit). packBits(binding.bufferWritten); @@ -2564,6 +2565,7 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind storageFirstLayer.push_back(firstLayer); // Images the shader only reads have nothing to store back. storageWritten.push_back(element >= binding.imageWritten.size() || binding.imageWritten[element]); + storageAtomic.push_back(element < binding.imageAtomic.size() && binding.imageAtomic[element]); describedRanges.push_back({"storage", resource.baseAddress, guestBytes, resource.width, resource.height, resource.format, static_cast(resource.tileMode), resource.dccAddress}); item.imageAllocations.push_back(storageTextures.size() - 1); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index 48b9a1e9c..c8d156bf5 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -70,7 +70,7 @@ constexpr std::uint32_t ScanModeMask = ~2u; constexpr std::uint32_t ScanControlMask = ~0x06003fffu; constexpr std::uint32_t ScreenOffsetMask = ~0x01ff01ffu; // Bits 26/27 (ZCLIP_NEAR/FAR_DISABLE) become depth clamping; bit 19 selects the [0, 1] clip space. -constexpr std::uint32_t ClipControlMask = ~(0x80000u | 0x0c000000u); +constexpr std::uint32_t ClipControlMask = ~(0x80000u | 0x01000000u | 0x0c000000u); // Debug aid: APS5_IGNORE_DEPTH_TEST=1 renders depth- and stencil-tested draws without a depth // target as if their tests always passed (wrong occlusion, but the draws run), so stages that diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index 777529fa5..2bb762263 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -691,7 +691,7 @@ StorageTexture::StorageTexture(const Context& context, TextureDetiler& detiler, uploadReason = "first"; upload(); defaultMip = mipLevel; - view = createView(mipLevel); + view = createView(mipLevel, false, storageFormat); { auto& live = Live(); std::lock_guard lock(live.mutex); @@ -942,15 +942,18 @@ bool AdjacentGenerationEnabled() { } -VkImageView StorageTexture::createView(std::uint32_t mip, bool firstLayer) const { +VkImageView StorageTexture::createView(std::uint32_t mip, bool firstLayer, VkFormat format) const { Require(mip < descriptor.mipCount, "storage texture mip level is outside the texture"); Require(!firstLayer || descriptor.dimension == TextureDimension::k2DArray, "a first-layer storage view needs a 2D array surface"); const auto viewLayerCount = firstLayer ? 1u : geometry.imageLayers - descriptor.baseArray; + VkImageViewUsageCreateInfo usage{VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO}; + usage.usage = VK_IMAGE_USAGE_STORAGE_BIT; VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + viewInfo.pNext = format == storageFormat ? nullptr : &usage; viewInfo.image = image; // Storage views address one mip; cube faces are written as array layers. viewInfo.viewType = firstLayer ? VK_IMAGE_VIEW_TYPE_2D : descriptor.dimension == TextureDimension::k1D ? VK_IMAGE_VIEW_TYPE_1D : descriptor.dimension == TextureDimension::k2D ? VK_IMAGE_VIEW_TYPE_2D : descriptor.dimension == TextureDimension::k3D ? VK_IMAGE_VIEW_TYPE_3D : VK_IMAGE_VIEW_TYPE_2D_ARRAY; - viewInfo.format = storageFormat; + viewInfo.format = format; viewInfo.components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY}; viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, mip, 1u, descriptor.baseArray, viewLayerCount}; VkImageView created = VK_NULL_HANDLE; @@ -984,7 +987,7 @@ VkImageView StorageTexture::View(std::uint32_t mip) { if (mip == defaultMip) return view; const auto found = extraViews.find(mip); if (found != extraViews.end()) return found->second; - const auto created = createView(mip); + const auto created = createView(mip, false, storageFormat); extraViews.emplace(mip, created); return created; } @@ -992,11 +995,21 @@ VkImageView StorageTexture::View(std::uint32_t mip) { VkImageView StorageTexture::FirstLayerView(std::uint32_t mip) { const auto found = firstLayerViews.find(mip); if (found != firstLayerViews.end()) return found->second; - const auto created = createView(mip, true); + const auto created = createView(mip, true, storageFormat); firstLayerViews.emplace(mip, created); return created; } +VkImageView StorageTexture::AtomicView(std::uint32_t mip, bool firstLayer) { + if (storageFormat == VK_FORMAT_R32_UINT) return firstLayer ? FirstLayerView(mip) : View(mip); + Require(storageFormat == VK_FORMAT_R32_SINT || storageFormat == VK_FORMAT_R32_SFLOAT, "storage image atomics need a surface of one 32-bit component"); + const auto found = atomicViews.find({mip, firstLayer}); + if (found != atomicViews.end()) return found->second; + const auto created = createView(mip, firstLayer, VK_FORMAT_R32_UINT); + atomicViews.emplace(std::pair{mip, firstLayer}, created); + return created; +} + const char* LookupOutcomes::Name(Kind kind) { static constexpr const char* names[Count] = {"sampled fast hit", "sampled fast miss", "sampled hit view", "sampled hit cleared view", "sampled hit snapshot", "sampled made view", "sampled made snapshot", "storage hit", "storage made", "refresh unchanged", "refresh compared", "upload direct", "upload cpu", "upload clear", "dcc scan", "pending flush"}; return kind < Count ? names[kind] : "?"; @@ -3559,6 +3572,8 @@ void StorageTexture::release() noexcept { extraViews.clear(); for (const auto& [mip, extra] : firstLayerViews) context.Function("vkDestroyImageView")(context.device, extra, nullptr); firstLayerViews.clear(); + for (const auto& [key, atomic] : atomicViews) context.Function("vkDestroyImageView")(context.device, atomic, nullptr); + atomicViews.clear(); for (const auto& [format, attachment] : attachmentViews) context.Function("vkDestroyImageView")(context.device, attachment, nullptr); attachmentViews.clear(); if (view) context.Function("vkDestroyImageView")(context.device, view, nullptr); diff --git a/core/libs/prx/libSceAgcDriver/Unimplemented.cpp b/core/libs/prx/libSceAgcDriver/Unimplemented.cpp index 8166a546f..775fcced3 100644 --- a/core/libs/prx/libSceAgcDriver/Unimplemented.cpp +++ b/core/libs/prx/libSceAgcDriver/Unimplemented.cpp @@ -85,26 +85,22 @@ int APS5_VABI sceAgcDriverUnregisterWorkloadStream() { return 0; } -APS5_EXPORT("+iAOE3jCnkk", sceAgcDriverUnknown__PiAOE3jCnkk); -int APS5_VABI sceAgcDriverUnknown__PiAOE3jCnkk() { +int APS5_VABI sceAgcDriverSetSubmitValidationMode() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("ICaGtkEIXTk", sceAgcDriverUnknown_ICaGtkEIXTk); -int APS5_VABI sceAgcDriverUnknown_ICaGtkEIXTk() { +int APS5_VABI sceAgcDriverGetSubmitValidationConfig() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("WHIOMbb+iIU", sceAgcDriverUnknown_WHIOMbb_PiIU); -int APS5_VABI sceAgcDriverUnknown_WHIOMbb_PiIU() { +int APS5_VABI sceAgcDriverGetSubmitValidationMode() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("chJWZcNSzjk", sceAgcDriverUnknown_chJWZcNSzjk); -int APS5_VABI sceAgcDriverUnknown_chJWZcNSzjk() { +int APS5_VABI sceAgcDriverSetSubmitValidationConfig() { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceAgcDriver/tests/Flip.cpp b/core/libs/prx/libSceAgcDriver/tests/Flip.cpp index 9beb24074..596974c90 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Flip.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Flip.cpp @@ -114,6 +114,12 @@ void testFlipAndBoundary() { Packet rejected{rollback.data(), 12, 0, {}}; expectFailure([&] { sceAgcDriverSubmitDcb(&rejected); }); check(output->state->alive == 0 && output->state->ready == 0, "rejected submission retained or executed a reservation"); + alignas(4) static std::uint32_t condition = 1; + const auto conditionAddress = reinterpret_cast(&condition); + std::array guarded{0xc0032200, static_cast(conditionAddress), static_cast(conditionAddress >> 32u), 0, 6, 0xc004105c, 7, 0, 1, 0, 0}; + Packet conditionalFlip{guarded.data(), 11, 0, {}}; + check(expectFailure([&] { sceAgcDriverSubmitDcb(&conditionalFlip); }).find("a flip inside a conditional execution range") != std::string::npos, "a flip inside a COND_EXEC range was not rejected"); + check(output->state->alive == 0 && output->state->ready == 0, "a rejected conditional flip retained or executed a reservation"); { std::lock_guard lock(output->state->mutex); output->state->block = true; diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 3cd226c5a..75b240db0 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -728,9 +728,23 @@ void DepthClipTests() { expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "inverted viewport depth clamp"); queue.context[0xb4] = 0; for (std::uint32_t bit = 0; bit < 32; ++bit) { - if (bit == 19 || bit == 26 || bit == 27) continue; - queue.context[0x204] = 1u << bit; - expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "PA_CL_CLIP_CNTL"); + if (bit == 19 || bit == 24 || bit == 26 || bit == 27) continue; + for (const auto linearBit : {0u, 0x01000000u}) { + queue.context[0x204] = (1u << bit) | linearBit; + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "PA_CL_CLIP_CNTL"); + Require(AgcDriver::Graphics::DrawRejection(queue, false).find("PA_CL_CLIP_CNTL") != std::string::npos, "the precheck accepted an unsupported PA_CL_CLIP_CNTL bit"); + } + } + queue = makeState(); + queue.context[0x1b3] = 2; + queue.context[0x1b4] = 2; + for (const auto clip : {0x00080000u, 0x00000000u, 0x0c080000u, 0x04000000u}) { + queue.context[0x204] = clip; + const auto plain = AgcDriver::Graphics::DecodeState(queue); + queue.context[0x204] = clip | 0x01000000u; + const auto linearClip = AgcDriver::Graphics::DecodeState(queue); + Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "the precheck rejected DX_LINEAR_ATTR_CLIP_ENA"); + Require(linearClip.negativeOneToOne == plain.negativeOneToOne && linearClip.depthClamp == plain.depthClamp && linearClip.viewport.minDepth == plain.viewport.minDepth && linearClip.viewport.maxDepth == plain.viewport.maxDepth, "DX_LINEAR_ATTR_CLIP_ENA changed the clip space, depth clamping or depth range"); } } diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index 4c559fc5f..2efbe1ab5 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -2,12 +2,16 @@ #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "prx/libSceAgcDriver/Submit/include/Acb.hpp" #include "prx/libc/include/Shutdown.hpp" +#include +#include #include #include #include #include #include +#include #include #ifdef _WIN32 #ifndef NOMINMAX @@ -290,6 +294,9 @@ void testMemory() { check(data[0] == 22 && data[1] == 12, "WRITE_DATA fixed destination failed"); execute(state, makePacket(0x37, {0x40000100, low(data.data()), high(data.data()), 41, 42})); check(data[0] == 41 && data[1] == 42, "WRITE_DATA from the PFP failed"); + execute(state, makePacket(0x37, {0x04100200, low(data.data()), high(data.data()), 61, 62})); + check(data[0] == 61 && data[1] == 62, "WRITE_DATA with a cache policy failed"); + expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x37, {0x08000100, low(data.data()), high(data.data()), 71}), 0); }, "reserved"); expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x37, {0x80000100, low(data.data()), high(data.data()), 51}), 0); }, "engine"); execute(state, makePacket(0x81, {4, 31, 32})); execute(state, makePacket(0x83, {4, 2, low(data.data()), high(data.data())})); @@ -405,6 +412,60 @@ void testMemorySynchronization() { check(memory.destination == 42, "failed synchronization changed the destination"); } +void testConditionalValidation() { + alignas(4) static std::uint32_t condition = 0; + const auto valid = makePacket(0x22, {low(&condition), high(&condition), 0, 0x3fff}); + check(AgcDriver::Pm4::UnsupportedReason(valid[0]).empty() && AgcDriver::Pm4::AccessesMemory(valid[0]), "COND_EXEC is rejected or does not synchronize guest memory"); + AgcDriver::Pm4::Validate(valid, 0); + AgcDriver::Pm4::Validate(valid, 0x20); + check(AgcDriver::Pm4::ConditionalWords(valid) == 0x3fff, "COND_EXEC count decoded wrong"); + check(AgcDriver::Pm4::ConditionalWords(makePacket(0x22, {low(&condition), high(&condition), 0, 0})) == 0, "empty COND_EXEC range decoded wrong"); + AgcDriver::Pm4::Validate(makePacket(0x22, {low(&condition), high(&condition), 3u << 25u, 5}), 0x20); + const auto invalidWord = [&](std::size_t word, std::uint32_t value, std::uint32_t queue, const char* text) { + auto packet = valid; + packet[word] = value; + expectFailure([&] { AgcDriver::Pm4::Validate(packet, queue); }, text); + }; + invalidWord(1, low(&condition) | 1u, 0, "reserved address bits"); + invalidWord(1, low(&condition) | 2u, 0x20, "reserved address bits"); + invalidWord(2, 0x10000u, 0, "above 48"); + invalidWord(3, 3u << 25u, 0, "reserved control fields"); + invalidWord(3, 1u, 0x20, "reserved control fields"); + invalidWord(3, 1u << 27u, 0x20, "reserved control fields"); + invalidWord(4, 0x4000u, 0, "reserved count bits"); + invalidWord(4, 0x80000005u, 0x20, "reserved count bits"); + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x22, {0, 0, 0}), 0); }, "packet size"); + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x22, {0, 0, 0, 0, 0}), 0); }, "packet size"); + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x22, {0, 0, 0, 0}, 1), 0); }, "header flags"); + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x22, {0, 0, 0, 0}, 2), 0x20); }, "header flags"); + expectFailure([] { AgcDriver::Pm4::ConditionalWords(makePacket(0x37, {0x100, 0, 0, 0})); }, "expected COND_EXEC"); + AgcDriver::QueueState state; + expectFailure([&] { AgcDriver::Pm4::Execute(valid, state); }, "driver execution"); +} + +void testConditionReadSynchronization() { + alignas(4) static std::uint32_t condition = 0; + static std::size_t flushed = 0; + condition = 0; + flushed = 0; + struct FlushHookReset { + ~FlushHookReset() { AgcDriver::GuestMemory::SetFlushHook(nullptr); } + } reset; + AgcDriver::GuestMemory::SetFlushHook([](std::uint64_t address, std::size_t bytes) { + check(address == reinterpret_cast(&condition) && bytes == sizeof(condition), "COND_EXEC synchronized an unrelated range"); + condition = 0x80; + ++flushed; + }); + const auto packet = makePacket(0x22, {low(&condition), high(&condition), 0, 5}); + check(AgcDriver::Pm4::ReadCondition(packet) == 0x80 && flushed == 1, "COND_EXEC read its condition before the GPU work that writes it"); + AgcDriver::GuestMemory::SetFlushHook([](std::uint64_t, std::size_t) { + throw std::runtime_error("condition synchronization failed"); + }); + expectFailure([&] { AgcDriver::Pm4::ReadCondition(packet); }, "condition synchronization failed"); + AgcDriver::GuestMemory::SetFlushHook(nullptr); + expectFailure([] { AgcDriver::Pm4::ReadCondition(makePacket(0x22, {0x1000, 0, 0, 5})); }, "guest"); +} + void testWriteChangedKeepsUntouchedBytes() { alignas(256) static std::uint8_t guest[256]; std::memset(guest, 0, sizeof(guest)); @@ -541,6 +602,97 @@ void testDriverSubmission() { check(destination[0] == 0, "rejected submission executed a prefix"); } +std::vector joinPackets(std::initializer_list> packets) { + std::vector words; + for (const auto& packet : packets) words.insert(words.end(), packet.begin(), packet.end()); + return words; +} + +std::vector writeWord(std::uint32_t& target, std::uint32_t value) { + return makePacket(0x37, {0x00100200, low(&target), high(&target), value}); +} + +std::vector conditional(const std::uint32_t& condition, std::uint32_t words, std::uint32_t control = 0) { + return makePacket(0x22, {low(&condition), high(&condition), control, words}); +} + +std::vector indirectBuffer(const std::vector& target, bool chain = false) { + return makePacket(0x3f, {low(target.data()), high(target.data()), static_cast(target.size()) | (chain ? 1u << 20u : 0u)}); +} + +void submitWords(std::vector& words, std::uint32_t queue = 0) { + Packet packet{words.data(), static_cast(words.size()), 0, {}}; + check((queue == 0 ? sceAgcDriverSubmitDcb(&packet) : sceAgcDriverSubmitAcb(queue, &packet)) == 0, "conditional submission failed"); +} + +void testConditionalSubmission() { + alignas(8) static std::uint32_t zero = 0, one = 1, condition = 0; + static std::array results{}; + results.fill(0); + const auto run = [](std::vector words, std::uint32_t queue = 0) { + submitWords(words, queue); + AgcDriverWaitIdle_nid_postfix(); + }; + run(joinPackets({conditional(one, 5), writeWord(results[0], 11), writeWord(results[1], 12)})); + check(results[0] == 11 && results[1] == 12, "COND_EXEC skipped a range whose condition is set"); + run(joinPackets({conditional(zero, 6), {0x80000000u}, writeWord(results[0], 21), writeWord(results[1], 22)})); + check(results[0] == 11 && results[1] == 22, "COND_EXEC did not skip exactly its range when the condition is zero"); + run(joinPackets({conditional(zero, 0), writeWord(results[2], 23)})); + check(results[2] == 23, "an empty COND_EXEC range skipped the next packet"); + alignas(4) static std::uint32_t never = 0; + never = 0; + auto skippedWait = joinPackets({conditional(zero, 9), makePacket(0x3c, {0x13, low(&never), high(&never), 1, 0xffffffffu, 0x190}), makePacket(0x10, {0}, 0x30), writeWord(results[14], 24)}); + submitWords(skippedWait); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); + while (std::atomic_ref(results[14]).load() != 24 && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + const bool skipped = std::atomic_ref(results[14]).load() == 24; + std::atomic_ref(never).store(1); + AgcDriverWaitIdle_nid_postfix(); + check(skipped, "a WAIT_REG_MEM inside a skipped COND_EXEC range was executed"); + + condition = 1; + run(joinPackets({writeWord(condition, 0), conditional(condition, 5), writeWord(results[0], 31), writeWord(results[1], 32)})); + check(condition == 0 && results[0] == 11 && results[1] == 32, "COND_EXEC read its condition before an earlier packet of its queue stored it"); + run(joinPackets({writeWord(condition, 9), conditional(condition, 5), writeWord(results[0], 41)})); + check(results[0] == 41, "COND_EXEC skipped a range whose condition an earlier packet of its queue set"); + condition = 0; + run(joinPackets({conditional(condition, 5, 3u << 25u), writeWord(results[3], 42), writeWord(condition, 1), conditional(condition, 5, 1u << 25u), writeWord(results[4], 43)}), 0x20); + check(results[3] == 0 && results[4] == 43, "compute queue COND_EXEC with a cache policy evaluated wrong"); + + run(joinPackets({conditional(one, 10), conditional(zero, 10), writeWord(results[5], 51), writeWord(results[6], 52), writeWord(results[7], 53)})); + check(results[5] == 0 && results[6] == 0 && results[7] == 53, "a nested COND_EXEC range reaching past the outer range was not skipped exactly"); + run(joinPackets({conditional(zero, 10), conditional(one, 10), writeWord(results[5], 54), writeWord(results[6], 55), writeWord(results[7], 56)})); + check(results[5] == 0 && results[6] == 55 && results[7] == 56, "a skipped COND_EXEC range did not skip the COND_EXEC inside it"); + + static std::vector target; + target = joinPackets({writeWord(results[8], 61), conditional(zero, 5), writeWord(results[9], 62), writeWord(results[10], 63)}); + run(joinPackets({conditional(zero, 4), indirectBuffer(target), writeWord(results[11], 64)})); + check(results[8] == 0 && results[10] == 0 && results[11] == 64, "COND_EXEC over an INDIRECT_BUFFER did not skip the whole buffer"); + run(joinPackets({conditional(one, 4), indirectBuffer(target), writeWord(results[11], 65)})); + check(results[8] == 61 && results[9] == 0 && results[10] == 63 && results[11] == 65, "COND_EXEC over an INDIRECT_BUFFER or inside one evaluated wrong"); + + const auto rejected = [](std::vector words, const char* text) { + results[12] = 0; + expectFailure([&] { submitWords(words); }, text); + AgcDriverWaitIdle_nid_postfix(); + check(results[12] == 0, "a rejected conditional submission executed a prefix"); + }; + const auto sentinel = writeWord(results[12], 1); + rejected(joinPackets({sentinel, conditional(one, 3), writeWord(results[13], 1)}), "ends inside a packet"); + rejected(joinPackets({sentinel, conditional(one, 6), writeWord(results[13], 1)}), "exceeds its command buffer"); + rejected(joinPackets({sentinel, conditional(one, 6), {0xc004105cu, 7, 0, 1, 0, 0}}), "a flip inside a conditional execution range"); + rejected(joinPackets({sentinel, conditional(one, 2), makePacket(0x59, {0x80000000u})}), "REWIND inside a conditional execution range"); + rejected(joinPackets({sentinel, conditional(one, 4), indirectBuffer(target, true)}), "chained INDIRECT_BUFFER"); + rejected(joinPackets({sentinel, conditional(one, 2), {0x40000000u, 0}}), "whole packets"); + rejected(joinPackets({sentinel, makePacket(0x22, {low(&one), high(&one), 0, 0x4005}), writeWord(results[13], 1)}), "reserved count bits"); + static std::vector unaligned, overlong; + unaligned = joinPackets({conditional(one, 3), writeWord(results[13], 1)}); + overlong = joinPackets({conditional(one, 10), writeWord(results[13], 1)}); + rejected(joinPackets({sentinel, indirectBuffer(unaligned)}), "ends inside a packet"); + rejected(joinPackets({sentinel, indirectBuffer(overlong), writeWord(results[13], 1)}), "exceeds its command buffer"); + check(results[13] == 0, "a rejected conditional submission executed a guarded packet"); +} + void testAsyncMemoryFailure() { auto commands = makePacket(0x37, {0x100, 0x1000, 0, 1}); Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; @@ -572,11 +724,14 @@ int main(int argc, char** argv) { testCopies(); testMemoryCopyDecode(); testMemorySynchronization(); + testConditionalValidation(); + testConditionReadSynchronization(); testEventWrite(); testAcquireMem(); testDriverSubmission(); + testConditionalSubmission(); LibcRunShutdown_nid_postfix(); - std::puts("PM4 catalog, registers, state, memory and submission tests passed"); + std::puts("PM4 catalog, registers, state, memory, conditional execution and submission tests passed"); return 0; } catch (const std::exception& error) { std::fprintf(stderr, "%s\n", error.what()); diff --git a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp index 8ae3444be..795db774c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp @@ -2573,6 +2573,44 @@ void firstLayerViewTests(const Device& device, Recorder& recorder) { expectRed(program.Red(flatTexture.View(), flatTexture.Layout(), 0.0f), 0x20 / 255.0f, "a 2D texture over the surface does not read its first layer"); } +void atomicViewTests(const Device& device, Recorder& recorder) { + const auto& context = device.GetContext(); + TextureDetiler detiler(context); + auto withDetiler = context; + withDetiler.detiler = &detiler; + for (const std::uint32_t format : {20u, 21u, 22u, 56u}) { + GuestTextureResource resource{}; + resource.width = 64; + resource.height = 4; + resource.mipCount = 1; + resource.tileMode = TextureTileMode::kLinear; + resource.dimension = TextureDimension::k2D; + resource.format = format; + resource.dstSelX = 4; + resource.dstSelY = 5; + resource.dstSelZ = 6; + resource.dstSelW = 7; + const auto geometry = DescribeSurface(resource); + std::vector memory(static_cast(geometry.guestBytes) + 256); + resource.baseAddress = (reinterpret_cast(memory.data()) + 255) & ~std::uintptr_t{255}; + auto image = std::make_shared(withDetiler, detiler, resource, 0); + recorder.Keep(image); + if (format == 56u) { + bool refused = false; + try { + static_cast(image->AtomicView(0, false)); + } catch (const std::exception&) { + refused = true; + } + Require(refused, "an 8_8_8_8 storage image has an atomic view"); + continue; + } + const auto atomic = image->AtomicView(0, false); + Require(atomic != VK_NULL_HANDLE && atomic == image->AtomicView(0, false), "atomic storage views are not reused"); + Require((atomic == image->View(0)) == (format == 20u), "only a 32_UINT storage image serves atomics through its own view"); + } +} + void keysFillTests(const Device& device, Recorder& recorder) { const auto& base = device.GetContext(); if (base.hostImportAlignment == 0) { @@ -2724,6 +2762,7 @@ int main() { dataRefreshTests(device, recorder); minLodTests(device, recorder); firstLayerViewTests(device, recorder); + atomicViewTests(device, recorder); metadataPassTests(device, recorder); pendingKeyStoreTests(device, recorder); sampleDumpTests(device, recorder); diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp index 55e79fbc5..20dd3e202 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp @@ -31,7 +31,7 @@ void setEnvironment(const char* name, const std::string& value) { } bool sameBinding(const DescriptorBinding& left, const DescriptorBinding& right) { - return left.kind == right.kind && left.role == right.role && left.descriptorSet == right.descriptorSet && left.binding == right.binding && left.count == right.count && left.guestDescriptor == right.guestDescriptor && left.readOnly == right.readOnly && left.imageShape == right.imageShape && left.samplerDepthCompare == right.samplerDepthCompare && left.imageWritten == right.imageWritten && left.imageDepthCompare == right.imageDepthCompare && left.bufferAtomic == right.bufferAtomic && left.bufferWritten == right.bufferWritten; + return left.kind == right.kind && left.role == right.role && left.descriptorSet == right.descriptorSet && left.binding == right.binding && left.count == right.count && left.guestDescriptor == right.guestDescriptor && left.readOnly == right.readOnly && left.imageShape == right.imageShape && left.samplerDepthCompare == right.samplerDepthCompare && left.imageWritten == right.imageWritten && left.imageDepthCompare == right.imageDepthCompare && left.imageAtomic == right.imageAtomic && left.bufferAtomic == right.bufferAtomic && left.bufferWritten == right.bufferWritten; } bool sameBindings(const std::vector& left, const std::vector& right) { @@ -92,6 +92,7 @@ DescriptorBinding sampleBinding(std::uint32_t seed) { binding.samplerDepthCompare = {true, false, true}; binding.imageDepthCompare = {false, true, false}; binding.imageWritten = {false, true, true}; + binding.imageAtomic = {false, true, false}; binding.bufferAtomic = {true}; binding.bufferWritten = {false, false, true, true, false}; return binding; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16RowShareXmask.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16RowShareXmask.cpp new file mode 100644 index 000000000..0fd6a6861 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16RowShareXmask.cpp @@ -0,0 +1,289 @@ +#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 +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t MaxThreads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Checked = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Wave32Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0x7e3202ff, 0xdeaddead, 0xbefe03ff, 0x33cc0ff3, 0x7e1402fa, + 0xff015004, 0x7e1602fa, 0xff055504, 0x7e1802fa, 0xff095c04, 0x7e1a02fa, 0xff0d5804, 0x7e1c02fa, + 0x5a095f04, 0x7e1e02fa, 0xff016104, 0x7e2002fa, 0xff056604, 0x7e2202fa, 0xff096f04, 0x7e2402fa, + 0xa5016a04, 0x4a260afa, 0xff015304, 0x4a280afa, 0xff095c04, 0x4a2a0afa, 0x69055404, 0x4a2c0afa, + 0xff016404, 0x4c2e0afa, 0xff0d6d04, 0x20300efa, 0xff095406, 0x20320efa, 0xff056906, 0xbefe03c1, + 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, + 0xbf810000, +}; + +alignas(256) constexpr std::array Wave64Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0x7e3202ff, 0xdeaddead, 0xbefe03ff, 0x33cc0ff3, 0xbeff03ff, + 0xc3a5960f, 0x7e1402fa, 0xff015004, 0x7e1602fa, 0xff055504, 0x7e1802fa, 0xff095c04, 0x7e1a02fa, + 0xff0d5804, 0x7e1c02fa, 0x5a095f04, 0x7e1e02fa, 0xff016104, 0x7e2002fa, 0xff056604, 0x7e2202fa, + 0xff096f04, 0x7e2402fa, 0xa5016a04, 0x4a260afa, 0xff015304, 0x4a280afa, 0xff095c04, 0x4a2a0afa, + 0x69055404, 0x4a2c0afa, 0xff016404, 0x4c2e0afa, 0xff0d6d04, 0x20300efa, 0xff095406, 0x20320efa, + 0xff056906, 0xbefe04c1, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, + 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[64][4] = { + {0x6ecd1cd2u, 0x4bef5ab2u, 0xc1f00000u, 0xc0c00000u}, + {0x6a003be9u, 0xe12916b9u, 0x422a0000u, 0x41fc0000u}, + {0xe8ace817u, 0x43f615feu, 0x42600000u, 0xc21e0000u}, + {0x4e3aa244u, 0x2d47d909u, 0x42540000u, 0xc2220000u}, + {0x68060c75u, 0xd69d14d5u, 0x41940000u, 0xc1d40000u}, + {0x5af9fb5au, 0x54393673u, 0x42440000u, 0xc1080000u}, + {0xad52f58fu, 0x3c3ff249u, 0x3fc00000u, 0xc26c0000u}, + {0xcb826265u, 0xd06f4fc2u, 0x425c0000u, 0xc2420000u}, + {0xc99d74ceu, 0xcb3eacf5u, 0x41ac0000u, 0x42320000u}, + {0x9c7fb5dbu, 0xa5e8a4bau, 0xc2340000u, 0xc24a0000u}, + {0x1e7cc79eu, 0x3197b5a3u, 0x41c00000u, 0x42420000u}, + {0x099b18b9u, 0xc7d8a2a5u, 0xc26a0000u, 0xc1980000u}, + {0x1504efdfu, 0x7e04a9d5u, 0xc2520000u, 0x422a0000u}, + {0xf200add6u, 0xd20e47a3u, 0xc1680000u, 0xc1380000u}, + {0xe31908c0u, 0x60de8b85u, 0xc20c0000u, 0x421a0000u}, + {0xf1c81bb0u, 0xa39b139du, 0x40000000u, 0xc2300000u}, + {0xdd7dd010u, 0x93d28ae8u, 0xc1f80000u, 0xc2460000u}, + {0xea239e60u, 0x1136bab0u, 0xc0900000u, 0xc1840000u}, + {0x4a248cbdu, 0x2cde59ccu, 0xc1380000u, 0xc1c40000u}, + {0x06734af7u, 0xfc5d75e7u, 0x40b00000u, 0xc2200000u}, + {0x76646c94u, 0x33cb3f0cu, 0xc25e0000u, 0xc2080000u}, + {0xc3f02221u, 0xba35f124u, 0xc2600000u, 0x424e0000u}, + {0xb955e2e5u, 0xb0a01d78u, 0xc1a40000u, 0x41300000u}, + {0xad47bf7du, 0x7e37ee41u, 0xc2380000u, 0x41e80000u}, + {0x4467b3a9u, 0xc243faa9u, 0x42400000u, 0x42280000u}, + {0xee39a32fu, 0x5d27404bu, 0xc1e00000u, 0xc2020000u}, + {0x362b62e4u, 0x40e07905u, 0x425e0000u, 0xc2640000u}, + {0x3597987bu, 0x9f2dda53u, 0xc1280000u, 0xc0200000u}, + {0x8cdfd404u, 0xaa825860u, 0x42700000u, 0x41940000u}, + {0x2883e9fdu, 0x6fcb4509u, 0xc1bc0000u, 0xc2700000u}, + {0xa64c74f6u, 0x6c225e76u, 0x41c40000u, 0xc22a0000u}, + {0xc9c4946du, 0x27f8b552u, 0x41300000u, 0xbf000000u}, + {0xec1dd6b9u, 0x37eae67cu, 0x42300000u, 0xc1fc0000u}, + {0x080a718au, 0xdd3a87b9u, 0xc1480000u, 0x426c0000u}, + {0x4d7d2f79u, 0x242b5b96u, 0xc1b80000u, 0xc25e0000u}, + {0xda0d5bd5u, 0xbd1eab24u, 0x41f40000u, 0xc2060000u}, + {0x30aa4ea5u, 0x908d264eu, 0xc0800000u, 0xc0c00000u}, + {0x4d4b8ec9u, 0x59f9bbd9u, 0xc1c40000u, 0x40c00000u}, + {0x008d7334u, 0x81e7a48bu, 0x3f800000u, 0x423a0000u}, + {0xa985a1d6u, 0x6454c697u, 0xc2300000u, 0xc22a0000u}, + {0x6945a98eu, 0x56efdfffu, 0x425e0000u, 0x41200000u}, + {0x543a17e0u, 0x9680cbfau, 0xc2360000u, 0xc20e0000u}, + {0x0e1e5311u, 0x8dbd6564u, 0xc2480000u, 0xc24c0000u}, + {0xd6c814a2u, 0x7f2c1289u, 0xc0b00000u, 0x41e00000u}, + {0x248db44au, 0xc60fd986u, 0x42400000u, 0xc2160000u}, + {0xd6c5e906u, 0x10117cecu, 0xc1f80000u, 0x41a80000u}, + {0x324909c5u, 0xdcd7b8c2u, 0x42080000u, 0x42220000u}, + {0xf995627fu, 0xc6e555f6u, 0xc2260000u, 0xc1800000u}, + {0x735c003eu, 0xc54e7632u, 0x41900000u, 0xc25a0000u}, + {0xfc3effbbu, 0x46c20a66u, 0xc1ec0000u, 0xc20a0000u}, + {0x0986feb5u, 0x040b21dfu, 0xc2180000u, 0x3f800000u}, + {0xacca236du, 0x7cfef2f2u, 0x40f00000u, 0x41cc0000u}, + {0x00bd61d8u, 0x5654123eu, 0x41400000u, 0xc1100000u}, + {0xe319a18bu, 0x5d8bc32du, 0x41100000u, 0xc2480000u}, + {0x6583147du, 0x72ce7a51u, 0x41780000u, 0x426e0000u}, + {0x49981da9u, 0x9b4ba109u, 0xc1a40000u, 0xc1e00000u}, + {0xe341cf98u, 0x3ec5433cu, 0xc0000000u, 0xc23c0000u}, + {0xc861e665u, 0xda64e2ddu, 0xc1f00000u, 0x41900000u}, + {0x8c31f3d4u, 0x807eed38u, 0xc1300000u, 0xc24e0000u}, + {0xf5411da7u, 0x6886f7e2u, 0xc1580000u, 0x420e0000u}, + {0x56aecd4cu, 0x7cfe2052u, 0xc23a0000u, 0x423a0000u}, + {0x29fc3f6du, 0x843d8b71u, 0x41d40000u, 0xbfc00000u}, + {0xf14c9634u, 0x6de904e9u, 0xc1c40000u, 0xc1180000u}, + {0x0524b659u, 0xb07d24c5u, 0xc22e0000u, 0xc23a0000u} +}; + +constexpr std::uint32_t Expected32[32][16] = { + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x6a003be9u, 0xad52f58fu, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x4bef5ab2u, 0xdeaddeadu, 0xb3f56727u, 0xa6115324u, 0x41940000u, 0xc0c00000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x6ecd1cd2u, 0xcb826265u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0xe12916b9u, 0xdeaddeadu, 0x3c231213u, 0x33dbd926u, 0x41fc0000u, 0x41fc0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0x5af9fb5au, 0xe8ace817u, 0x099b18b9u, 0xdeaddeadu, 0xdeaddeadu, 0xd69d14d5u, 0xdeaddeadu, 0x456a31a7u, 0xc5e2a106u, 0x41940000u, 0xc1680000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0x68060c75u, 0x4e3aa244u, 0x1e7cc79eu, 0xdeaddeadu, 0xdeaddeadu, 0x54393673u, 0xdeaddeadu, 0xbe39725cu, 0x75643e5bu, 0x41940000u, 0xc1080000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0xcb826265u, 0x6ecd1cd2u, 0x9c7fb5dbu, 0xdeaddeadu, 0xdeaddeadu, 0x3c3ff249u, 0xdeaddeadu, 0xdeaddeadu, 0xcd5b2670u, 0x41940000u, 0x40000000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0xad52f58fu, 0x6a003be9u, 0xc99d74ceu, 0xdeaddeadu, 0xdeaddeadu, 0xd06f4fc2u, 0xdeaddeadu, 0xdeaddeadu, 0x4e0d77dcu, 0x41940000u, 0xc20c0000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x9c7fb5dbu, 0xe31908c0u, 0xcb826265u, 0xdeaddeadu, 0xdeaddeadu, 0xcb3eacf5u, 0xdeaddeadu, 0xdeaddeadu, 0x8fbb4e65u, 0x42320000u, 0x42320000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0xc99d74ceu, 0xf1c81bb0u, 0xad52f58fu, 0xdeaddeadu, 0xdeaddeadu, 0xa5e8a4bau, 0xdeaddeadu, 0xdeaddeadu, 0xc21d67bbu, 0x41940000u, 0xc1f00000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x099b18b9u, 0x1504efdfu, 0x5af9fb5au, 0xdeaddeadu, 0xdeaddeadu, 0x3197b5a3u, 0xdeaddeadu, 0xdeaddeadu, 0x99eaacc2u, 0x42420000u, 0x42540000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x1e7cc79eu, 0xf200add6u, 0x68060c75u, 0xdeaddeadu, 0xdeaddeadu, 0xc7d8a2a5u, 0xdeaddeadu, 0xdeaddeadu, 0xe57a52eau, 0x41940000u, 0x42600000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x06734af7u, 0x76646c94u, 0x2883e9fdu, 0x4467b3a9u, 0x3351a4c3u, 0xb9be2dd0u, 0xa342c660u, 0xe6343cb1u, 0x9ce63aa1u, 0x00000000u, 0xc1280000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x4a248cbdu, 0xc3f02221u, 0x8cdfd404u, 0xee39a32fu, 0x02d0c0deu, 0x893d49ebu, 0x72c1e27bu, 0xa9a53564u, 0xa9eeff0fu, 0x00000000u, 0x425e0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xad47bf7du, 0xdd7dd010u, 0xee39a32fu, 0xdeaddeadu, 0xb713686fu, 0x3d7ff17cu, 0xdeaddeadu, 0xfac4aa35u, 0x84f77b03u, 0x41300000u, 0x41300000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xb955e2e5u, 0xea239e60u, 0x4467b3a9u, 0xdeaddeadu, 0x84ab3938u, 0x0b17c245u, 0xdeaddeadu, 0x84ab3938u, 0xb7f374a3u, 0x41e80000u, 0x41e80000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xee39a32fu, 0xa64c74f6u, 0xad47bf7du, 0x4a248cbdu, 0xc8b745a0u, 0x4f23ceadu, 0xdeaddeadu, 0x4f23ceadu, 0x01ac2778u, 0x42280000u, 0x42280000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x4467b3a9u, 0xc9c4946du, 0xb955e2e5u, 0x06734af7u, 0x639a8b42u, 0xea07144fu, 0xdeaddeadu, 0x85ab2a48u, 0x193d2c49u, 0x00000000u, 0xc1f80000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x2883e9fdu, 0x362b62e4u, 0x06734af7u, 0xdeaddeadu, 0xb0f5a357u, 0x37622c64u, 0x20e6c4f4u, 0xeeea0c09u, 0x3fa14600u, 0x41940000u, 0x41940000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x8cdfd404u, 0x3597987bu, 0x4a248cbdu, 0xdeaddeadu, 0x763e9000u, 0xfcab190du, 0xe62fb19du, 0x5e04e838u, 0x6db28b07u, 0x00000000u, 0xc25e0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu} +}; +constexpr std::uint32_t Expected64[64][16] = { + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x6a003be9u, 0xad52f58fu, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x4bef5ab2u, 0xdeaddeadu, 0xb3f56727u, 0xa6115324u, 0x41940000u, 0xc0c00000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x6ecd1cd2u, 0xcb826265u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0xe12916b9u, 0xdeaddeadu, 0x3c231213u, 0x33dbd926u, 0x41fc0000u, 0x41fc0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0x5af9fb5au, 0xe8ace817u, 0x099b18b9u, 0xdeaddeadu, 0xdeaddeadu, 0xd69d14d5u, 0xdeaddeadu, 0x456a31a7u, 0xc5e2a106u, 0x41940000u, 0xc1680000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0x68060c75u, 0x4e3aa244u, 0x1e7cc79eu, 0xdeaddeadu, 0xdeaddeadu, 0x54393673u, 0xdeaddeadu, 0xbe39725cu, 0x75643e5bu, 0x41940000u, 0xc1080000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0xcb826265u, 0x6ecd1cd2u, 0x9c7fb5dbu, 0xdeaddeadu, 0xdeaddeadu, 0x3c3ff249u, 0xdeaddeadu, 0xdeaddeadu, 0xcd5b2670u, 0x41940000u, 0x40000000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0xad52f58fu, 0x6a003be9u, 0xc99d74ceu, 0xdeaddeadu, 0xdeaddeadu, 0xd06f4fc2u, 0xdeaddeadu, 0xdeaddeadu, 0x4e0d77dcu, 0x41940000u, 0xc20c0000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x9c7fb5dbu, 0xe31908c0u, 0xcb826265u, 0xdeaddeadu, 0xdeaddeadu, 0xcb3eacf5u, 0xdeaddeadu, 0xdeaddeadu, 0x8fbb4e65u, 0x42320000u, 0x42320000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0xc99d74ceu, 0xf1c81bb0u, 0xad52f58fu, 0xdeaddeadu, 0xdeaddeadu, 0xa5e8a4bau, 0xdeaddeadu, 0xdeaddeadu, 0xc21d67bbu, 0x41940000u, 0xc1f00000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x099b18b9u, 0x1504efdfu, 0x5af9fb5au, 0xdeaddeadu, 0xdeaddeadu, 0x3197b5a3u, 0xdeaddeadu, 0xdeaddeadu, 0x99eaacc2u, 0x42420000u, 0x42540000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x1e7cc79eu, 0xf200add6u, 0x68060c75u, 0xdeaddeadu, 0xdeaddeadu, 0xc7d8a2a5u, 0xdeaddeadu, 0xdeaddeadu, 0xe57a52eau, 0x41940000u, 0x42600000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x06734af7u, 0x76646c94u, 0x2883e9fdu, 0x4467b3a9u, 0x3351a4c3u, 0xb9be2dd0u, 0xa342c660u, 0xe6343cb1u, 0x9ce63aa1u, 0x00000000u, 0xc1280000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x4a248cbdu, 0xc3f02221u, 0x8cdfd404u, 0xee39a32fu, 0x02d0c0deu, 0x893d49ebu, 0x72c1e27bu, 0xa9a53564u, 0xa9eeff0fu, 0x00000000u, 0x425e0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xad47bf7du, 0xdd7dd010u, 0xee39a32fu, 0xdeaddeadu, 0xb713686fu, 0x3d7ff17cu, 0xdeaddeadu, 0xfac4aa35u, 0x84f77b03u, 0x41300000u, 0x41300000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xb955e2e5u, 0xea239e60u, 0x4467b3a9u, 0xdeaddeadu, 0x84ab3938u, 0x0b17c245u, 0xdeaddeadu, 0x84ab3938u, 0xb7f374a3u, 0x41e80000u, 0x41e80000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xee39a32fu, 0xa64c74f6u, 0xad47bf7du, 0x4a248cbdu, 0xc8b745a0u, 0x4f23ceadu, 0xdeaddeadu, 0x4f23ceadu, 0x01ac2778u, 0x42280000u, 0x42280000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x4467b3a9u, 0xc9c4946du, 0xb955e2e5u, 0x06734af7u, 0x639a8b42u, 0xea07144fu, 0xdeaddeadu, 0x85ab2a48u, 0x193d2c49u, 0x00000000u, 0xc1f80000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x2883e9fdu, 0x362b62e4u, 0x06734af7u, 0xdeaddeadu, 0xb0f5a357u, 0x37622c64u, 0x20e6c4f4u, 0xeeea0c09u, 0x3fa14600u, 0x41940000u, 0x41940000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x8cdfd404u, 0x3597987bu, 0x4a248cbdu, 0xdeaddeadu, 0x763e9000u, 0xfcab190du, 0xe62fb19du, 0x5e04e838u, 0x6db28b07u, 0x00000000u, 0xc25e0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0x080a718au, 0x008d7334u, 0xf995627fu, 0xdeaddeadu, 0x11f84251u, 0x5c789ac6u, 0x68953521u, 0xdeaddeadu, 0x9edb028au, 0x00000000u, 0xc1fc0000u}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0xec1dd6b9u, 0xa985a1d6u, 0x00000000u, 0xdeaddeadu, 0xb747e38eu, 0x01c83c03u, 0x0de4d65eu, 0xdeaddeadu, 0x47532c91u, 0x426c0000u, 0x426c0000u}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0xda0d5bd5u, 0x30aa4ea5u, 0x00000000u, 0xdeaddeadu, 0xfe38b76bu, 0x48b90fe0u, 0x54d5aa3bu, 0xdeaddeadu, 0xd56a06e9u, 0x00000000u, 0xc0b00000u}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0x4d7d2f79u, 0x4d4b8ec9u, 0x248db44au, 0xdeaddeadu, 0x972c06f9u, 0xe1ac5f6eu, 0xedc8f9c9u, 0xdeaddeadu, 0x752a5ea1u, 0x00000000u, 0xc2060000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0xdeaddeadu, 0xf995627fu, 0x00000000u, 0xdeaddeadu, 0x708e27cfu, 0xbb0e8044u, 0xdeaddeadu, 0xdeaddeadu, 0x9a2982abu, 0x00000000u, 0x42300000u}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0xdeaddeadu, 0x248db44au, 0x00000000u, 0xdeaddeadu, 0x67cac139u, 0xb24b19aeu, 0xdeaddeadu, 0xdeaddeadu, 0x1bc83c72u, 0x00000000u, 0x41f40000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xf995627fu, 0xdeaddeadu, 0x0e1e5311u, 0xda0d5bd5u, 0xdeaddeadu, 0xa01d355bu, 0xea9d8dd0u, 0xf6ba282bu, 0xdeaddeadu, 0x41fa9804u, 0x00000000u, 0xc1c40000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xf995627fu, 0xdeaddeadu, 0x543a17e0u, 0xec1dd6b9u, 0xdeaddeadu, 0xa0f2b1cbu, 0xeb730a40u, 0xf78fa49bu, 0xdeaddeadu, 0x8697d983u, 0x00000000u, 0x3f800000u}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xdeaddeadu, 0x6583147du, 0x0524b659u, 0xdeaddeadu, 0xdeaddeadu, 0xc54e7632u, 0xdeaddeadu, 0xdeaddeadu, 0x64adc93bu, 0x00000000u, 0xc1f00000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xdeaddeadu, 0x00bd61d8u, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0x040b21dfu, 0xdeaddeadu, 0xdeaddeadu, 0x0119947au, 0x3f800000u, 0x3f800000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xdeaddeadu, 0xacca236du, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x5d8bc32du, 0xdeaddeadu, 0xdeaddeadu, 0x85b60c6bu, 0x00000000u, 0xc23a0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xdeaddeadu, 0xfc3effbbu, 0xe341cf98u, 0xdeaddeadu, 0xdeaddeadu, 0x9b4ba109u, 0xdeaddeadu, 0xdeaddeadu, 0xf0e652cbu, 0x00000000u, 0xc1c40000u}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xc861e665u, 0xf14c9634u, 0x49981da9u, 0x0986feb5u, 0xdeaddeadu, 0x3ec5433cu, 0xdeaddeadu, 0xdeaddeadu, 0xa4545e4fu, 0x00000000u, 0xc1ec0000u}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xe341cf98u, 0x0524b659u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0xda64e2ddu, 0xdeaddeadu, 0xdeaddeadu, 0x26587efbu, 0x41900000u, 0x41900000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0x0524b659u, 0xe341cf98u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x6de904e9u, 0xdeaddeadu, 0xdeaddeadu, 0x3ee11e84u, 0x00000000u, 0xc1180000u}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xf14c9634u, 0xc861e665u, 0x735c003eu, 0xdeaddeadu, 0xdeaddeadu, 0xb07d24c5u, 0xdeaddeadu, 0xdeaddeadu, 0x5909d9f0u, 0x00000000u, 0x41780000u} +}; + +constexpr const char* Names[Checked] = {"mov_row_share0", "mov_row_share5_fi", "mov_row_share12_bc", "mov_row_share8_bc_fi", "mov_row_share15_masks", "mov_row_xmask1", "mov_row_xmask6_fi", "mov_row_xmask15_bc", "mov_row_xmask10_masks", "add_row_share3", "add_row_share12_bc", "add_row_share4_fi_masks", "add_row_xmask4", "sub_row_xmask13_bc_fi", "max_f32_row_share4_bc", "max_f32_row_xmask9_fi"}; + +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 Run(AgcDriver::VulkanDevice& device, std::span code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + for (std::uint32_t tid = 0; tid < MaxThreads; ++tid) std::copy(std::begin(Rows[tid]), std::end(Rows[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::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{waveSize, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + 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(); +} + +template +void Check(const std::uint32_t (&expected)[Lanes][Checked]) { + for (std::uint32_t tid = 0; tid < Lanes; ++tid) { + for (std::uint32_t index = 0; index < Checked; ++index) { + const auto actual = Output[tid * Results + index]; + Require(actual == expected[tid][index], "dpp16 row share xmask wave" + std::to_string(Lanes) + ": lane " + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < 32) { + std::printf("skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); + return VulkanTestSkipped; + } + Run(*device, Wave32Code, 32, device->ComputeTarget(32)); + Check(Expected32); + Run(*device, Wave64Code, 64, device->Target()); + Check(Expected64); + Run(*device, Wave64Code, 64, device->ComputeTarget(32)); + Check(Expected64); + std::puts("dpp16 row share xmask tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatCacheBits.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatCacheBits.cpp index 9b1650f3f..717f9f028 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/FlatCacheBits.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatCacheBits.cpp @@ -23,62 +23,58 @@ namespace { using AgcDriver::Graphics::Require; using ShaderRecompiler::ShaderStage; -constexpr std::uint32_t Threads = 64; -constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Results = 4; constexpr std::uint32_t Sentinel = 0x05e471e1; constexpr std::size_t BlockBytes = 65536; constexpr std::uint8_t Fill = 0xcd; alignas(256) std::array Output{}; -alignas(256) constexpr std::array FlatCacheBitsCode{ - 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0xd70f6a1e, 0x02020208, 0x7e3e0209, - 0x503e3e80, 0xdc318000, 0x0a080001, 0xdc328004, 0x0b080001, 0xdc309008, 0x0c080001, 0xdc33900c, - 0x0d080001, 0xdc359004, 0x0e080001, 0xdc228001, 0x10080001, 0xdc259007, 0x11080001, 0xbf8c3f70, - 0xdc739000, 0x00080501, 0xdc628006, 0x00080601, 0xbf8c3f70, 0xdc359000, 0x12080001, 0xdccb9008, - 0x14080701, 0xdcce900c, 0x00080401, 0xbf8c3f70, 0xdc358008, 0x15080001, 0xdc311000, 0x177d001e, - 0xdc730000, 0x007d071e, 0xbf8c0070, 0xdcef1000, 0x187d061e, 0xbf8c0070, 0xdc320000, 0x197d001e, - 0xbf8c0070, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, 0x80011202, 0xe0781030, - 0x80011602, 0xbf810000 +alignas(256) constexpr std::array FlatCacheBitsCode{ + 0x34020084, 0x34040084, 0xd70f6a1e, 0x02020208, 0x7e3e0209, 0x503e3e80, 0xd70f6a1e, 0x02023c8c, + 0x503e3e80, 0xdc379000, 0x0a080001, 0xdc331000, 0x0d7d001e, 0xbf8c0070, 0xdc739004, 0x00080d01, + 0xdccb9008, 0x0c080a01, 0xdc731000, 0x007d0b1e, 0xbf8c0070, 0xe0781000, 0x80010a02, 0xbf810000 }; struct Row { std::array memory; std::array results; + std::array final; }; constexpr std::array Rows{{ - {{0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x0000007f, 0x00000080, 0x7fffffff, 0x0000007f, 0x00000080, 0x00000000, 0x00000000, 0x0000007f, 0x0080007f, 0x00000080, 0x8000007f, 0x7ffffffe, 0x0000007f, 0x7fffffff, 0x7fffff7f}}, - {{0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01, 0x000000ff, 0x80000000, 0x00000000, 0x00000000, 0x000000ff, 0x000000ff, 0x80000000, 0x00ff7f01, 0x80ff7e82, 0x000000ff, 0x80ff7f01, 0x00ff7f01}}, - {{0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff, 0x80000000, 0xdeadbeef, 0x00000000, 0xffffff80, 0x80000000, 0x80ef0000, 0xdeadbeef, 0xdeadbfee, 0x0000007f, 0x80000000, 0x000000ff, 0xdeadbe10}}, - {{0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef, 0x12345678, 0x00000001, 0x00000000, 0x00000012, 0x12345678, 0x12015678, 0x00000001, 0xdeadbef0, 0xdeadbdf0, 0x12345678, 0xdeadbeef, 0xdeadbeee}}, - {{0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080, 0x80ff7f01, 0x000000ff, 0x000000ff, 0xffffff80, 0x80ff7f01, 0x80ff7f01, 0x000000ff, 0x0000017f, 0x80000081, 0x80ff7f01, 0x00000080, 0x0000007f}}, - {{0x80000000, 0x00000001, 0xffffffff, 0x12345678}, {0x80000000, 0x00000001, 0xffffffff, 0x12345678, 0x00000001, 0xffffffff, 0x00000000, 0x00000000, 0x00000001, 0x00ff0001, 0xffffffff, 0x12345677, 0x92345678, 0x00000001, 0x12345678, 0xedcba987}}, - {{0xffffffff, 0x00000080, 0x80ff7f01, 0x0000007f}, {0xffffffff, 0x00000080, 0x80ff7f01, 0x0000007f, 0x00000080, 0x80ff7f01, 0x000000ff, 0x00000000, 0x00000080, 0x00010080, 0x80ff7f01, 0x80ff7f80, 0x00000080, 0x00000080, 0x0000007f, 0x80ff7f7e}}, - {{0x12345678, 0x7fffffff, 0x0000007f, 0xffffffff}, {0x12345678, 0x7fffffff, 0x0000007f, 0xffffffff, 0x7fffffff, 0x0000007f, 0x00000056, 0x0000007f, 0x7fffffff, 0x7f7fffff, 0x0000007f, 0x0000007e, 0xedcba987, 0x7fffffff, 0xffffffff, 0xffffff80}}, - {{0xdeadbeef, 0xffffffff, 0x7fffffff, 0x00000001}, {0xdeadbeef, 0xffffffff, 0x7fffffff, 0x00000001, 0xffffffff, 0x7fffffff, 0x000000be, 0xffffffff, 0xffffffff, 0xffffffff, 0x7fffffff, 0x80000000, 0x21524112, 0xffffffff, 0x00000001, 0x7ffffffe}}, - {{0x80ff7f01, 0xdeadbeef, 0x12345678, 0x80000000}, {0x80ff7f01, 0xdeadbeef, 0x12345678, 0x80000000, 0xdeadbeef, 0x12345678, 0x0000007f, 0xffffffde, 0xdeadbeef, 0xde78beef, 0x12345678, 0x92345678, 0xff0080ff, 0xdeadbeef, 0x80000000, 0x92345678}}, - {{0x00000000, 0x00000000, 0x00000000, 0x00000000}, {0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}}, - {{0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x0000007f, 0x00000080, 0x7fffffff, 0x0000007f, 0x00000080, 0x00000000, 0x00000000, 0x0000007f, 0x0080007f, 0x00000080, 0x8000007f, 0x7ffffffe, 0x0000007f, 0x7fffffff, 0x7fffff7f}}, - {{0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01, 0x000000ff, 0x80000000, 0x00000000, 0x00000000, 0x000000ff, 0x000000ff, 0x80000000, 0x00ff7f01, 0x80ff7e82, 0x000000ff, 0x80ff7f01, 0x00ff7f01}}, - {{0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff, 0x80000000, 0xdeadbeef, 0x00000000, 0xffffff80, 0x80000000, 0x80ef0000, 0xdeadbeef, 0xdeadbfee, 0x0000007f, 0x80000000, 0x000000ff, 0xdeadbe10}}, - {{0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef, 0x12345678, 0x00000001, 0x00000000, 0x00000012, 0x12345678, 0x12015678, 0x00000001, 0xdeadbef0, 0xdeadbdf0, 0x12345678, 0xdeadbeef, 0xdeadbeee}}, - {{0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080, 0x80ff7f01, 0x000000ff, 0x000000ff, 0xffffff80, 0x80ff7f01, 0x80ff7f01, 0x000000ff, 0x0000017f, 0x80000081, 0x80ff7f01, 0x00000080, 0x0000007f}}, - {{0x2265b1f5, 0x91b7584a, 0xd8f16adf, 0xcd613e30}, {0x2265b1f5, 0x91b7584a, 0xd8f16adf, 0xcd613e30, 0x91b7584a, 0xd8f16adf, 0x000000b1, 0xffffff91, 0x91b7584a, 0x91df584a, 0xd8f16adf, 0xa652a90f, 0xaafb8c3b, 0x91b7584a, 0xcd613e30, 0x159054ef}}, - {{0xc386bbc4, 0x1027c4d1, 0x414c343c, 0x1e2feb89}, {0xc386bbc4, 0x1027c4d1, 0x414c343c, 0x1e2feb89, 0x1027c4d1, 0x414c343c, 0x000000bb, 0x00000010, 0x1027c4d1, 0x103cc4d1, 0x414c343c, 0x5f7c1fc5, 0x5aa92fc5, 0x1027c4d1, 0x1e2feb89, 0x5f63dfb5}}, - {{0x7ed4d57b, 0xc2ce6f44, 0x7311d8a3, 0x78e51061}, {0x7ed4d57b, 0xc2ce6f44, 0x7311d8a3, 0x78e51061, 0xc2ce6f44, 0x7311d8a3, 0x000000d5, 0xffffffc2, 0xc2ce6f44, 0xc2a36f44, 0x7311d8a3, 0xebf6e904, 0xfa103ae6, 0xc2ce6f44, 0x78e51061, 0x0bf4c8c2}}, - {{0xa6cecc1b, 0x612e7696, 0xc9e9c616, 0x35bf992d}, {0xa6cecc1b, 0x612e7696, 0xc9e9c616, 0x35bf992d, 0x612e7696, 0xc9e9c616, 0x000000cc, 0x00000061, 0x612e7696, 0x61167696, 0xc9e9c616, 0xffa95f43, 0x8ef0cd12, 0x612e7696, 0x35bf992d, 0xfc565f3b}}, - {{0x18072e8c, 0x7ce42c82, 0x0741c7a8, 0xe4b06ce6}, {0x18072e8c, 0x7ce42c82, 0x0741c7a8, 0xe4b06ce6, 0x7ce42c82, 0x0741c7a8, 0x0000002e, 0x0000007c, 0x7ce42c82, 0x7ca82c82, 0x0741c7a8, 0xebf2348e, 0xcca93e5a, 0x7ce42c82, 0xe4b06ce6, 0xe3f1ab4e}}, - {{0xd5f4b3b2, 0x63ca828d, 0x6ec9d286, 0x9b810e76}, {0xd5f4b3b2, 0x63ca828d, 0x6ec9d286, 0x9b810e76, 0x63ca828d, 0x6ec9d286, 0x000000b3, 0x00000063, 0x63ca828d, 0x6386828d, 0x6ec9d286, 0x0a4ae0fc, 0xc58c5ac4, 0x63ca828d, 0x9b810e76, 0xf548dcf0}}, - {{0xc324c985, 0xc4647159, 0x008a05a6, 0xb2221a58}, {0xc324c985, 0xc4647159, 0x008a05a6, 0xb2221a58, 0xc4647159, 0x008a05a6, 0x000000c9, 0xffffffc4, 0xc4647159, 0xc4a67159, 0x008a05a6, 0xb2ac1ffe, 0xeefd50d3, 0xc4647159, 0xb2221a58, 0xb2a81ffe}}, - {{0x7204e52d, 0x442e3d43, 0xb8b6d8fe, 0xcd447e35}, {0x7204e52d, 0x442e3d43, 0xb8b6d8fe, 0xcd447e35, 0x442e3d43, 0xb8b6d8fe, 0x000000e5, 0x00000044, 0x442e3d43, 0x44fe3d43, 0xb8b6d8fe, 0x85fb5733, 0x5b3f9908, 0x442e3d43, 0xcd447e35, 0x75f2a6cb}}, - {{0x3a902931, 0x9755d4c1, 0xf1fd42a2, 0x1a2b8f1f}, {0x3a902931, 0x9755d4c1, 0xf1fd42a2, 0x1a2b8f1f, 0x9755d4c1, 0xf1fd42a2, 0x00000029, 0xffffff97, 0x9755d4c1, 0x97a2d4c1, 0xf1fd42a2, 0x0c28d1c1, 0xdf9b65ee, 0x9755d4c1, 0x1a2b8f1f, 0xebd6cdbd}}, - {{0xe6c3f339, 0x51431193, 0x07d4bedc, 0x05b6e6e3}, {0xe6c3f339, 0x51431193, 0x07d4bedc, 0x05b6e6e3, 0x51431193, 0x07d4bedc, 0x000000f3, 0x00000051, 0x51431193, 0x51dc1193, 0x07d4bedc, 0x0d8ba5bf, 0x1ef2f3aa, 0x51431193, 0x05b6e6e3, 0x0262583f}}, - {{0x06839eb9, 0xa648a7dd, 0x8a9a021e, 0x025b413f}, {0x06839eb9, 0xa648a7dd, 0x8a9a021e, 0x025b413f, 0xa648a7dd, 0x8a9a021e, 0x0000009e, 0xffffffa6, 0xa648a7dd, 0xa61ea7dd, 0x8a9a021e, 0x8cf5435d, 0xfbd7a286, 0xa648a7dd, 0x025b413f, 0x88c14321}}, - {{0xf06c144a, 0xe1988ad9, 0x619699cf, 0xafbd67f9}, {0xf06c144a, 0xe1988ad9, 0x619699cf, 0xafbd67f9, 0xe1988ad9, 0x619699cf, 0x00000014, 0xffffffe1, 0xe1988ad9, 0xe1cf8ad9, 0x619699cf, 0x115401c8, 0xbf5153af, 0xe1988ad9, 0xafbd67f9, 0xce2bfe36}}, - {{0x37730edf, 0xf8130c42, 0x6c0fd4f5, 0xb9d179e0}, {0x37730edf, 0xf8130c42, 0x6c0fd4f5, 0xb9d179e0, 0xf8130c42, 0x6c0fd4f5, 0x0000000e, 0xfffffff8, 0xf8130c42, 0xf8f50c42, 0x6c0fd4f5, 0x25e14ed5, 0x825e6b01, 0xf8130c42, 0xb9d179e0, 0xd5dead15}}, - {{0x076f3787, 0x8712b8bc, 0x38c0c8fd, 0xc381e88f}, {0x076f3787, 0x8712b8bc, 0x38c0c8fd, 0xc381e88f, 0x8712b8bc, 0x38c0c8fd, 0x00000037, 0xffffff87, 0x8712b8bc, 0x87fdb8bc, 0x38c0c8fd, 0xfc42b18c, 0xbc12b108, 0x8712b8bc, 0xc381e88f, 0xfb412072}}, - {{0x701966a0, 0xf06d3fef, 0x7eed8d14, 0x8d88348a}, {0x701966a0, 0xf06d3fef, 0x7eed8d14, 0x8d88348a, 0xf06d3fef, 0x7eed8d14, 0x00000066, 0xfffffff0, 0xf06d3fef, 0xf0143fef, 0x7eed8d14, 0x0c75c19e, 0x1d6ecdea, 0xf06d3fef, 0x8d88348a, 0xf365b99e}}, - {{0x3bab6c39, 0x587fd280, 0x3b1a11df, 0xad45f23d}, {0x3bab6c39, 0x587fd280, 0x3b1a11df, 0xad45f23d, 0x587fd280, 0x3b1a11df, 0x0000006c, 0x00000058, 0x587fd280, 0x58dfd280, 0x3b1a11df, 0xe860041c, 0x719a8604, 0x587fd280, 0xad45f23d, 0x965fe3e2}} + {{0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x7fffffff, 0x00000081, 0x0000007f}}, + {{0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x80ff7f01, 0x8000007f, 0x000000ff}}, + {{0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x000000ff, 0xdeadbf6f, 0x80000000}}, + {{0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0xdeadbeef, 0x00000100, 0x12345678}}, + {{0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x00000080, 0x800000fe, 0x80ff7f01}}, + {{0x80000000, 0x00000001, 0xffffffff, 0x12345678}, {0x80000000, 0x00000001, 0xffffffff, 0x12345678}, {0x80000000, 0x12345678, 0x7fffffff, 0x00000001}}, + {{0xffffffff, 0x00000080, 0x80ff7f01, 0x0000007f}, {0xffffffff, 0x00000080, 0x80ff7f01, 0x0000007f}, {0xffffffff, 0x0000007f, 0x80ff7f00, 0x00000080}}, + {{0x12345678, 0x7fffffff, 0x0000007f, 0xffffffff}, {0x12345678, 0x7fffffff, 0x0000007f, 0xffffffff}, {0x12345678, 0xffffffff, 0x123456f7, 0x7fffffff}}, + {{0xdeadbeef, 0xffffffff, 0x7fffffff, 0x00000001}, {0xdeadbeef, 0xffffffff, 0x7fffffff, 0x00000001}, {0xdeadbeef, 0x00000001, 0x5eadbeee, 0xffffffff}}, + {{0x80ff7f01, 0xdeadbeef, 0x12345678, 0x80000000}, {0x80ff7f01, 0xdeadbeef, 0x12345678, 0x80000000}, {0x80ff7f01, 0x80000000, 0x9333d579, 0xdeadbeef}}, + {{0x00000000, 0x00000000, 0x00000000, 0x00000000}, {0x00000000, 0x00000000, 0x00000000, 0x00000000}, {0x00000000, 0x00000000, 0x00000000, 0x00000000}}, + {{0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x7fffffff, 0x00000081, 0x0000007f}}, + {{0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x80ff7f01, 0x8000007f, 0x000000ff}}, + {{0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x000000ff, 0xdeadbf6f, 0x80000000}}, + {{0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0xdeadbeef, 0x00000100, 0x12345678}}, + {{0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x00000080, 0x800000fe, 0x80ff7f01}}, + {{0x2265b1f5, 0x91b7584a, 0xd8f16adf, 0xcd613e30}, {0x2265b1f5, 0x91b7584a, 0xd8f16adf, 0xcd613e30}, {0x2265b1f5, 0xcd613e30, 0xfb571cd4, 0x91b7584a}}, + {{0xc386bbc4, 0x1027c4d1, 0x414c343c, 0x1e2feb89}, {0xc386bbc4, 0x1027c4d1, 0x414c343c, 0x1e2feb89}, {0xc386bbc4, 0x1e2feb89, 0x04d2f000, 0x1027c4d1}}, + {{0x7ed4d57b, 0xc2ce6f44, 0x7311d8a3, 0x78e51061}, {0x7ed4d57b, 0xc2ce6f44, 0x7311d8a3, 0x78e51061}, {0x7ed4d57b, 0x78e51061, 0xf1e6ae1e, 0xc2ce6f44}}, + {{0xa6cecc1b, 0x612e7696, 0xc9e9c616, 0x35bf992d}, {0xa6cecc1b, 0x612e7696, 0xc9e9c616, 0x35bf992d}, {0xa6cecc1b, 0x35bf992d, 0x70b89231, 0x612e7696}}, + {{0x18072e8c, 0x7ce42c82, 0x0741c7a8, 0xe4b06ce6}, {0x18072e8c, 0x7ce42c82, 0x0741c7a8, 0xe4b06ce6}, {0x18072e8c, 0xe4b06ce6, 0x1f48f634, 0x7ce42c82}}, + {{0xd5f4b3b2, 0x63ca828d, 0x6ec9d286, 0x9b810e76}, {0xd5f4b3b2, 0x63ca828d, 0x6ec9d286, 0x9b810e76}, {0xd5f4b3b2, 0x9b810e76, 0x44be8638, 0x63ca828d}}, + {{0xc324c985, 0xc4647159, 0x008a05a6, 0xb2221a58}, {0xc324c985, 0xc4647159, 0x008a05a6, 0xb2221a58}, {0xc324c985, 0xb2221a58, 0xc3aecf2b, 0xc4647159}}, + {{0x7204e52d, 0x442e3d43, 0xb8b6d8fe, 0xcd447e35}, {0x7204e52d, 0x442e3d43, 0xb8b6d8fe, 0xcd447e35}, {0x7204e52d, 0xcd447e35, 0x2abbbe2b, 0x442e3d43}}, + {{0x3a902931, 0x9755d4c1, 0xf1fd42a2, 0x1a2b8f1f}, {0x3a902931, 0x9755d4c1, 0xf1fd42a2, 0x1a2b8f1f}, {0x3a902931, 0x1a2b8f1f, 0x2c8d6bd3, 0x9755d4c1}}, + {{0xe6c3f339, 0x51431193, 0x07d4bedc, 0x05b6e6e3}, {0xe6c3f339, 0x51431193, 0x07d4bedc, 0x05b6e6e3}, {0xe6c3f339, 0x05b6e6e3, 0xee98b215, 0x51431193}}, + {{0x06839eb9, 0xa648a7dd, 0x8a9a021e, 0x025b413f}, {0x06839eb9, 0xa648a7dd, 0x8a9a021e, 0x025b413f}, {0x06839eb9, 0x025b413f, 0x911da0d7, 0xa648a7dd}}, + {{0xf06c144a, 0xe1988ad9, 0x619699cf, 0xafbd67f9}, {0xf06c144a, 0xe1988ad9, 0x619699cf, 0xafbd67f9}, {0xf06c144a, 0xafbd67f9, 0x5202ae19, 0xe1988ad9}}, + {{0x37730edf, 0xf8130c42, 0x6c0fd4f5, 0xb9d179e0}, {0x37730edf, 0xf8130c42, 0x6c0fd4f5, 0xb9d179e0}, {0x37730edf, 0xb9d179e0, 0xa382e3d4, 0xf8130c42}}, + {{0x076f3787, 0x8712b8bc, 0x38c0c8fd, 0xc381e88f}, {0x076f3787, 0x8712b8bc, 0x38c0c8fd, 0xc381e88f}, {0x076f3787, 0xc381e88f, 0x40300084, 0x8712b8bc}}, + {{0x701966a0, 0xf06d3fef, 0x7eed8d14, 0x8d88348a}, {0x701966a0, 0xf06d3fef, 0x7eed8d14, 0x8d88348a}, {0x701966a0, 0x8d88348a, 0xef06f3b4, 0xf06d3fef}}, + {{0x3bab6c39, 0x587fd280, 0x3b1a11df, 0xad45f23d}, {0x3bab6c39, 0x587fd280, 0x3b1a11df, 0xad45f23d}, {0x3bab6c39, 0xad45f23d, 0x76c57e18, 0x587fd280}} }}; class GuestBlock { @@ -117,11 +113,6 @@ std::string Hex(std::uint32_t value) { return text; } -std::array FinalMemory(const Row& row) { - const auto [a, b, c, d] = row.memory; - return {d ^ c, (b & ~0xff0000u) | ((c & 0xffu) << 16u), c + d, d - a}; -} - void Dispatch(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const std::uint8_t* base) { const auto address = static_cast(reinterpret_cast(base)); std::vector userData(10, 0u); @@ -160,9 +151,8 @@ void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveS } std::array memory{}; std::memcpy(memory.data(), guest.Data() + tid * 16u, 16u); - const auto expected = FinalMemory(row); for (std::uint32_t dword = 0; dword < 4u; ++dword) { - Require(memory[dword] == expected[dword], wave + std::to_string(tid) + " memory dword " + std::to_string(dword) + " is " + Hex(memory[dword]) + ", expected " + Hex(expected[dword])); + Require(memory[dword] == row.final[dword], wave + std::to_string(tid) + " memory dword " + std::to_string(dword) + " is " + Hex(memory[dword]) + ", expected " + Hex(row.final[dword])); } } for (std::size_t offset = Threads * 16u; offset < BlockBytes; ++offset) { @@ -178,7 +168,6 @@ int main() { if (!device) return VulkanTestSkipped; GuestBlock guest; Run(*device, guest, 32); - Run(*device, guest, 64); std::puts("flat cache bits tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoads.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoadsRegion.hpp similarity index 64% rename from core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoads.cpp rename to core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoadsRegion.hpp index 3ca6fa1b2..8a6272e69 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoads.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoadsRegion.hpp @@ -1,65 +1,10 @@ -#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" -#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" -#include "prx/libc/include/GuestAllocations.hpp" -#include "Recompiler.hpp" -#include "VulkanTestDevice.hpp" -#ifdef _WIN32 -#ifndef NOMINMAX -#define NOMINMAX -#endif -#include -#endif -#include +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_EXECUTION_FLATGLOBALLOADSREGION_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_EXECUTION_FLATGLOBALLOADSREGION_HPP + #include -#include -#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 Results = 16; -constexpr std::uint32_t Sentinel = 0x05e471e1; -constexpr std::size_t BlockBytes = 65536; -constexpr std::size_t RegionOffset = Threads * 16u; -alignas(256) std::array Output{}; - -alignas(256) constexpr std::array GlobalLoadsCode{ - 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, - 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, - 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, - 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, - 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, - 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, - 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc208800, 0x0a080020, 0xdc2487ff, - 0x0b080020, 0xdc288ffd, 0x0c080020, 0xdc2c8001, 0x0d7d0022, 0xdc308fff, 0x0e080020, 0xdc348ffa, - 0x0f7d0022, 0xdc3c8003, 0x11080020, 0xdc3887f8, 0x147d0022, 0xdc308000, 0x18080021, 0xdc208800, - 0x197d0022, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, - 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 -}; - -alignas(256) constexpr std::array FlatLoadsCode{ - 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, - 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, - 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, - 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, - 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, - 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, - 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc200000, 0x0a7d0024, 0xdc2407ff, - 0x0b7d0024, 0xdc280001, 0x0c7d0024, 0xdc2c0003, 0x0d7d0024, 0xdc300002, 0x0e7d0024, 0xdc340001, - 0x0f7d0024, 0xdc3c0007, 0x117d0024, 0xdc3807f1, 0x147d0024, 0xdc300400, 0x187d0024, 0xdc2807ff, - 0x197d0024, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, - 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 -}; - -constexpr std::array Region{ +inline constexpr std::array FlatGlobalLoadsRegion{ 0xd2, 0xf2, 0x26, 0x65, 0xa6, 0x0c, 0x12, 0xd2, 0x89, 0x18, 0x5d, 0x95, 0x0e, 0xe8, 0x81, 0x36, 0x09, 0x16, 0x6f, 0x6b, 0x11, 0x3d, 0x17, 0x8d, 0x6c, 0x0f, 0xd3, 0x90, 0x1f, 0xf2, 0x39, 0xa1, 0xa0, 0x95, 0xf2, 0x0f, 0x93, 0x95, 0x65, 0x0c, 0xf9, 0x38, 0x0b, 0x8e, 0xdb, 0x22, 0x4a, 0x6b, @@ -446,140 +391,4 @@ constexpr std::array Region{ 0xcd, 0x85, 0x85, 0x4d, 0x73, 0xa9, 0x16, 0x46, 0x65, 0x4a, 0xff, 0x72, 0xb1, 0x1c, 0x73, 0xa2 }; -struct Row { - std::array input; - std::array global; - std::array flat; -}; - -constexpr std::array Rows{{ - {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x000000d2, 0x00000029, 0x0000ebf6, 0x00004397, 0x4397357a, 0xf6804124, 0x97357aeb, 0x513c819d, 0x2e32095f, 0xefa22967, 0x6595f862, 0x295aec03, 0x523df3dc, 0x457d538e, 0x6526f2d2, 0x000000d2}, {0x000000d2, 0x0000007a, 0x000026f2, 0xffffa665, 0x0ca66526, 0xa66526f2, 0x89d2120c, 0x5d1889d2, 0x81e80e95, 0x6f160936, 0xe5522840, 0xd0e7469d, 0x80412474, 0x357aebf6, 0xfe1ec477, 0x0000357a}}, - {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x000000f2, 0xffffffdc, 0x00007aeb, 0xffff9d43, 0x9d439735, 0xebf68041, 0x4397357a, 0x5f513c81, 0x672e3209, 0x47efa229, 0x036595f8, 0xdc295aec, 0x8e523df3, 0x48457d53, 0xa66526f2, 0x000000f2}, {0x000000f2, 0x00000035, 0x00006526, 0x00000ca6, 0x120ca665, 0x0ca66526, 0x1889d212, 0x955d1889, 0x3681e80e, 0x6b6f1609, 0x9de55228, 0x74d0e746, 0xf6804124, 0x97357aeb, 0xe4fe1ec4, 0x00009735}}, - {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x00000026, 0xfffffff3, 0x0000357a, 0xffff819d, 0x819d4397, 0x7aebf680, 0x9d439735, 0x095f513c, 0x29672e32, 0xad47efa2, 0xec036595, 0xf3dc295a, 0x538e523d, 0xe048457d, 0x0ca66526, 0x00000026}, {0x000000a4, 0x0000005a, 0x0000449e, 0xffffadea, 0x9fadea44, 0xadea449e, 0x8aa0459f, 0xecce8aa0, 0x40199f09, 0x6f03851f, 0x97578278, 0x3c61fe8b, 0xbecaa1d3, 0xb65a60de, 0x37307a70, 0x0000b65a}}, - {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x00000065, 0x0000003d, 0x00009735, 0x00003c81, 0x3c819d43, 0x357aebf6, 0x819d4397, 0x32095f51, 0xa229672e, 0x53ad47ef, 0x5aec0365, 0x3df3dc29, 0x7d538e52, 0xfce04845, 0x120ca665, 0x00000065}, {0x000000dd, 0x00000060, 0x00009ea4, 0xffffea44, 0xadea449e, 0xea449ea4, 0xa0459fad, 0xce8aa045, 0x199f09ec, 0x03851f40, 0x578278da, 0x61fe8b97, 0xcaa1d33c, 0x5a60debe, 0x307a70d9, 0x00005a60}}, - {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x00000077, 0x00000072, 0x00007ea7, 0x00003154, 0x31541eca, 0xa734f958, 0x541eca7e, 0x204cb651, 0x16a2f996, 0x660affc8, 0x30d907a2, 0x72bdad38, 0xa92515b0, 0x948e5f94, 0xa2731cb1, 0x00000077}, {0x00000026, 0xffffff97, 0x0000a665, 0x0000120c, 0xd2120ca6, 0x120ca665, 0x5d1889d2, 0x0e955d18, 0x093681e8, 0x116b6f16, 0x469de552, 0x2474d0e7, 0xebf68041, 0x4397357a, 0x8ce4fe1e, 0x00004397}}, - {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, - {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x00000049, 0xffffffad, 0x000034f9, 0x00001eca, 0x1eca7ea7, 0xf95881df, 0xca7ea734, 0xb6513154, 0xf996204c, 0xffc816a2, 0x07a2e287, 0xad3830d9, 0x15b072bd, 0x5f94a925, 0x1cb172ff, 0x00000049}, {0x0000007a, 0x0000005a, 0x00009735, 0xffff9d43, 0x819d4397, 0x9d439735, 0x5f513c81, 0x32095f51, 0xa229672e, 0x53ad47ef, 0x553e074f, 0x8b833038, 0x6595f862, 0x295aec03, 0x5131541e, 0x0000295a}}, - {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x000000c6, 0xffffff91, 0x00002075, 0xffffb0c1, 0xb0c19888, 0x75f13ba1, 0xc1988820, 0x08a59ac0, 0x85539459, 0x73d7de27, 0x62c3c5bc, 0x91b7a807, 0x63034f25, 0x2db116b5, 0xdc295aec, 0x000000c6}, {0x00000097, 0xffffffdc, 0x00009d43, 0x00003c81, 0x513c819d, 0x3c819d43, 0x32095f51, 0x672e3209, 0x47efa229, 0x60e553ad, 0x3038553e, 0xf8628b83, 0xec036595, 0xf3dc295a, 0x4cb65131, 0x0000f3dc}}, - {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x000000f1, 0x00000054, 0x00001873, 0xffffdf25, 0xdf25a310, 0x7326b53e, 0x25a31018, 0x6645c8aa, 0x02f743cf, 0x8fd2a50e, 0x3b946093, 0x54ebd216, 0x9bd7f852, 0x53f53ed6, 0x7dee2657, 0x000000f1}, {0x00000000, 0x00000058, 0x00003386, 0xffffc349, 0xc0c34933, 0xc3493386, 0x010dfac0, 0x7d59010d, 0xcbb17d18, 0x7ef72fd3, 0x1ff610a6, 0x59e9d36c, 0x30633e8d, 0x3c584877, 0xb4de5e8e, 0x00003c58}}, - {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x000000a8, 0x0000002d, 0x0000a25c, 0xffff9c05, 0x9c0507a5, 0x5ca32e13, 0x0507a5a2, 0xeebcae0b, 0xfbcf54ff, 0x7c7b8218, 0x02701056, 0x2dd3f4aa, 0x602ab9e7, 0xcd71014b, 0xf610a667, 0x000000a8}, {0x00000021, 0xffffff8b, 0x0000d19a, 0xffff82a1, 0xc782a1d1, 0x82a1d19a, 0xb41ce3c7, 0x3b5db41c, 0x7ce0e57f, 0x00280664, 0x300b011b, 0x9b79ebd2, 0xc90fa8c4, 0x9c8be880, 0x401ae5e1, 0x00009c8b}}, - {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x000000d7, 0xffffffb6, 0x00004159, 0xffffb70c, 0xb70c48ee, 0x5905426c, 0x0c48ee41, 0x52e95ec2, 0x809af6c4, 0x9e49d979, 0x693895b7, 0xb6e0664f, 0xdeca057c, 0x2b2c163e, 0x3e052289, 0x000000d7}, {0x000000fa, 0x0000003d, 0x000017c8, 0xffffe6ab, 0xe4e6ab17, 0xe6ab17c8, 0xa2c98ce4, 0x4cd6a2c9, 0x36b17e32, 0xd6bd1487, 0x5bd3a52f, 0x225e3a51, 0xd65eeb8d, 0x0e3d40d4, 0x3f5aefae, 0x00000e3d}}, - {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x0000003b, 0x00000015, 0x00007b82, 0x000024e5, 0x24e5c17c, 0x8218fbcf, 0xe5c17c7b, 0x6ab73608, 0x185620a0, 0x575da8dc, 0x59ac90cd, 0x15783291, 0x7584528a, 0xe888f86d, 0xdebaea1d, 0x0000003b}, {0x0000008d, 0x00000074, 0x0000538b, 0xfffffa29, 0x6dfa2953, 0xfa29538b, 0xfc1ae26d, 0x4312fc1a, 0x1835159f, 0xb5fd7f6b, 0x4506273f, 0x554bd692, 0x7d422acd, 0xe774511b, 0xda660308, 0x0000e774}}, - {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, - {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x00000021, 0x00000071, 0x00007769, 0xffff8cc2, 0x8cc24bff, 0x69d0cbd1, 0xc24bff77, 0xd5c720a6, 0xd9c85a78, 0x60b4443a, 0xf6724fb3, 0x7153291c, 0x4a5c9177, 0x0b128e2b, 0x7f9859b1, 0x00000021}, {0x0000005d, 0x00000066, 0x00007957, 0xffff86c7, 0x8d86c779, 0x86c77957, 0x35e9c58d, 0x6f4835e9, 0x8d406c57, 0x4a4ad30d, 0x528a1578, 0xf86d7584, 0xdda0e888, 0xf666f927, 0xb19c74d6, 0x0000f666}}, - {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x00000011, 0x00000016, 0x0000b53e, 0x00001018, 0x10187326, 0x3ecdef03, 0x187326b5, 0xaadf25a3, 0xcf6645c8, 0x0e02f743, 0x936f25cd, 0x163b9460, 0x5254ebd2, 0xd69bd7f8, 0x224cb259, 0x00000011}, {0x00000010, 0x0000006e, 0x0000fc1c, 0xffffc9eb, 0x3ac9ebfc, 0xc9ebfc1c, 0x1ae0f83a, 0x43151ae0, 0xc7e70a45, 0x21c1452e, 0xc6e0e1a7, 0xda95b3a4, 0xa42ead3a, 0x506e741f, 0x3c3e2f3f, 0x0000506e}}, - {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x00000065, 0xffffff90, 0x0000764b, 0xffff9a41, 0x9a412405, 0x4ba1fb80, 0x41240576, 0xbd0167bc, 0x6edae83e, 0xbf9692b3, 0xa14f8d0a, 0x902df1a3, 0x7f913ad6, 0xec4185b7, 0x99571c29, 0x00000065}, {0x00000015, 0xffffffbe, 0x000078a4, 0xffffe536, 0x26e53678, 0xe53678a4, 0x6d03a026, 0x02016d03, 0xfc1fabaf, 0x3716dbf7, 0xfd9f16e3, 0x1d9033df, 0x5adebaea, 0xd7be5d2b, 0x936f25cd, 0x0000d7be}}, - {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x00000053, 0x00000067, 0x00004431, 0xffffd3b4, 0xd3b4c14d, 0x31813cb2, 0xb4c14d44, 0xb9279ed7, 0xb93ff927, 0x59859a53, 0xb7d12049, 0x675b4866, 0xc676e8d8, 0xdca0e1a0, 0x034ab01d, 0x00000053}, {0x000000a0, 0x0000006b, 0x00004ec7, 0xffffeba6, 0x4eeba64e, 0xeba64ec7, 0x6c3f954e, 0xa8636c3f, 0x7080725e, 0x9e00052d, 0x871b5d02, 0x6e53112f, 0x05ab81b3, 0xf86b2339, 0x6b431e8e, 0x0000f86b}}, - {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x000000fd, 0x00000072, 0x0000d4a3, 0x000069d0, 0x69d0cbd1, 0xa3aa5a25, 0xd0cbd1d4, 0xc24bff77, 0xc720a68c, 0xc85a78d5, 0x324d47e5, 0x724fb31c, 0x53291cf6, 0x5c917771, 0x15602efe, 0x000000fd}, {0x000000c8, 0xffffffd3, 0x00003527, 0x000067e7, 0xca67e735, 0x67e73527, 0x9c2a88ca, 0xb0e49c2a, 0x17c8fa9b, 0x8ce4e6ab, 0x1a2baeed, 0x4fd4b8c8, 0x61539dbe, 0x5bd3a52f, 0xd052e9fc, 0x00005bd3}}, - {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x00000086, 0xfffffffe, 0x000070d6, 0xffff8e1d, 0x8e1de1ab, 0xd6bd1487, 0x1de1ab70, 0x3b6b431e, 0x7e7923d3, 0x777b0e8e, 0x6c7e37f1, 0xfe28bb7f, 0xa0949a4c, 0x3ab02414, 0x65d656c0, 0x00000086}, {0x000000cd, 0xfffffffd, 0x0000e9fc, 0xffffd052, 0x66d052e9, 0xd052e9fc, 0x0d8fd866, 0x84530d8f, 0xefaef525, 0x6cde3f5a, 0x68e162c2, 0xffe524cd, 0x9b3d01a2, 0x41fded80, 0x3a515bd3, 0x000041fd}}, - {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, - {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x00000019, 0x00000057, 0x00005567, 0xffffdf45, 0xdf45fb80, 0x677ed35a, 0x45fb8055, 0x34f95881, 0x1eca7ea7, 0xb6513154, 0xa5a87b5e, 0x57dd4c23, 0x07a2e287, 0xad3830d9, 0xd14b0cf5, 0x00000019}, {0x000000fc, 0xffffffb0, 0x0000267e, 0xffffb948, 0x9eb94826, 0xb948267e, 0x0b25a49e, 0xd5d30b25, 0xa083e4b7, 0xcfb3bb6d, 0x5873c62f, 0xe73527c8, 0x2a88ca67, 0x9bb0e49c, 0xf83f261f, 0x00009bb0}}, - {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x0000009e, 0xffffff91, 0x000017c8, 0xffff8ce4, 0x8ce4e6ab, 0xc8fa9bb0, 0xe4e6ab17, 0x4cd6a2c9, 0x36b17e32, 0xd6bd1487, 0x3d40d4d6, 0x911b0a0e, 0xd1eba0cd, 0xe767b4ff, 0x1d426570, 0x0000009e}, {0x00000079, 0xffffffba, 0x000069c5, 0x00001a8c, 0x151a8c69, 0x1a8c69c5, 0x3678a415, 0x26e53678, 0x016d03a0, 0x1fabaf02, 0x6d22b51e, 0x9f16e3e8, 0x9033dffd, 0xdebaea1d, 0x892f4c71, 0x0000deba}}, - {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x00000065, 0xffffff90, 0x0000764b, 0xffff9a41, 0x9a412405, 0x4ba1fb80, 0x41240576, 0xbd0167bc, 0x6edae83e, 0xbf9692b3, 0xa14f8d0a, 0x902df1a3, 0x7f913ad6, 0xec4185b7, 0x79fec2ec, 0x00000065}, {0x00000013, 0x00000033, 0x0000835f, 0x00002ddd, 0x722ddd83, 0x2ddd835f, 0xc6429a72, 0xaac7c642, 0xa31b01f2, 0x599eb598, 0x2c46c27e, 0xab9c2334, 0x31cfa0b5, 0x02334e95, 0x19598433, 0x00000233}}, - {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x000000a2, 0x0000001c, 0x00005a25, 0xffffd1d4, 0xd1d4a3aa, 0x25f6d039, 0xd4a3aa5a, 0x7769d0cb, 0x8cc24bff, 0xd5c720a6, 0xe50f0b34, 0x1c324d47, 0xf6724fb3, 0x7153291c, 0x524da73e, 0x000000a2}, {0x000000ca, 0x0000001b, 0x00006436, 0xffff959f, 0x13959f64, 0x959f6436, 0x2be99013, 0x08252be9, 0x9f1b1c06, 0xfa5829ed, 0x24ff8388, 0x9a693293, 0x8428251f, 0x071b82c2, 0x1588587f, 0x0000071b}}, - {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x0000008c, 0x00000017, 0x00006d7c, 0xffffa815, 0xa815a39f, 0x7c7a524d, 0x15a39f6d, 0xed275ce5, 0x0e62da4d, 0xe790d315, 0x528bc830, 0x17eb5b02, 0x9da049a4, 0xb3a7bbef, 0x660a3790, 0x0000008c}, {0x00000088, 0x0000000d, 0x0000aae4, 0xffffe110, 0xdee110aa, 0xe110aae4, 0xb6eac1de, 0x62f1b6ea, 0x34343f1b, 0xf308081c, 0xdb19a044, 0x1095f2e7, 0x9f733159, 0x380d0562, 0xe242e585, 0x0000380d}}, - {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x00000047, 0x0000000d, 0x0000d463, 0x00001bf9, 0x1bf9e87d, 0x6302c753, 0xf9e87dd4, 0x378b4009, 0xf3c8b729, 0x598433f0, 0xa0d6d4b0, 0x0d8273a0, 0x6d34b2ad, 0xedd883ac, 0xaedff3c7, 0x00000047}, {0x00000013, 0xffffffef, 0x0000ea6b, 0xffffa06b, 0xb2a06bea, 0xa06bea6b, 0x945aacb2, 0x1b43945a, 0x66bd4d39, 0xf886f4f0, 0x7cb4af22, 0xb43c7bab, 0xb183013d, 0xa4ef2271, 0x72ee855b, 0x0000a4ef}}, - {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, - {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x000000c7, 0xffffffdb, 0x0000b20a, 0x0000100b, 0x100bbc11, 0x0aa2a4b1, 0x0bbc11b2, 0x5dc397db, 0xd1f4d133, 0x10aae488, 0x082b465a, 0xdb19a044, 0x1095f2e7, 0x9f733159, 0x92b36eda, 0x000000c7}, {0x000000af, 0x00000015, 0x0000fb40, 0xffffad6d, 0x2fad6dfb, 0xad6dfb40, 0xce007b2f, 0xccb8ce00, 0xa73e5b47, 0x7c7a524d, 0x4a5c9177, 0x0b128e2b, 0xfafd7702, 0xbf157cc0, 0xde591a71, 0x0000bf15}}, - {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x00000025, 0x0000002b, 0x00006fd8, 0x00001477, 0x1477e9be, 0xd8600189, 0x77e9be6f, 0x2c73a7bd, 0x421aff39, 0x1f09a43b, 0x87b15796, 0x2b17d0bf, 0xfa5d515c, 0x834fd313, 0x63779354, 0x00000025}, {0x00000073, 0x0000002f, 0x0000c858, 0x00003527, 0xe73527c8, 0x3527c858, 0x88ca67e7, 0x9c2a88ca, 0xfa9bb0e4, 0xe6ab17c8, 0xaeede5d8, 0xb8c81a2b, 0x9dbe4fd4, 0xa52f6153, 0xe9fccd90, 0x0000a52f}}, - {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x000000c2, 0xffffffd1, 0x0000e2a4, 0x00003181, 0x31813cb2, 0xa4765520, 0x813cb2e2, 0xb4c14d44, 0x279ed7d3, 0x3ff927b9, 0x164fb710, 0xd1204939, 0x5b4866b7, 0x76e8d867, 0x764984b5, 0x000000c2}, {0x0000009d, 0xffffffde, 0x000023c0, 0xffffdea4, 0x49dea423, 0xdea423c0, 0xe90c7c49, 0x8cede90c, 0x6a782b20, 0x414c4857, 0x6d03a026, 0xabaf0201, 0xdbf7fc1f, 0x1fde3716, 0xa50e02f7, 0x00001fde}}, - {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x00000049, 0xfffffff9, 0x00001856, 0x0000575d, 0x575da8dc, 0x5620a06a, 0x5da8dc18, 0x8d86c779, 0x4835e9c5, 0x406c576f, 0xe888f86d, 0xf927dda0, 0x9e9bf666, 0x0fcfcf14, 0x1d9033df, 0x00000049}, {0x000000fe, 0xffffff9a, 0x0000602e, 0x00000715, 0x0d071560, 0x0715602e, 0x5e8e080d, 0xb4de5e8e, 0xd8f27c75, 0xdc10e5e8, 0xa02ac205, 0x05b43c44, 0x72660c37, 0x489ae433, 0x30633e8d, 0x0000489a}}, - {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} -}}; - -class GuestBlock { -public: - GuestBlock() { -#ifdef _WIN32 - block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); -#else - block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); #endif - Require(block != nullptr, "flat global loads: cannot allocate the guest block"); - GuestAllocations::Mutation().Add(block, BlockBytes, true, false); - } - - ~GuestBlock() { - GuestAllocations::Mutation().Remove(block); -#ifdef _WIN32 - VirtualFree(block, 0, MEM_RELEASE); -#else - std::free(block); -#endif - } - - GuestBlock(const GuestBlock&) = delete; - GuestBlock& operator=(const GuestBlock&) = delete; - - std::uint8_t* Data() { return block; } - -private: - std::uint8_t* block = nullptr; -}; - -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%x", value); - return text; -} - -void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { - const auto address = static_cast(reinterpret_cast(base)); - std::vector userData(10, 0u); - const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); - std::copy(output.begin(), output.end(), userData.begin() + 4); - userData[8] = static_cast(address); - userData[9] = static_cast(address >> 32u); - const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; - const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {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 Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code, bool flat) { - Output.fill(Sentinel); - Dispatch(device, code, guest.Data()); - const auto wave = std::string(flat ? "flat" : "global") + " loads: lane "; - for (std::uint32_t tid = 0; tid < Threads; ++tid) { - const auto& expected = flat ? Rows[tid].flat : Rows[tid].global; - for (std::uint32_t result = 0; result < Results; ++result) { - const auto actual = Output[tid * Results + result]; - Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); - } - } -} - -} - -int main() { - try { - const auto device = OpenVulkanTestDevice(); - if (!device) return VulkanTestSkipped; - GuestBlock guest; - std::memset(guest.Data(), 0, BlockBytes); - for (std::uint32_t tid = 0; tid < Threads; ++tid) { - std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); - } - std::memcpy(guest.Data() + RegionOffset, Region.data(), Region.size()); - Run(*device, guest, GlobalLoadsCode, false); - Run(*device, guest, FlatLoadsCode, true); - std::puts("flat global loads tests passed"); - return 0; - } catch (const std::exception& error) { - std::cerr << error.what() << '\n'; - return 1; - } -} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoads.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoads.cpp new file mode 100644 index 000000000..6e3677e26 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoads.cpp @@ -0,0 +1,182 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#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 Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc200000, 0x0a7d0024, 0xdc2407ff, + 0x0b7d0024, 0xdc280001, 0x0c7d0024, 0xdc2c0003, 0x0d7d0024, 0xdc300002, 0x0e7d0024, 0xdc300400, + 0x187d0024, 0xdc2807ff, 0x197d0024, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, + 0x80010e02, 0xe0781020, 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x000000d2, 0x0000007a, 0x000026f2, 0xffffa665, 0x0ca66526, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xfe1ec477, 0x0000357a}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x000000f2, 0x00000035, 0x00006526, 0x00000ca6, 0x120ca665, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xe4fe1ec4, 0x00009735}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x000000a4, 0x0000005a, 0x0000449e, 0xffffadea, 0x9fadea44, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x37307a70, 0x0000b65a}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x000000dd, 0x00000060, 0x00009ea4, 0xffffea44, 0xadea449e, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x307a70d9, 0x00005a60}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x00000026, 0xffffff97, 0x0000a665, 0x0000120c, 0xd2120ca6, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x8ce4fe1e, 0x00004397}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x0000007a, 0x0000005a, 0x00009735, 0xffff9d43, 0x819d4397, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5131541e, 0x0000295a}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x00000097, 0xffffffdc, 0x00009d43, 0x00003c81, 0x513c819d, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x4cb65131, 0x0000f3dc}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x00000000, 0x00000058, 0x00003386, 0xffffc349, 0xc0c34933, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xb4de5e8e, 0x00003c58}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x00000021, 0xffffff8b, 0x0000d19a, 0xffff82a1, 0xc782a1d1, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x401ae5e1, 0x00009c8b}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x000000fa, 0x0000003d, 0x000017c8, 0xffffe6ab, 0xe4e6ab17, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x3f5aefae, 0x00000e3d}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x0000008d, 0x00000074, 0x0000538b, 0xfffffa29, 0x6dfa2953, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xda660308, 0x0000e774}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x0000005d, 0x00000066, 0x00007957, 0xffff86c7, 0x8d86c779, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xb19c74d6, 0x0000f666}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x00000010, 0x0000006e, 0x0000fc1c, 0xffffc9eb, 0x3ac9ebfc, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x3c3e2f3f, 0x0000506e}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x00000015, 0xffffffbe, 0x000078a4, 0xffffe536, 0x26e53678, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x936f25cd, 0x0000d7be}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x000000a0, 0x0000006b, 0x00004ec7, 0xffffeba6, 0x4eeba64e, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x6b431e8e, 0x0000f86b}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x000000c8, 0xffffffd3, 0x00003527, 0x000067e7, 0xca67e735, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xd052e9fc, 0x00005bd3}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x000000cd, 0xfffffffd, 0x0000e9fc, 0xffffd052, 0x66d052e9, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x3a515bd3, 0x000041fd}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x000000fc, 0xffffffb0, 0x0000267e, 0xffffb948, 0x9eb94826, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xf83f261f, 0x00009bb0}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x00000079, 0xffffffba, 0x000069c5, 0x00001a8c, 0x151a8c69, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x892f4c71, 0x0000deba}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x00000013, 0x00000033, 0x0000835f, 0x00002ddd, 0x722ddd83, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x19598433, 0x00000233}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x000000ca, 0x0000001b, 0x00006436, 0xffff959f, 0x13959f64, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x1588587f, 0x0000071b}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x00000088, 0x0000000d, 0x0000aae4, 0xffffe110, 0xdee110aa, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xe242e585, 0x0000380d}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x00000013, 0xffffffef, 0x0000ea6b, 0xffffa06b, 0xb2a06bea, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x72ee855b, 0x0000a4ef}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x000000af, 0x00000015, 0x0000fb40, 0xffffad6d, 0x2fad6dfb, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xde591a71, 0x0000bf15}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x00000073, 0x0000002f, 0x0000c858, 0x00003527, 0xe73527c8, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xe9fccd90, 0x0000a52f}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x0000009d, 0xffffffde, 0x000023c0, 0xffffdea4, 0x49dea423, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xa50e02f7, 0x00001fde}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x000000fe, 0xffffff9a, 0x0000602e, 0x00000715, 0x0d071560, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x30633e8d, 0x0000489a}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "flat loads: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +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%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {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 Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "flat loads: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("flat loads tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX2X3.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX2X3.cpp new file mode 100644 index 000000000..2642ebc78 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX2X3.cpp @@ -0,0 +1,181 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#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 Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc340001, 0x0f7d0024, 0xdc3c0007, + 0x117d0024, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, + 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xa66526f2, 0x89d2120c, 0x5d1889d2, 0x81e80e95, 0x6f160936, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x0ca66526, 0x1889d212, 0x955d1889, 0x3681e80e, 0x6b6f1609, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xadea449e, 0x8aa0459f, 0xecce8aa0, 0x40199f09, 0x6f03851f, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xea449ea4, 0xa0459fad, 0xce8aa045, 0x199f09ec, 0x03851f40, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x120ca665, 0x5d1889d2, 0x0e955d18, 0x093681e8, 0x116b6f16, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x9d439735, 0x5f513c81, 0x32095f51, 0xa229672e, 0x53ad47ef, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x3c819d43, 0x32095f51, 0x672e3209, 0x47efa229, 0x60e553ad, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xc3493386, 0x010dfac0, 0x7d59010d, 0xcbb17d18, 0x7ef72fd3, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x82a1d19a, 0xb41ce3c7, 0x3b5db41c, 0x7ce0e57f, 0x00280664, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xe6ab17c8, 0xa2c98ce4, 0x4cd6a2c9, 0x36b17e32, 0xd6bd1487, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xfa29538b, 0xfc1ae26d, 0x4312fc1a, 0x1835159f, 0xb5fd7f6b, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x86c77957, 0x35e9c58d, 0x6f4835e9, 0x8d406c57, 0x4a4ad30d, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xc9ebfc1c, 0x1ae0f83a, 0x43151ae0, 0xc7e70a45, 0x21c1452e, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xe53678a4, 0x6d03a026, 0x02016d03, 0xfc1fabaf, 0x3716dbf7, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xeba64ec7, 0x6c3f954e, 0xa8636c3f, 0x7080725e, 0x9e00052d, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x67e73527, 0x9c2a88ca, 0xb0e49c2a, 0x17c8fa9b, 0x8ce4e6ab, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xd052e9fc, 0x0d8fd866, 0x84530d8f, 0xefaef525, 0x6cde3f5a, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xb948267e, 0x0b25a49e, 0xd5d30b25, 0xa083e4b7, 0xcfb3bb6d, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x1a8c69c5, 0x3678a415, 0x26e53678, 0x016d03a0, 0x1fabaf02, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x2ddd835f, 0xc6429a72, 0xaac7c642, 0xa31b01f2, 0x599eb598, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x959f6436, 0x2be99013, 0x08252be9, 0x9f1b1c06, 0xfa5829ed, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xe110aae4, 0xb6eac1de, 0x62f1b6ea, 0x34343f1b, 0xf308081c, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xa06bea6b, 0x945aacb2, 0x1b43945a, 0x66bd4d39, 0xf886f4f0, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xad6dfb40, 0xce007b2f, 0xccb8ce00, 0xa73e5b47, 0x7c7a524d, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x3527c858, 0x88ca67e7, 0x9c2a88ca, 0xfa9bb0e4, 0xe6ab17c8, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xdea423c0, 0xe90c7c49, 0x8cede90c, 0x6a782b20, 0x414c4857, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x0715602e, 0x5e8e080d, 0xb4de5e8e, 0xd8f27c75, 0xdc10e5e8, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "flat loads x2 x3: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +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%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {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 Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "flat loads x2 x3: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("flat loads x2 x3 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX4.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX4.cpp new file mode 100644 index 000000000..008004a55 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX4.cpp @@ -0,0 +1,181 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#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 Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc3807f1, 0x147d0024, 0xbf8c0070, + 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, 0x80011202, 0xe0781030, + 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xe5522840, 0xd0e7469d, 0x80412474, 0x357aebf6, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x9de55228, 0x74d0e746, 0xf6804124, 0x97357aeb, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x97578278, 0x3c61fe8b, 0xbecaa1d3, 0xb65a60de, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x578278da, 0x61fe8b97, 0xcaa1d33c, 0x5a60debe, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x469de552, 0x2474d0e7, 0xebf68041, 0x4397357a, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x553e074f, 0x8b833038, 0x6595f862, 0x295aec03, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x3038553e, 0xf8628b83, 0xec036595, 0xf3dc295a, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x1ff610a6, 0x59e9d36c, 0x30633e8d, 0x3c584877, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x300b011b, 0x9b79ebd2, 0xc90fa8c4, 0x9c8be880, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5bd3a52f, 0x225e3a51, 0xd65eeb8d, 0x0e3d40d4, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x4506273f, 0x554bd692, 0x7d422acd, 0xe774511b, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x528a1578, 0xf86d7584, 0xdda0e888, 0xf666f927, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xc6e0e1a7, 0xda95b3a4, 0xa42ead3a, 0x506e741f, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xfd9f16e3, 0x1d9033df, 0x5adebaea, 0xd7be5d2b, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x871b5d02, 0x6e53112f, 0x05ab81b3, 0xf86b2339, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x1a2baeed, 0x4fd4b8c8, 0x61539dbe, 0x5bd3a52f, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x68e162c2, 0xffe524cd, 0x9b3d01a2, 0x41fded80, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5873c62f, 0xe73527c8, 0x2a88ca67, 0x9bb0e49c, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x6d22b51e, 0x9f16e3e8, 0x9033dffd, 0xdebaea1d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x2c46c27e, 0xab9c2334, 0x31cfa0b5, 0x02334e95, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x24ff8388, 0x9a693293, 0x8428251f, 0x071b82c2, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xdb19a044, 0x1095f2e7, 0x9f733159, 0x380d0562, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x7cb4af22, 0xb43c7bab, 0xb183013d, 0xa4ef2271, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x4a5c9177, 0x0b128e2b, 0xfafd7702, 0xbf157cc0, 0x5a5a000e, 0x5a5a000f}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xaeede5d8, 0xb8c81a2b, 0x9dbe4fd4, 0xa52f6153, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x6d03a026, 0xabaf0201, 0xdbf7fc1f, 0x1fde3716, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xa02ac205, 0x05b43c44, 0x72660c37, 0x489ae433, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "flat loads x4: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +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%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {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 Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "flat loads x4: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("flat loads x4 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicCsub.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicCsub.cpp new file mode 100644 index 000000000..7c84e3e8c --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicCsub.cpp @@ -0,0 +1,249 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 4; +constexpr std::uint32_t Written = 3; +constexpr std::uint32_t SharedOffset = Threads * 4u; +constexpr std::uint32_t Untouched = 0xdeadbeefu; +constexpr std::uint32_t SharedPartial = 0x800186a0u; +constexpr std::uint32_t SharedExhausted = 1000u; +constexpr std::size_t BlockBytes = 4096; +constexpr std::uint8_t Fill = 0xcd; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060082, 0xe0301000, 0x80000a01, 0xe0301004, 0x80000b01, 0xe0301008, 0x80000c01, + 0x7e0e02ff, 0x00000100, 0xbf8c3f70, 0xdcd18000, 0x14080a03, 0xdcd18000, 0x15080b07, 0xdcd18004, + 0x16080c07, 0xbf8c3f70, 0xe0701000, 0x80011401, 0xe0701004, 0x80011501, 0xe0701008, 0x80011601, + 0xbf810000, +}; + +alignas(256) constexpr std::array NoReturnCode{ + 0xdcd08000, 0x00080a03, 0xbf810000, +}; + +alignas(256) constexpr std::array FlatSegmentCode{ + 0xdcd10000, 0x147f0a03, 0xbf810000, +}; + +struct Row { + std::uint32_t memory; + std::uint32_t subtrahend; +}; + +constexpr std::array Edges{{ + {0u, 0u}, {0u, 1u}, {1u, 0u}, {1u, 1u}, + {5u, 7u}, {7u, 5u}, {0xffffffffu, 1u}, {1u, 0xffffffffu}, + {0x80000000u, 0x7fffffffu}, {0x7fffffffu, 0x80000000u}, {0xffffffffu, 0xffffffffu}, {0x80000000u, 0x80000000u}, + {0x80000001u, 0x80000000u}, {0u, 0xffffffffu}, {0xffffffffu, 0u}, {0x12345678u, 0x12345679u}, +}}; + +Row RowOf(std::uint32_t tid) { + if (tid < Edges.size()) return Edges[tid]; + const std::uint32_t memory = (tid + 1u) * 0x9e3779b9u; + const std::uint32_t subtrahend = (tid + 1u) * 0x85ebca6bu; + return {memory, (tid & 3u) == 0u ? subtrahend >> 8u : subtrahend}; +} + +std::uint32_t SharedPartialStep(std::uint32_t tid) { return tid + 1u; } +std::uint32_t SharedExhaustedStep(std::uint32_t tid) { return tid + 100u; } + +std::uint32_t SubtractClamped(std::uint32_t memory, std::uint32_t subtrahend) { + return memory >= subtrahend ? memory - subtrahend : 0u; +} + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "global atomic csub: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, true); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +void Put(std::vector& image, std::uint32_t offset, std::uint32_t value) { + for (std::uint32_t byte = 0; byte < 4u; ++byte) image.at(offset + byte) = static_cast(value >> (byte * 8u)); +} + +std::vector Initial() { + std::vector image(BlockBytes, Fill); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, tid * 4u, RowOf(tid).memory); + } + Put(image, SharedOffset, SharedPartial); + Put(image, SharedOffset + 4u, SharedExhausted); + return image; +} + +std::vector Expected() { + auto image = Initial(); + std::uint32_t partial = SharedPartial; + std::uint32_t exhausted = SharedExhausted; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const Row row = RowOf(tid); + Put(image, tid * 4u, SubtractClamped(row.memory, row.subtrahend)); + partial = SubtractClamped(partial, SharedPartialStep(tid)); + exhausted = SubtractClamped(exhausted, SharedExhaustedStep(tid)); + } + Require(partial != 0u && exhausted == 0u, "global atomic csub: the shared dwords do not cover both outcomes"); + Put(image, SharedOffset, partial); + Put(image, SharedOffset + 4u, exhausted); + return image; +} + +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; +} + +std::vector UserData(const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 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); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + return userData; +} + +ShaderRecompiler::RecompileResult Recompile(const AgcDriver::VulkanDevice& device, std::span code, std::uint32_t waveSize, std::span userData) { + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + return ShaderRecompiler::Recompile(request); +} + +void CheckChain(const std::string& where, std::uint32_t column, std::uint32_t initial, std::uint32_t (*step)(std::uint32_t)) { + std::vector> order; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + order.emplace_back(Output[tid * Results + column], tid); + } + std::sort(order.begin(), order.end(), [](const auto& a, const auto& b) { return a.first > b.first; }); + std::uint32_t running = initial; + for (const auto& [returned, tid] : order) { + Require(returned == running, where + " returned " + Hex(returned) + " to thread " + std::to_string(tid) + ", expected " + Hex(running)); + running = SubtractClamped(running, step(tid)); + } +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + Input.fill(0u); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Input[tid * Inputs] = RowOf(tid).subtrahend; + Input[tid * Inputs + 1u] = SharedPartialStep(tid); + Input[tid * Inputs + 2u] = SharedExhaustedStep(tid); + } + Output.fill(Untouched); + const auto userData = UserData(guest.Data()); + const std::span code(Code); + const auto result = Recompile(device, code, waveSize, userData); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + + const auto wave = "global atomic csub: wave" + std::to_string(waveSize) + " "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const Row row = RowOf(tid); + const auto returned = Output[tid * Results]; + Require(returned == row.memory, wave + "thread " + std::to_string(tid) + " returned " + Hex(returned) + ", expected " + Hex(row.memory)); + for (std::uint32_t j = Written; j < Results; ++j) { + Require(Output[tid * Results + j] == Untouched, wave + "thread " + std::to_string(tid) + " result " + std::to_string(j) + " was written"); + } + } + CheckChain(wave + "dword shared by all lanes", 1u, SharedPartial, SharedPartialStep); + CheckChain(wave + "dword exhausted by all lanes", 2u, SharedExhausted, SharedExhaustedStep); + const auto expected = Expected(); + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + const auto actual = guest.Data()[offset]; + Require(actual == expected[offset], wave + "byte " + std::to_string(offset) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[offset])); + } +} + +void CheckRejected(const AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code, const std::string& reason) { + const auto userData = UserData(guest.Data()); + std::string failure; + try { + static_cast(Recompile(device, code, 32, userData)); + } catch (const std::exception& error) { + failure = error.what(); + } + Require(failure.find(reason) != std::string::npos, "global atomic csub: expected '" + reason + "', got '" + failure + "'"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + Run(*device, guest, 32); + Run(*device, guest, 64); + CheckRejected(*device, guest, NoReturnCode, "global_atomic_csub without glc is not supported"); + CheckRejected(*device, guest, FlatSegmentCode, "global_atomic_csub is available only in the global segment"); + std::puts("global atomic csub tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics.cpp index 736dd9cdc..3409e7768 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics.cpp @@ -26,7 +26,7 @@ using ShaderRecompiler::ShaderStage; constexpr std::uint32_t Threads = 64; constexpr std::uint32_t RowsPerOp = 16; -constexpr std::uint32_t OpCount = 24; +constexpr std::uint32_t OpCount = 13; constexpr std::uint32_t NarrowOps = 13; constexpr std::uint32_t OpStride = 0x200; constexpr std::uint32_t Special = OpCount * OpStride; @@ -36,7 +36,7 @@ constexpr std::uint8_t Fill = 0xcd; alignas(256) std::array Input{}; alignas(256) std::array Output{}; -alignas(256) constexpr std::array GlobalAtomicsCode{ +alignas(256) constexpr std::array GlobalAtomicsCode{ 0x34020083, 0x34040084, 0x340a0083, 0x7e2802ff, 0x05e471e1, 0x7e2a02ff, 0x05e471e1, 0x4a060280, 0x4a080480, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0a80, 0xdcc18000, 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, 0x4a0602ff, 0x00000200, 0x4a0804ff, 0x00000400, 0xe0381000, 0x80000a04, @@ -60,31 +60,7 @@ alignas(256) constexpr std::array GlobalAtomicsCode{ 0x4a0804ff, 0x00002c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00001600, 0xdcf18000, 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, 0x4a0602ff, 0x00001800, 0x4a0804ff, 0x00003000, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00001800, 0xdcf58000, 0x14080a03, 0xbf8c3f70, - 0xe0701000, 0x80011406, 0x4a0602ff, 0x00001a00, 0x4a0804ff, 0x00003400, 0xe0381000, 0x80000a04, - 0xbf8c3f70, 0x4a0c0aff, 0x00001a00, 0xdd418000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, - 0x4a0602ff, 0x00001c00, 0x4a0804ff, 0x00003800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, - 0x00001c00, 0xdd458000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00001e00, - 0x4a0804ff, 0x00003c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00001e00, 0xdd498000, - 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002000, 0x4a0804ff, 0x00004000, - 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00002000, 0xdd4d8000, 0x14080a03, 0xbf8c3f70, - 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002200, 0x4a0804ff, 0x00004400, 0xe0381000, 0x80000a04, - 0xbf8c3f70, 0x4a0c0aff, 0x00002200, 0xdd558000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, - 0x4a0602ff, 0x00002400, 0x4a0804ff, 0x00004800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, - 0x00002400, 0xdd598000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002600, - 0x4a0804ff, 0x00004c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00002600, 0xdd5d8000, - 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002800, 0x4a0804ff, 0x00005000, - 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00002800, 0xdd618000, 0x14080a03, 0xbf8c3f70, - 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002a00, 0x4a0804ff, 0x00005400, 0xe0381000, 0x80000a04, - 0xbf8c3f70, 0x4a0c0aff, 0x00002a00, 0xdd658000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, - 0x4a0602ff, 0x00002c00, 0x4a0804ff, 0x00005800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, - 0x00002c00, 0xdd698000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002e00, - 0x4a0804ff, 0x00005c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00002e00, 0xdd6d8000, - 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00003000, 0x7e2802ff, 0x05e471e1, - 0x4a140081, 0xdcc88000, 0x00080a03, 0xbf8c3f70, 0xd70f6a1e, 0x02020608, 0x7e3e0209, 0x503e3e80, - 0xd70f6a1e, 0x02023cff, 0x00000200, 0x503e3e80, 0x34140088, 0xdced0000, 0x147d0a1e, 0xbf8c0070, - 0x4a0c0aff, 0x00003000, 0xe0701000, 0x80011406, 0x7e0602ff, 0x00003400, 0x4a1414ff, 0x00000100, - 0xdcc98004, 0x14080a03, 0xbf8c3f70, 0x4a0c0aff, 0x00003200, 0xe0701000, 0x80011406, 0x7da80090, - 0x4a0602ff, 0x00003600, 0x7e1402ff, 0x000000f0, 0xdce88000, 0x00080a03, 0xbf8c3f70, 0xbf810000 + 0xe0701000, 0x80011406, 0xbf810000 }; struct Row { @@ -96,7 +72,7 @@ struct Row { std::uint64_t final; }; -constexpr std::array Rows{{ +constexpr std::array Rows{{ {0, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, {0, 0x1ull, 0x80000000ull, 0xf9677975ull, 0x1ull, 0x80000000ull}, {0, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0xffffffffull}, @@ -305,182 +281,6 @@ constexpr std::array Rows{{ {12, 0xf9a9360ull, 0xf9a9360ull, 0xf85b81cdull, 0xf9a9360ull, 0xf9a935full}, {12, 0x6e723d5cull, 0x6e723d5dull, 0x6e723d5cull, 0x6e723d5cull, 0x6e723d5bull}, {12, 0x1f609b4full, 0x1f609b4full, 0x69d177f8ull, 0x1f609b4full, 0x1f609b4eull}, - {13, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, - {13, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, - {13, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, - {13, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xb210ee8d643f54e6ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, - {13, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {13, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, - {13, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, - {13, 0xfffffffeffffffffull, 0x1ull, 0xed5fb21be38948d7ull, 0xfffffffeffffffffull, 0x1ull}, - {13, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, - {13, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, - {13, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, - {13, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xb346daefa27ab170ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, - {13, 0x53bb9629497dfb3full, 0xa4abfed10ad2a242ull, 0x53bb9629497dfb3full, 0x53bb9629497dfb3full, 0xa4abfed10ad2a242ull}, - {13, 0x6c12816965391baaull, 0xce7cd7b44b45eaf3ull, 0x6c12816967391baaull, 0x6c12816965391baaull, 0xce7cd7b44b45eaf3ull}, - {13, 0xdba996ccffffffffull, 0x10bbe64100000001ull, 0xdba996ccffffffffull, 0xdba996ccffffffffull, 0x10bbe64100000001ull}, - {13, 0x5a7c138cffffffffull, 0x9c15fed00000001ull, 0x5a7c138cdfffffffull, 0x5a7c138cffffffffull, 0x9c15fed00000001ull}, - {14, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, - {14, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, - {14, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, - {14, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x166c48c6e9c1b01cull, 0x8000000000000000ull, 0x8000000000000000ull}, - {14, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {14, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, - {14, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, - {14, 0xfffffffeffffffffull, 0x1ull, 0x866b08e67ea9d02cull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, - {14, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, - {14, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, - {14, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, - {14, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xa54604e5b9b1311full, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, - {14, 0xb9a56d03ead2890full, 0x242f2f3b525af861ull, 0xb9a56d03ead2890full, 0xb9a56d03ead2890full, 0x242f2f3b525af861ull}, - {14, 0x65c783e7e26c0130ull, 0x19a0106761c2f186ull, 0x65c783e7e06c0130ull, 0x65c783e7e26c0130ull, 0x65c783e7e26c0130ull}, - {14, 0x7bebc240ffffffffull, 0x4da1ba4b00000001ull, 0x7bebc240ffffffffull, 0x7bebc240ffffffffull, 0x4da1ba4b00000001ull}, - {14, 0x57e3536bffffffffull, 0xd8d033bd00000001ull, 0x57e3536bdfffffffull, 0x57e3536bffffffffull, 0x57e3536bffffffffull}, - {15, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, - {15, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xffffffff00000000ull}, - {15, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {15, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd4db9193123f6ebaull, 0x8000000000000000ull, 0x923456789abcdef0ull}, - {15, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xfffffffffffffffeull}, - {15, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, - {15, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, - {15, 0xfffffffeffffffffull, 0x1ull, 0x25fd502c82facbc0ull, 0xfffffffeffffffffull, 0xffffffff00000000ull}, - {15, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, - {15, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, - {15, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xffffffff00000000ull}, - {15, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf9124f0e4aae24c5ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {15, 0xc3e86469baffc1dull, 0xc22f0e9d6d69338dull, 0xc3e86469baffc1dull, 0xc3e86469baffc1dull, 0xce6d94e409192faaull}, - {15, 0xe499e6544c91e53ull, 0x4647904bf55b8053ull, 0xe499e6546c91e53ull, 0xe499e6544c91e53ull, 0x54912eb13a249ea6ull}, - {15, 0x7a4a48e2ffffffffull, 0x7ccc4b5300000001ull, 0x7a4a48e2ffffffffull, 0x7a4a48e2ffffffffull, 0xf716943600000000ull}, - {15, 0xa925179dffffffffull, 0xb2b1ac1800000001ull, 0xa925179ddfffffffull, 0xa925179dffffffffull, 0x5bd6c3b600000000ull}, - {16, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x8000000000000001ull}, - {16, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x100000002ull}, - {16, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {16, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x6835bf86a066a97eull, 0x8000000000000000ull, 0x6dcba98765432110ull}, - {16, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0x0ull}, - {16, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, - {16, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ull}, - {16, 0xfffffffeffffffffull, 0x1ull, 0xbcbca7d9810b83c1ull, 0xfffffffeffffffffull, 0xfffffffefffffffeull}, - {16, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456779abcdef0ull}, - {16, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x8000000000000001ull}, - {16, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x100000002ull}, - {16, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x663c553a9a16e35ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {16, 0x63fbf4e8e453a4c5ull, 0xb7a15652045c43f6ull, 0x63fbf4e8e453a4c5ull, 0x63fbf4e8e453a4c5ull, 0xac5a9e96dff760cfull}, - {16, 0x1603b25645bdd8aaull, 0x1f87b96dd545e1f1ull, 0x1603b25647bdd8aaull, 0x1603b25645bdd8aaull, 0xf67bf8e87077f6b9ull}, - {16, 0x757fd168ffffffffull, 0x2356488500000001ull, 0x757fd168ffffffffull, 0x757fd168ffffffffull, 0x522988e3fffffffeull}, - {16, 0x752b2a35ffffffffull, 0x869e617c00000001ull, 0x752b2a35dfffffffull, 0x752b2a35ffffffffull, 0xee8cc8b9fffffffeull}, - {17, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, - {17, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, - {17, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, - {17, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd286c038892fd9d4ull, 0x8000000000000000ull, 0x8000000000000000ull}, - {17, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {17, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, - {17, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, - {17, 0xfffffffeffffffffull, 0x1ull, 0x80b34c5cb5907e40ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, - {17, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, - {17, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, - {17, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, - {17, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xe7cd1f2260293767ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, - {17, 0x8372abbda96ba8eeull, 0x5e71be928ba29363ull, 0x8372abbda96ba8eeull, 0x8372abbda96ba8eeull, 0x8372abbda96ba8eeull}, - {17, 0xce77c3cdcc677910ull, 0xdf44f6f934647f0eull, 0xce77c3cdce677910ull, 0xce77c3cdcc677910ull, 0xce77c3cdcc677910ull}, - {17, 0x9b3edbb1ffffffffull, 0xf4f16cf100000001ull, 0x9b3edbb1ffffffffull, 0x9b3edbb1ffffffffull, 0x9b3edbb1ffffffffull}, - {17, 0xadf3fbf0ffffffffull, 0x8b29592900000001ull, 0xadf3fbf0dfffffffull, 0xadf3fbf0ffffffffull, 0x8b29592900000001ull}, - {18, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, - {18, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, - {18, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, - {18, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xff63cf82d01938e9ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, - {18, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {18, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, - {18, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, - {18, 0xfffffffeffffffffull, 0x1ull, 0x2a88358605f1254ull, 0xfffffffeffffffffull, 0x1ull}, - {18, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, - {18, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, - {18, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, - {18, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf078418740b4f6d3ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, - {18, 0x8d4a240ceafb92f2ull, 0xd789c424b5282b1dull, 0x8d4a240ceafb92f2ull, 0x8d4a240ceafb92f2ull, 0x8d4a240ceafb92f2ull}, - {18, 0x64b3620ed7a9434full, 0x930b9894c3741be0ull, 0x64b3620ed5a9434full, 0x64b3620ed7a9434full, 0x64b3620ed7a9434full}, - {18, 0x62a12f63ffffffffull, 0x4a1df58400000001ull, 0x62a12f63ffffffffull, 0x62a12f63ffffffffull, 0x4a1df58400000001ull}, - {18, 0xd0826a0cffffffffull, 0x14fe08aa00000001ull, 0xd0826a0cdfffffffull, 0xd0826a0cffffffffull, 0x14fe08aa00000001ull}, - {19, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, - {19, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, - {19, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, - {19, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x35f6b71611838017ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, - {19, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {19, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, - {19, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x100000000ull}, - {19, 0xfffffffeffffffffull, 0x1ull, 0x884dcfec36f43da6ull, 0xfffffffeffffffffull, 0x1ull}, - {19, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull}, - {19, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, - {19, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, - {19, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x97e21514d87b299dull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, - {19, 0x7ae9f54bc19e56ddull, 0x4a6ac7aeb830ed76ull, 0x7ae9f54bc19e56ddull, 0x7ae9f54bc19e56ddull, 0x7ae9f54bc19e56ddull}, - {19, 0x5b1c8aa1eb9a258cull, 0xf4a0ea8b3badac72ull, 0x5b1c8aa1e99a258cull, 0x5b1c8aa1eb9a258cull, 0x5b1c8aa1eb9a258cull}, - {19, 0x869f35deffffffffull, 0xdeffa68100000001ull, 0x869f35deffffffffull, 0x869f35deffffffffull, 0xdeffa68100000001ull}, - {19, 0x5752b4dfffffffffull, 0x2a51ee7e00000001ull, 0x5752b4dfdfffffffull, 0x5752b4dfffffffffull, 0x5752b4dfffffffffull}, - {20, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, - {20, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, - {20, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, - {20, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd316f470e2d8993bull, 0x8000000000000000ull, 0x8000000000000000ull}, - {20, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {20, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, - {20, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x100000000ull}, - {20, 0xfffffffeffffffffull, 0x1ull, 0xfc2b7ace1072e7b9ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, - {20, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull}, - {20, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, - {20, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, - {20, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x142c1e683d47a123ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, - {20, 0x5ea0e74f0d30cb76ull, 0xecd62560f1407a13ull, 0x5ea0e74f0d30cb76ull, 0x5ea0e74f0d30cb76ull, 0xecd62560f1407a13ull}, - {20, 0x6f8664b99db31c8bull, 0x77417f9d1c387d00ull, 0x6f8664b99fb31c8bull, 0x6f8664b99db31c8bull, 0x77417f9d1c387d00ull}, - {20, 0x1e0ce078ffffffffull, 0xc130cf3c00000001ull, 0x1e0ce078ffffffffull, 0x1e0ce078ffffffffull, 0xc130cf3c00000001ull}, - {20, 0xd160d889ffffffffull, 0x2b456f6800000001ull, 0xd160d889dfffffffull, 0xd160d889ffffffffull, 0xd160d889ffffffffull}, - {21, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, - {21, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, - {21, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x0ull}, - {21, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x691beca72ac31615ull, 0x8000000000000000ull, 0x0ull}, - {21, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {21, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, - {21, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x0ull}, - {21, 0xfffffffeffffffffull, 0x1ull, 0xc89594109911783aull, 0xfffffffeffffffffull, 0x1ull}, - {21, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x0ull}, - {21, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, - {21, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, - {21, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xa69bfe93d6efb4ddull, 0x7fffffffffffffffull, 0x0ull}, - {21, 0xd5e7cc579d21963aull, 0x93826a1ee8ab2ed1ull, 0xd5e7cc579d21963aull, 0xd5e7cc579d21963aull, 0x9182481688210610ull}, - {21, 0x657c382c4e1cfcddull, 0x837a0dc63ac35f32ull, 0x657c382c4c1cfcddull, 0x657c382c4e1cfcddull, 0x17808040a005c10ull}, - {21, 0x7552c284ffffffffull, 0x995c2cf200000001ull, 0x7552c284ffffffffull, 0x7552c284ffffffffull, 0x1150008000000001ull}, - {21, 0xe20d4f8dffffffffull, 0x4864cdbc00000001ull, 0xe20d4f8ddfffffffull, 0xe20d4f8dffffffffull, 0x40044d8c00000001ull}, - {22, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, - {22, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, - {22, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {22, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xbad3dfdbb007d4ecull, 0x8000000000000000ull, 0x923456789abcdef0ull}, - {22, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {22, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, - {22, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, - {22, 0xfffffffeffffffffull, 0x1ull, 0xedecea3c6fa0d23full, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, - {22, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, - {22, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, - {22, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, - {22, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf7926675f45d1971ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {22, 0x360443a82569e20cull, 0x3800d9ff899231faull, 0x360443a82569e20cull, 0x360443a82569e20cull, 0x3e04dbffadfbf3feull}, - {22, 0xc265df77a6c71a0dull, 0x5e94d6ccc50557f8ull, 0xc265df77a4c71a0dull, 0xc265df77a6c71a0dull, 0xdef5dfffe7c75ffdull}, - {22, 0x41a90d6bffffffffull, 0x4bb3092400000001ull, 0x41a90d6bffffffffull, 0x41a90d6bffffffffull, 0x4bbb0d6fffffffffull}, - {22, 0xc54e9269ffffffffull, 0xf536f34000000001ull, 0xc54e9269dfffffffull, 0xc54e9269ffffffffull, 0xf57ef369ffffffffull}, - {23, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, - {23, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffefffffffeull}, - {23, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {23, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xe3b411de678ee5c0ull, 0x8000000000000000ull, 0x923456789abcdef0ull}, - {23, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0x0ull}, - {23, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, - {23, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, - {23, 0xfffffffeffffffffull, 0x1ull, 0xe4e171d83c299defull, 0xfffffffeffffffffull, 0xfffffffefffffffeull}, - {23, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, - {23, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, - {23, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffefffffffeull}, - {23, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xaa23249b400d9ebdull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {23, 0xc1f06114a97ad060ull, 0x679f7fe64c950a15ull, 0xc1f06114a97ad060ull, 0xc1f06114a97ad060ull, 0xa66f1ef2e5efda75ull}, - {23, 0x59bdb13042bcfe48ull, 0x1669010b3ea903bbull, 0x59bdb13040bcfe48ull, 0x59bdb13042bcfe48ull, 0x4fd4b03b7c15fdf3ull}, - {23, 0x701d03a9ffffffffull, 0xf96a081600000001ull, 0x701d03a9ffffffffull, 0x701d03a9ffffffffull, 0x89770bbffffffffeull}, - {23, 0x9b817862ffffffffull, 0x3c3e50fe00000001ull, 0x9b817862dfffffffull, 0x9b817862ffffffffull, 0xa7bf289cfffffffeull}, }}; class GuestBlock { @@ -539,12 +339,6 @@ std::vector Initial() { Put(image, op * OpStride + tid * 8u, RowOf(op, tid).memory, OpBytes(op)); } } - for (std::uint32_t tid = 0; tid < Threads; ++tid) { - Put(image, Special + tid * 8u, 0x1000u * tid, 4); - Put(image, Special + Threads * 8u + tid * 8u, 0xa5a5a5a5u ^ tid, 4); - Put(image, Special + Threads * 24u + tid * 8u, 0x0f0f0f00u + tid, 4); - } - Put(image, Special + Threads * 16u + 4u, 0x11u, 4); return image; } @@ -555,14 +349,6 @@ std::vector Expected() { Put(image, op * OpStride + tid * 8u, RowOf(op, tid).final, OpBytes(op)); } } - std::uint32_t total = 0x11u; - for (std::uint32_t tid = 0; tid < Threads; ++tid) { - Put(image, Special + tid * 8u, 0x1000u * tid + tid + 1u, 4); - Put(image, Special + Threads * 8u + tid * 8u, (0xa5a5a5a5u ^ tid) ^ (tid << 8u), 4); - if (tid < 16u) Put(image, Special + Threads * 24u + tid * 8u, (0x0f0f0f00u + tid) | 0xf0u, 4); - total += (tid + 1u) << 8u; - } - Put(image, Special + Threads * 16u + 4u, total, 4); return image; } @@ -625,18 +411,6 @@ void RunAtomics(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_ Require(returned == row.returned, wave + "op " + std::to_string(op) + " lane " + std::to_string(tid) + " returned " + Hex(returned) + ", expected " + Hex(row.returned)); } } - std::vector> order; - for (std::uint32_t tid = 0; tid < Threads; ++tid) { - const auto flat = Output[(OpCount * Threads + tid) * 2u]; - Require(flat == (0xa5a5a5a5u ^ tid), wave + "flat xor returned " + Hex(flat) + " to lane " + std::to_string(tid)); - order.emplace_back(Output[((OpCount + 1u) * Threads + tid) * 2u], tid); - } - std::sort(order.begin(), order.end()); - std::uint32_t running = 0x11u; - for (const auto& [returned, tid] : order) { - Require(returned == running, wave + "contended add returned " + Hex(returned) + " to lane " + std::to_string(tid) + ", expected " + Hex(running)); - running += (tid + 1u) << 8u; - } const auto expected = Expected(); for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { const auto actual = guest.Data()[offset]; @@ -644,15 +418,6 @@ void RunAtomics(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_ } } -void RunReadOnly(AgcDriver::VulkanDevice& device, GuestBlock& guest) { - const auto initial = Initial(); - std::memcpy(guest.Data(), initial.data(), BlockBytes); - FillInput(); - Dispatch(device, 32, guest.Data()); - for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { - Require(guest.Data()[offset] == initial[offset], "global atomics: an atomic into a read-only range changed byte " + std::to_string(offset)); - } -} } @@ -661,10 +426,7 @@ int main() { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; GuestBlock writable(true); - GuestBlock readOnly(false); RunAtomics(*device, writable, 32); - RunAtomics(*device, writable, 64); - RunReadOnly(*device, readOnly); std::puts("global atomics tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics64.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics64.cpp new file mode 100644 index 000000000..541e26eb0 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics64.cpp @@ -0,0 +1,400 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t RowsPerOp = 16; +constexpr std::uint32_t OpCount = 11; +constexpr std::uint32_t NarrowOps = 0; +constexpr std::uint32_t OpStride = 0x200; +constexpr std::uint32_t Special = OpCount * OpStride; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint8_t Fill = 0xcd; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array GlobalAtomicsCode{ + 0x34020083, 0x34040084, 0x340a0083, 0x7e2802ff, 0x05e471e1, 0x7e2a02ff, 0x05e471e1, 0x4a060280, + 0x4a080480, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0a80, 0xdd418000, 0x14080a03, 0xbf8c3f70, + 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000200, 0x4a0804ff, 0x00000400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00000200, 0xdd458000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, + 0x4a0602ff, 0x00000400, 0x4a0804ff, 0x00000800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00000400, 0xdd498000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000600, + 0x4a0804ff, 0x00000c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000600, 0xdd4d8000, + 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000800, 0x4a0804ff, 0x00001000, + 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000800, 0xdd558000, 0x14080a03, 0xbf8c3f70, + 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000a00, 0x4a0804ff, 0x00001400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00000a00, 0xdd598000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, + 0x4a0602ff, 0x00000c00, 0x4a0804ff, 0x00001800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00000c00, 0xdd5d8000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000e00, + 0x4a0804ff, 0x00001c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000e00, 0xdd618000, + 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00001000, 0x4a0804ff, 0x00002000, + 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00001000, 0xdd658000, 0x14080a03, 0xbf8c3f70, + 0xe0741000, 0x80011406, 0x4a0602ff, 0x00001200, 0x4a0804ff, 0x00002400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00001200, 0xdd698000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, + 0x4a0602ff, 0x00001400, 0x4a0804ff, 0x00002800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00001400, 0xdd6d8000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0xbf810000 +}; + +struct Row { + std::uint32_t op; + std::uint64_t memory; + std::uint64_t data; + std::uint64_t comparator; + std::uint64_t returned; + std::uint64_t final; +}; + +constexpr std::array Rows{{ + {0, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {0, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, + {0, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {0, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xb210ee8d643f54e6ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, + {0, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {0, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, + {0, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, + {0, 0xfffffffeffffffffull, 0x1ull, 0xed5fb21be38948d7ull, 0xfffffffeffffffffull, 0x1ull}, + {0, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, + {0, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {0, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {0, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xb346daefa27ab170ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {0, 0x53bb9629497dfb3full, 0xa4abfed10ad2a242ull, 0x53bb9629497dfb3full, 0x53bb9629497dfb3full, 0xa4abfed10ad2a242ull}, + {0, 0x6c12816965391baaull, 0xce7cd7b44b45eaf3ull, 0x6c12816967391baaull, 0x6c12816965391baaull, 0xce7cd7b44b45eaf3ull}, + {0, 0xdba996ccffffffffull, 0x10bbe64100000001ull, 0xdba996ccffffffffull, 0xdba996ccffffffffull, 0x10bbe64100000001ull}, + {0, 0x5a7c138cffffffffull, 0x9c15fed00000001ull, 0x5a7c138cdfffffffull, 0x5a7c138cffffffffull, 0x9c15fed00000001ull}, + {1, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {1, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, + {1, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {1, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x166c48c6e9c1b01cull, 0x8000000000000000ull, 0x8000000000000000ull}, + {1, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {1, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {1, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, + {1, 0xfffffffeffffffffull, 0x1ull, 0x866b08e67ea9d02cull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, + {1, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, + {1, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, + {1, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {1, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xa54604e5b9b1311full, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {1, 0xb9a56d03ead2890full, 0x242f2f3b525af861ull, 0xb9a56d03ead2890full, 0xb9a56d03ead2890full, 0x242f2f3b525af861ull}, + {1, 0x65c783e7e26c0130ull, 0x19a0106761c2f186ull, 0x65c783e7e06c0130ull, 0x65c783e7e26c0130ull, 0x65c783e7e26c0130ull}, + {1, 0x7bebc240ffffffffull, 0x4da1ba4b00000001ull, 0x7bebc240ffffffffull, 0x7bebc240ffffffffull, 0x4da1ba4b00000001ull}, + {1, 0x57e3536bffffffffull, 0xd8d033bd00000001ull, 0x57e3536bdfffffffull, 0x57e3536bffffffffull, 0x57e3536bffffffffull}, + {2, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {2, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xffffffff00000000ull}, + {2, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {2, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd4db9193123f6ebaull, 0x8000000000000000ull, 0x923456789abcdef0ull}, + {2, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xfffffffffffffffeull}, + {2, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {2, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, + {2, 0xfffffffeffffffffull, 0x1ull, 0x25fd502c82facbc0ull, 0xfffffffeffffffffull, 0xffffffff00000000ull}, + {2, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, + {2, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {2, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xffffffff00000000ull}, + {2, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf9124f0e4aae24c5ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {2, 0xc3e86469baffc1dull, 0xc22f0e9d6d69338dull, 0xc3e86469baffc1dull, 0xc3e86469baffc1dull, 0xce6d94e409192faaull}, + {2, 0xe499e6544c91e53ull, 0x4647904bf55b8053ull, 0xe499e6546c91e53ull, 0xe499e6544c91e53ull, 0x54912eb13a249ea6ull}, + {2, 0x7a4a48e2ffffffffull, 0x7ccc4b5300000001ull, 0x7a4a48e2ffffffffull, 0x7a4a48e2ffffffffull, 0xf716943600000000ull}, + {2, 0xa925179dffffffffull, 0xb2b1ac1800000001ull, 0xa925179ddfffffffull, 0xa925179dffffffffull, 0x5bd6c3b600000000ull}, + {3, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x8000000000000001ull}, + {3, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x100000002ull}, + {3, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {3, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x6835bf86a066a97eull, 0x8000000000000000ull, 0x6dcba98765432110ull}, + {3, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0x0ull}, + {3, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {3, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ull}, + {3, 0xfffffffeffffffffull, 0x1ull, 0xbcbca7d9810b83c1ull, 0xfffffffeffffffffull, 0xfffffffefffffffeull}, + {3, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456779abcdef0ull}, + {3, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x8000000000000001ull}, + {3, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x100000002ull}, + {3, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x663c553a9a16e35ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {3, 0x63fbf4e8e453a4c5ull, 0xb7a15652045c43f6ull, 0x63fbf4e8e453a4c5ull, 0x63fbf4e8e453a4c5ull, 0xac5a9e96dff760cfull}, + {3, 0x1603b25645bdd8aaull, 0x1f87b96dd545e1f1ull, 0x1603b25647bdd8aaull, 0x1603b25645bdd8aaull, 0xf67bf8e87077f6b9ull}, + {3, 0x757fd168ffffffffull, 0x2356488500000001ull, 0x757fd168ffffffffull, 0x757fd168ffffffffull, 0x522988e3fffffffeull}, + {3, 0x752b2a35ffffffffull, 0x869e617c00000001ull, 0x752b2a35dfffffffull, 0x752b2a35ffffffffull, 0xee8cc8b9fffffffeull}, + {4, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, + {4, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, + {4, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {4, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd286c038892fd9d4ull, 0x8000000000000000ull, 0x8000000000000000ull}, + {4, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {4, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, + {4, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, + {4, 0xfffffffeffffffffull, 0x1ull, 0x80b34c5cb5907e40ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, + {4, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, + {4, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, + {4, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {4, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xe7cd1f2260293767ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {4, 0x8372abbda96ba8eeull, 0x5e71be928ba29363ull, 0x8372abbda96ba8eeull, 0x8372abbda96ba8eeull, 0x8372abbda96ba8eeull}, + {4, 0xce77c3cdcc677910ull, 0xdf44f6f934647f0eull, 0xce77c3cdce677910ull, 0xce77c3cdcc677910ull, 0xce77c3cdcc677910ull}, + {4, 0x9b3edbb1ffffffffull, 0xf4f16cf100000001ull, 0x9b3edbb1ffffffffull, 0x9b3edbb1ffffffffull, 0x9b3edbb1ffffffffull}, + {4, 0xadf3fbf0ffffffffull, 0x8b29592900000001ull, 0xadf3fbf0dfffffffull, 0xadf3fbf0ffffffffull, 0x8b29592900000001ull}, + {5, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, + {5, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, + {5, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {5, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xff63cf82d01938e9ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, + {5, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {5, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, + {5, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, + {5, 0xfffffffeffffffffull, 0x1ull, 0x2a88358605f1254ull, 0xfffffffeffffffffull, 0x1ull}, + {5, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, + {5, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, + {5, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, + {5, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf078418740b4f6d3ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {5, 0x8d4a240ceafb92f2ull, 0xd789c424b5282b1dull, 0x8d4a240ceafb92f2ull, 0x8d4a240ceafb92f2ull, 0x8d4a240ceafb92f2ull}, + {5, 0x64b3620ed7a9434full, 0x930b9894c3741be0ull, 0x64b3620ed5a9434full, 0x64b3620ed7a9434full, 0x64b3620ed7a9434full}, + {5, 0x62a12f63ffffffffull, 0x4a1df58400000001ull, 0x62a12f63ffffffffull, 0x62a12f63ffffffffull, 0x4a1df58400000001ull}, + {5, 0xd0826a0cffffffffull, 0x14fe08aa00000001ull, 0xd0826a0cdfffffffull, 0xd0826a0cffffffffull, 0x14fe08aa00000001ull}, + {6, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {6, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, + {6, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {6, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x35f6b71611838017ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, + {6, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {6, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {6, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x100000000ull}, + {6, 0xfffffffeffffffffull, 0x1ull, 0x884dcfec36f43da6ull, 0xfffffffeffffffffull, 0x1ull}, + {6, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull}, + {6, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {6, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, + {6, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x97e21514d87b299dull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {6, 0x7ae9f54bc19e56ddull, 0x4a6ac7aeb830ed76ull, 0x7ae9f54bc19e56ddull, 0x7ae9f54bc19e56ddull, 0x7ae9f54bc19e56ddull}, + {6, 0x5b1c8aa1eb9a258cull, 0xf4a0ea8b3badac72ull, 0x5b1c8aa1e99a258cull, 0x5b1c8aa1eb9a258cull, 0x5b1c8aa1eb9a258cull}, + {6, 0x869f35deffffffffull, 0xdeffa68100000001ull, 0x869f35deffffffffull, 0x869f35deffffffffull, 0xdeffa68100000001ull}, + {6, 0x5752b4dfffffffffull, 0x2a51ee7e00000001ull, 0x5752b4dfdfffffffull, 0x5752b4dfffffffffull, 0x5752b4dfffffffffull}, + {7, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {7, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, + {7, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {7, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd316f470e2d8993bull, 0x8000000000000000ull, 0x8000000000000000ull}, + {7, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {7, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {7, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x100000000ull}, + {7, 0xfffffffeffffffffull, 0x1ull, 0xfc2b7ace1072e7b9ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, + {7, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull}, + {7, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {7, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {7, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x142c1e683d47a123ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {7, 0x5ea0e74f0d30cb76ull, 0xecd62560f1407a13ull, 0x5ea0e74f0d30cb76ull, 0x5ea0e74f0d30cb76ull, 0xecd62560f1407a13ull}, + {7, 0x6f8664b99db31c8bull, 0x77417f9d1c387d00ull, 0x6f8664b99fb31c8bull, 0x6f8664b99db31c8bull, 0x77417f9d1c387d00ull}, + {7, 0x1e0ce078ffffffffull, 0xc130cf3c00000001ull, 0x1e0ce078ffffffffull, 0x1e0ce078ffffffffull, 0xc130cf3c00000001ull}, + {7, 0xd160d889ffffffffull, 0x2b456f6800000001ull, 0xd160d889dfffffffull, 0xd160d889ffffffffull, 0xd160d889ffffffffull}, + {8, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, + {8, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, + {8, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x0ull}, + {8, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x691beca72ac31615ull, 0x8000000000000000ull, 0x0ull}, + {8, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {8, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, + {8, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x0ull}, + {8, 0xfffffffeffffffffull, 0x1ull, 0xc89594109911783aull, 0xfffffffeffffffffull, 0x1ull}, + {8, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x0ull}, + {8, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, + {8, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, + {8, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xa69bfe93d6efb4ddull, 0x7fffffffffffffffull, 0x0ull}, + {8, 0xd5e7cc579d21963aull, 0x93826a1ee8ab2ed1ull, 0xd5e7cc579d21963aull, 0xd5e7cc579d21963aull, 0x9182481688210610ull}, + {8, 0x657c382c4e1cfcddull, 0x837a0dc63ac35f32ull, 0x657c382c4c1cfcddull, 0x657c382c4e1cfcddull, 0x17808040a005c10ull}, + {8, 0x7552c284ffffffffull, 0x995c2cf200000001ull, 0x7552c284ffffffffull, 0x7552c284ffffffffull, 0x1150008000000001ull}, + {8, 0xe20d4f8dffffffffull, 0x4864cdbc00000001ull, 0xe20d4f8ddfffffffull, 0xe20d4f8dffffffffull, 0x40044d8c00000001ull}, + {9, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {9, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, + {9, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {9, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xbad3dfdbb007d4ecull, 0x8000000000000000ull, 0x923456789abcdef0ull}, + {9, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {9, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {9, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, + {9, 0xfffffffeffffffffull, 0x1ull, 0xedecea3c6fa0d23full, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, + {9, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, + {9, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {9, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {9, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf7926675f45d1971ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {9, 0x360443a82569e20cull, 0x3800d9ff899231faull, 0x360443a82569e20cull, 0x360443a82569e20cull, 0x3e04dbffadfbf3feull}, + {9, 0xc265df77a6c71a0dull, 0x5e94d6ccc50557f8ull, 0xc265df77a4c71a0dull, 0xc265df77a6c71a0dull, 0xdef5dfffe7c75ffdull}, + {9, 0x41a90d6bffffffffull, 0x4bb3092400000001ull, 0x41a90d6bffffffffull, 0x41a90d6bffffffffull, 0x4bbb0d6fffffffffull}, + {9, 0xc54e9269ffffffffull, 0xf536f34000000001ull, 0xc54e9269dfffffffull, 0xc54e9269ffffffffull, 0xf57ef369ffffffffull}, + {10, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {10, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffefffffffeull}, + {10, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {10, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xe3b411de678ee5c0ull, 0x8000000000000000ull, 0x923456789abcdef0ull}, + {10, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0x0ull}, + {10, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {10, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, + {10, 0xfffffffeffffffffull, 0x1ull, 0xe4e171d83c299defull, 0xfffffffeffffffffull, 0xfffffffefffffffeull}, + {10, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, + {10, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {10, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffefffffffeull}, + {10, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xaa23249b400d9ebdull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {10, 0xc1f06114a97ad060ull, 0x679f7fe64c950a15ull, 0xc1f06114a97ad060ull, 0xc1f06114a97ad060ull, 0xa66f1ef2e5efda75ull}, + {10, 0x59bdb13042bcfe48ull, 0x1669010b3ea903bbull, 0x59bdb13040bcfe48ull, 0x59bdb13042bcfe48ull, 0x4fd4b03b7c15fdf3ull}, + {10, 0x701d03a9ffffffffull, 0xf96a081600000001ull, 0x701d03a9ffffffffull, 0x701d03a9ffffffffull, 0x89770bbffffffffeull}, + {10, 0x9b817862ffffffffull, 0x3c3e50fe00000001ull, 0x9b817862dfffffffull, 0x9b817862ffffffffull, 0xa7bf289cfffffffeull}, +}}; + +class GuestBlock { +public: + explicit GuestBlock(bool writable) { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "global atomics: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, writable); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + const std::uint8_t* Data() const { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +void Put(std::vector& image, std::uint32_t offset, std::uint64_t value, std::uint32_t bytes) { + for (std::uint32_t byte = 0; byte < bytes; ++byte) image.at(offset + byte) = static_cast(value >> (byte * 8u)); +} + +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::uint64_t value) { + char text[24]; + std::snprintf(text, sizeof(text), "0x%llx", static_cast(value)); + return text; +} + +const Row& RowOf(std::uint32_t op, std::uint32_t tid) { + return Rows.at(op * RowsPerOp + tid % RowsPerOp); +} + +std::uint32_t OpBytes(std::uint32_t op) { + return op < NarrowOps ? 4u : 8u; +} + +std::vector Initial() { + std::vector image(BlockBytes, Fill); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).memory, OpBytes(op)); + } + } + return image; +} + +std::vector Expected() { + auto image = Initial(); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).final, OpBytes(op)); + } + } + return image; +} + +void FillInput() { + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + auto* words = &Input[(op * Threads + tid) * 4u]; + if (OpBytes(op) == 4u) { + words[0] = static_cast(row.data); + words[1] = static_cast(row.comparator); + words[2] = 0u; + words[3] = 0u; + } else { + words[0] = static_cast(row.data); + words[1] = static_cast(row.data >> 32u); + words[2] = static_cast(row.comparator); + words[3] = static_cast(row.comparator >> 32u); + } + } + } +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 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); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::span code(GlobalAtomicsCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 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 RunAtomics(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + FillInput(); + Output.fill(Sentinel); + Dispatch(device, waveSize, guest.Data()); + const auto wave = "global atomics: wave" + std::to_string(waveSize) + " "; + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + const auto* words = &Output[(op * Threads + tid) * 2u]; + const auto returned = OpBytes(op) == 4u ? static_cast(words[0]) : (static_cast(words[1]) << 32u) | words[0]; + Require(returned == row.returned, wave + "op " + std::to_string(op) + " lane " + std::to_string(tid) + " returned " + Hex(returned) + ", expected " + Hex(row.returned)); + } + } + const auto expected = Expected(); + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + const auto actual = guest.Data()[offset]; + Require(actual == expected[offset], wave + "byte " + std::to_string(offset) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[offset])); + } +} + + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock writable(true); + RunAtomics(*device, writable, 32); + std::puts("global atomics 64 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsLanes.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsLanes.cpp new file mode 100644 index 000000000..7629fb5af --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsLanes.cpp @@ -0,0 +1,251 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t RowsPerOp = 16; +constexpr std::uint32_t OpCount = 0; +constexpr std::uint32_t NarrowOps = 0; +constexpr std::uint32_t OpStride = 0x200; +constexpr std::uint32_t Special = OpCount * OpStride; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint8_t Fill = 0xcd; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array GlobalAtomicsCode{ + 0x34020083, 0x34040084, 0x340a0083, 0x7e2802ff, 0x05e471e1, 0x7e2a02ff, 0x05e471e1, 0x4a060280, + 0x7e2802ff, 0x05e471e1, 0x4a140081, 0xdcc88000, 0x00080a03, 0xbf8c3f70, 0xd70f6a1e, 0x02020608, + 0x7e3e0209, 0x503e3e80, 0xd70f6a1e, 0x02023cff, 0x00000200, 0x503e3e80, 0x34140088, 0xdced0000, + 0x147d0a1e, 0xbf8c0070, 0x4a0c0a80, 0xe0701000, 0x80011406, 0x7e0602ff, 0x00000400, 0x4a1414ff, + 0x00000100, 0xdcc98004, 0x14080a03, 0xbf8c3f70, 0x4a0c0aff, 0x00000200, 0xe0701000, 0x80011406, + 0x4a0602ff, 0x00000800, 0x4a1400ff, 0x00000100, 0x7e160280, 0xdd488000, 0x00080a03, 0xbf8c3f70, + 0x7da80090, 0x4a0602ff, 0x00000600, 0x7e1402ff, 0x000000f0, 0xdce88000, 0x00080a03, 0xbf8c3f70, + 0xbf810000 +}; + +struct Row { + std::uint32_t op; + std::uint64_t memory; + std::uint64_t data; + std::uint64_t comparator; + std::uint64_t returned; + std::uint64_t final; +}; + +constexpr std::array Rows{}; + +class GuestBlock { +public: + explicit GuestBlock(bool writable) { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "global atomics: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, writable); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + const std::uint8_t* Data() const { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +void Put(std::vector& image, std::uint32_t offset, std::uint64_t value, std::uint32_t bytes) { + for (std::uint32_t byte = 0; byte < bytes; ++byte) image.at(offset + byte) = static_cast(value >> (byte * 8u)); +} + +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::uint64_t value) { + char text[24]; + std::snprintf(text, sizeof(text), "0x%llx", static_cast(value)); + return text; +} + +const Row& RowOf(std::uint32_t op, std::uint32_t tid) { + return Rows.at(op * RowsPerOp + tid % RowsPerOp); +} + +std::uint32_t OpBytes(std::uint32_t op) { + return op < NarrowOps ? 4u : 8u; +} + +std::vector Initial() { + std::vector image(BlockBytes, Fill); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).memory, OpBytes(op)); + } + } + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, Special + tid * 8u, 0x1000u * tid, 4); + Put(image, Special + Threads * 8u + tid * 8u, 0xa5a5a5a5u ^ tid, 4); + Put(image, Special + Threads * 24u + tid * 8u, 0x0f0f0f00u + tid, 4); + Put(image, Special + Threads * 32u + tid * 8u, 0x7fffffffffffff00ull + tid, 8); + } + Put(image, Special + Threads * 16u + 4u, 0x11u, 4); + return image; +} + +std::vector Expected() { + auto image = Initial(); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).final, OpBytes(op)); + } + } + std::uint32_t total = 0x11u; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, Special + tid * 8u, 0x1000u * tid + tid + 1u, 4); + Put(image, Special + Threads * 8u + tid * 8u, (0xa5a5a5a5u ^ tid) ^ (tid << 8u), 4); + if (tid < 16u) Put(image, Special + Threads * 24u + tid * 8u, (0x0f0f0f00u + tid) | 0xf0u, 4); + Put(image, Special + Threads * 32u + tid * 8u, 0x7fffffffffffff00ull + tid + 0x100u + tid, 8); + total += (tid + 1u) << 8u; + } + Put(image, Special + Threads * 16u + 4u, total, 4); + return image; +} + +void FillInput() { + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + auto* words = &Input[(op * Threads + tid) * 4u]; + if (OpBytes(op) == 4u) { + words[0] = static_cast(row.data); + words[1] = static_cast(row.comparator); + words[2] = 0u; + words[3] = 0u; + } else { + words[0] = static_cast(row.data); + words[1] = static_cast(row.data >> 32u); + words[2] = static_cast(row.comparator); + words[3] = static_cast(row.comparator >> 32u); + } + } + } +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 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); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::span code(GlobalAtomicsCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 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 RunAtomics(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + FillInput(); + Output.fill(Sentinel); + Dispatch(device, waveSize, guest.Data()); + const auto wave = "global atomics: wave" + std::to_string(waveSize) + " "; + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + const auto* words = &Output[(op * Threads + tid) * 2u]; + const auto returned = OpBytes(op) == 4u ? static_cast(words[0]) : (static_cast(words[1]) << 32u) | words[0]; + Require(returned == row.returned, wave + "op " + std::to_string(op) + " lane " + std::to_string(tid) + " returned " + Hex(returned) + ", expected " + Hex(row.returned)); + } + } + std::vector> order; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto flat = Output[(OpCount * Threads + tid) * 2u]; + Require(flat == (0xa5a5a5a5u ^ tid), wave + "flat xor returned " + Hex(flat) + " to lane " + std::to_string(tid)); + order.emplace_back(Output[((OpCount + 1u) * Threads + tid) * 2u], tid); + } + std::sort(order.begin(), order.end()); + std::uint32_t running = 0x11u; + for (const auto& [returned, tid] : order) { + Require(returned == running, wave + "contended add returned " + Hex(returned) + " to lane " + std::to_string(tid) + ", expected " + Hex(running)); + running += (tid + 1u) << 8u; + } + const auto expected = Expected(); + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + const auto actual = guest.Data()[offset]; + Require(actual == expected[offset], wave + "byte " + std::to_string(offset) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[offset])); + } +} + +void RunReadOnly(AgcDriver::VulkanDevice& device, GuestBlock& guest) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + FillInput(); + Dispatch(device, 32, guest.Data()); + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + Require(guest.Data()[offset] == initial[offset], "global atomics: an atomic into a read-only range changed byte " + std::to_string(offset)); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock writable(true); + GuestBlock readOnly(false); + RunAtomics(*device, writable, 32); + RunAtomics(*device, writable, 64); + RunReadOnly(*device, readOnly); + std::puts("global atomics lanes tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoads.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoads.cpp new file mode 100644 index 000000000..ad91fd604 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoads.cpp @@ -0,0 +1,182 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#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 Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc208800, 0x0a080020, 0xdc2487ff, + 0x0b080020, 0xdc288ffd, 0x0c080020, 0xdc2c8001, 0x0d7d0022, 0xdc308fff, 0x0e080020, 0xdc308000, + 0x18080021, 0xdc208800, 0x197d0022, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, + 0x80010e02, 0xe0781020, 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x000000d2, 0x00000029, 0x0000ebf6, 0x00004397, 0x4397357a, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x6526f2d2, 0x000000d2}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x000000f2, 0xffffffdc, 0x00007aeb, 0xffff9d43, 0x9d439735, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xa66526f2, 0x000000f2}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x00000026, 0xfffffff3, 0x0000357a, 0xffff819d, 0x819d4397, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x0ca66526, 0x00000026}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x00000065, 0x0000003d, 0x00009735, 0x00003c81, 0x3c819d43, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x120ca665, 0x00000065}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x00000077, 0x00000072, 0x00007ea7, 0x00003154, 0x31541eca, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xa2731cb1, 0x00000077}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x00000049, 0xffffffad, 0x000034f9, 0x00001eca, 0x1eca7ea7, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x1cb172ff, 0x00000049}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x000000c6, 0xffffff91, 0x00002075, 0xffffb0c1, 0xb0c19888, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xdc295aec, 0x000000c6}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x000000f1, 0x00000054, 0x00001873, 0xffffdf25, 0xdf25a310, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x7dee2657, 0x000000f1}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x000000a8, 0x0000002d, 0x0000a25c, 0xffff9c05, 0x9c0507a5, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xf610a667, 0x000000a8}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x000000d7, 0xffffffb6, 0x00004159, 0xffffb70c, 0xb70c48ee, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x3e052289, 0x000000d7}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x0000003b, 0x00000015, 0x00007b82, 0x000024e5, 0x24e5c17c, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xdebaea1d, 0x0000003b}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x00000021, 0x00000071, 0x00007769, 0xffff8cc2, 0x8cc24bff, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x7f9859b1, 0x00000021}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x00000011, 0x00000016, 0x0000b53e, 0x00001018, 0x10187326, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x224cb259, 0x00000011}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x00000065, 0xffffff90, 0x0000764b, 0xffff9a41, 0x9a412405, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x99571c29, 0x00000065}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x00000053, 0x00000067, 0x00004431, 0xffffd3b4, 0xd3b4c14d, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x034ab01d, 0x00000053}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x000000fd, 0x00000072, 0x0000d4a3, 0x000069d0, 0x69d0cbd1, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x15602efe, 0x000000fd}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x00000086, 0xfffffffe, 0x000070d6, 0xffff8e1d, 0x8e1de1ab, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x65d656c0, 0x00000086}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x00000019, 0x00000057, 0x00005567, 0xffffdf45, 0xdf45fb80, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xd14b0cf5, 0x00000019}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x0000009e, 0xffffff91, 0x000017c8, 0xffff8ce4, 0x8ce4e6ab, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x1d426570, 0x0000009e}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x00000065, 0xffffff90, 0x0000764b, 0xffff9a41, 0x9a412405, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x79fec2ec, 0x00000065}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x000000a2, 0x0000001c, 0x00005a25, 0xffffd1d4, 0xd1d4a3aa, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x524da73e, 0x000000a2}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x0000008c, 0x00000017, 0x00006d7c, 0xffffa815, 0xa815a39f, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x660a3790, 0x0000008c}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x00000047, 0x0000000d, 0x0000d463, 0x00001bf9, 0x1bf9e87d, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xaedff3c7, 0x00000047}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x000000c7, 0xffffffdb, 0x0000b20a, 0x0000100b, 0x100bbc11, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x92b36eda, 0x000000c7}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x00000025, 0x0000002b, 0x00006fd8, 0x00001477, 0x1477e9be, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x63779354, 0x00000025}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x000000c2, 0xffffffd1, 0x0000e2a4, 0x00003181, 0x31813cb2, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x764984b5, 0x000000c2}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x00000049, 0xfffffff9, 0x00001856, 0x0000575d, 0x575da8dc, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x1d9033df, 0x00000049}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "global loads: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +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%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {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 Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "global loads: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("global loads tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX2X3.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX2X3.cpp new file mode 100644 index 000000000..49ee77d74 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX2X3.cpp @@ -0,0 +1,181 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#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 Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc348ffa, 0x0f7d0022, 0xdc3c8003, + 0x11080020, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, + 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xf6804124, 0x97357aeb, 0x513c819d, 0x2e32095f, 0xefa22967, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xebf68041, 0x4397357a, 0x5f513c81, 0x672e3209, 0x47efa229, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x7aebf680, 0x9d439735, 0x095f513c, 0x29672e32, 0xad47efa2, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x357aebf6, 0x819d4397, 0x32095f51, 0xa229672e, 0x53ad47ef, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xa734f958, 0x541eca7e, 0x204cb651, 0x16a2f996, 0x660affc8, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xf95881df, 0xca7ea734, 0xb6513154, 0xf996204c, 0xffc816a2, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x75f13ba1, 0xc1988820, 0x08a59ac0, 0x85539459, 0x73d7de27, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x7326b53e, 0x25a31018, 0x6645c8aa, 0x02f743cf, 0x8fd2a50e, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5ca32e13, 0x0507a5a2, 0xeebcae0b, 0xfbcf54ff, 0x7c7b8218, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5905426c, 0x0c48ee41, 0x52e95ec2, 0x809af6c4, 0x9e49d979, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x8218fbcf, 0xe5c17c7b, 0x6ab73608, 0x185620a0, 0x575da8dc, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x69d0cbd1, 0xc24bff77, 0xd5c720a6, 0xd9c85a78, 0x60b4443a, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x3ecdef03, 0x187326b5, 0xaadf25a3, 0xcf6645c8, 0x0e02f743, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x4ba1fb80, 0x41240576, 0xbd0167bc, 0x6edae83e, 0xbf9692b3, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x31813cb2, 0xb4c14d44, 0xb9279ed7, 0xb93ff927, 0x59859a53, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xa3aa5a25, 0xd0cbd1d4, 0xc24bff77, 0xc720a68c, 0xc85a78d5, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xd6bd1487, 0x1de1ab70, 0x3b6b431e, 0x7e7923d3, 0x777b0e8e, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x677ed35a, 0x45fb8055, 0x34f95881, 0x1eca7ea7, 0xb6513154, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xc8fa9bb0, 0xe4e6ab17, 0x4cd6a2c9, 0x36b17e32, 0xd6bd1487, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x4ba1fb80, 0x41240576, 0xbd0167bc, 0x6edae83e, 0xbf9692b3, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x25f6d039, 0xd4a3aa5a, 0x7769d0cb, 0x8cc24bff, 0xd5c720a6, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x7c7a524d, 0x15a39f6d, 0xed275ce5, 0x0e62da4d, 0xe790d315, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x6302c753, 0xf9e87dd4, 0x378b4009, 0xf3c8b729, 0x598433f0, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x0aa2a4b1, 0x0bbc11b2, 0x5dc397db, 0xd1f4d133, 0x10aae488, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xd8600189, 0x77e9be6f, 0x2c73a7bd, 0x421aff39, 0x1f09a43b, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xa4765520, 0x813cb2e2, 0xb4c14d44, 0x279ed7d3, 0x3ff927b9, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5620a06a, 0x5da8dc18, 0x8d86c779, 0x4835e9c5, 0x406c576f, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "global loads x2 x3: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +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%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {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 Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "global loads x2 x3: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("global loads x2 x3 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX4.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX4.cpp new file mode 100644 index 000000000..d0bb6392d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX4.cpp @@ -0,0 +1,181 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#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 Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc3887f8, 0x147d0022, 0xbf8c0070, + 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, 0x80011202, 0xe0781030, + 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x6595f862, 0x295aec03, 0x523df3dc, 0x457d538e, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x036595f8, 0xdc295aec, 0x8e523df3, 0x48457d53, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xec036595, 0xf3dc295a, 0x538e523d, 0xe048457d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5aec0365, 0x3df3dc29, 0x7d538e52, 0xfce04845, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x30d907a2, 0x72bdad38, 0xa92515b0, 0x948e5f94, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x07a2e287, 0xad3830d9, 0x15b072bd, 0x5f94a925, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x62c3c5bc, 0x91b7a807, 0x63034f25, 0x2db116b5, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x3b946093, 0x54ebd216, 0x9bd7f852, 0x53f53ed6, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x02701056, 0x2dd3f4aa, 0x602ab9e7, 0xcd71014b, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x693895b7, 0xb6e0664f, 0xdeca057c, 0x2b2c163e, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x59ac90cd, 0x15783291, 0x7584528a, 0xe888f86d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xf6724fb3, 0x7153291c, 0x4a5c9177, 0x0b128e2b, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x936f25cd, 0x163b9460, 0x5254ebd2, 0xd69bd7f8, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xa14f8d0a, 0x902df1a3, 0x7f913ad6, 0xec4185b7, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xb7d12049, 0x675b4866, 0xc676e8d8, 0xdca0e1a0, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x324d47e5, 0x724fb31c, 0x53291cf6, 0x5c917771, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x6c7e37f1, 0xfe28bb7f, 0xa0949a4c, 0x3ab02414, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xa5a87b5e, 0x57dd4c23, 0x07a2e287, 0xad3830d9, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x3d40d4d6, 0x911b0a0e, 0xd1eba0cd, 0xe767b4ff, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xa14f8d0a, 0x902df1a3, 0x7f913ad6, 0xec4185b7, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xe50f0b34, 0x1c324d47, 0xf6724fb3, 0x7153291c, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x528bc830, 0x17eb5b02, 0x9da049a4, 0xb3a7bbef, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xa0d6d4b0, 0x0d8273a0, 0x6d34b2ad, 0xedd883ac, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x082b465a, 0xdb19a044, 0x1095f2e7, 0x9f733159, 0x5a5a000e, 0x5a5a000f}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x87b15796, 0x2b17d0bf, 0xfa5d515c, 0x834fd313, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x164fb710, 0xd1204939, 0x5b4866b7, 0x76e8d867, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xe888f86d, 0xf927dda0, 0x9e9bf666, 0x0fcfcf14, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "global loads x4: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +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%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {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 Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "global loads x4: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("global loads x4 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAddressDimension.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAddressDimension.cpp new file mode 100644 index 000000000..0910f962a --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAddressDimension.cpp @@ -0,0 +1,188 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#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 Words = 32; +constexpr std::uint32_t Size = 16; +constexpr std::uint32_t Levels = 5; +constexpr std::uint32_t Format8888UNorm = 56; +constexpr std::uint32_t Type2D = 9; +constexpr std::uint32_t ArrayInput = 0; +constexpr std::uint32_t ArrayResult = 8; +constexpr std::uint32_t PlainInput = 16; +constexpr std::uint32_t PlainResult = 24; +alignas(256) std::array Buffer{}; +alignas(256) std::array Texels{}; + +alignas(256) constexpr std::array Code{ + 0x34020087, 0xe0301000, 0x80000201, 0xe0301004, 0x80000301, 0xe0301008, 0x80000401, 0xe030100c, + 0x80000501, 0xe0301010, 0x80000601, 0xe0301014, 0x80000701, 0xe0301018, 0x80000801, 0xe030101c, + 0x80000901, 0xe0301040, 0x80001001, 0xe0301044, 0x80001101, 0xe0301048, 0x80001201, 0xe030104c, + 0x80001301, 0xe0301050, 0x80001401, 0xe0301054, 0x80001501, 0xbf8c3f70, 0xf0900f28, 0x00610c02, + 0xe0701020, 0x80000c01, 0xe0701024, 0x80000d01, 0xe0701028, 0x80000e01, 0xe070102c, 0x80000f01, + 0xf0041f28, 0x00010c06, 0xe0701030, 0x80000c01, 0xe0701034, 0x80000d01, 0xe0701038, 0x80000e01, + 0xe070103c, 0x80000f01, 0xf0900f08, 0x00610c10, 0xe0701060, 0x80000c01, 0xe0701064, 0x80000d01, + 0xe0701068, 0x80000e01, 0xe070106c, 0x80000f01, 0xf0041f08, 0x00010c13, 0xe0701070, 0x80000c01, + 0xe0701074, 0x80000d01, 0xe0701078, 0x80000e01, 0xe070107c, 0x80000f01, 0xbf810000, +}; + +alignas(256) constexpr std::array NarrowCode{ + 0x34020087, 0xe0301040, 0x80001001, 0xe0301044, 0x80001101, 0xbf8c3f70, 0xf0900f00, 0x00610c10, + 0xe0701070, 0x80000c01, 0xbf810000, +}; + +struct Sample { + std::uint32_t level; + std::uint32_t x; + std::uint32_t y; +}; + +Sample SampleOf(std::uint32_t tid) { + return {tid % Levels, (tid * 7u + 3u) % Size, (tid * 5u + 1u) % Size}; +} + +std::uint32_t Bits(float value) { + return std::bit_cast(value); +} + +void FillInput() { + Buffer.fill(0xdeadbeefu); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto sample = SampleOf(tid); + const auto u = Bits((static_cast(sample.x) + 0.5f) / static_cast(Size)); + const auto v = Bits((static_cast(sample.y) + 0.5f) / static_cast(Size)); + const auto lod = Bits(static_cast(sample.level)); + const auto x = sample.x >> sample.level; + const auto y = sample.y >> sample.level; + auto* words = &Buffer[tid * Words]; + const std::array array{u, v, Bits(0.0f), lod, x, y, 0u, sample.level}; + const std::array plain{u, v, lod, x, y, sample.level}; + std::copy(array.begin(), array.end(), words + ArrayInput); + std::copy(plain.begin(), plain.end(), words + PlainInput); + } +} + +void FillTexture() { + const auto mips = AgcDriver::Graphics::ComputeMipLayout(AgcDriver::Graphics::TextureTileMode::kLinear, Format8888UNorm, Size, Size, Levels); + Require(AgcDriver::Graphics::ComputeSurfaceSize(mips, 1) <= Texels.size(), "image address dimension: the mip chain does not fit the texel storage"); + Texels.fill(0xeeu); + for (std::uint32_t level = 0; level < Levels; ++level) { + const auto& mip = mips[level]; + for (std::uint32_t y = 0; y < mip.height; ++y) { + for (std::uint32_t x = 0; x < mip.width; ++x) { + auto* texel = &Texels[mip.tiledOffset + static_cast(y) * mip.pitchBytes + x * 4u]; + texel[0] = static_cast(level); + texel[1] = static_cast(x); + texel[2] = static_cast(y); + texel[3] = 255u; + } + } + } +} + +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::array TextureDescriptor(const void* data) { + const auto address = static_cast(reinterpret_cast(data)); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (((Size - 1u) & 3u) << 30u), + ((Size - 1u) >> 2u) | ((Size - 1u) << 14u), + 0xfacu | ((Levels - 1u) << 16u) | (Type2D << 28u), + 0u, + (Levels - 1u) << 4u, + 0u, + 0u, + }; +} + +std::array SamplerDescriptor() { + return {0u, 0xfffu << 12u, 1u << 26u, 0u}; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code) { + std::vector userData(16, 0u); + const auto buffer = BufferDescriptor(Buffer.data(), static_cast(Buffer.size() * 4u)); + const auto texture = TextureDescriptor(Texels.data()); + const auto sampler = SamplerDescriptor(); + std::copy(buffer.begin(), buffer.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + std::copy(sampler.begin(), sampler.end(), userData.begin() + 12); + 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() { + constexpr std::array names{"image_sample_l 2d_array", "image_load_mip 2d_array", "image_sample_l 2d", "image_load_mip 2d"}; + constexpr std::array results{ArrayResult, ArrayResult + 4u, PlainResult, PlainResult + 4u}; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto sample = SampleOf(tid); + const std::array expected{sample.level, sample.x >> sample.level, sample.y >> sample.level, 255u}; + for (std::uint32_t index = 0; index < names.size(); ++index) { + for (std::uint32_t component = 0; component < 4u; ++component) { + const float value = std::bit_cast(Buffer[tid * Words + results[index] + component]) * 255.0f; + Require(std::lround(value) == static_cast(expected[component]), std::string(names[index]) + " on a 2D texture: thread " + std::to_string(tid) + " component " + std::to_string(component) + " is " + std::to_string(value) + ", expected " + std::to_string(expected[component])); + } + } + } +} + +void RequireRefused(AgcDriver::VulkanDevice& device) { + std::string refusal; + try { + Run(device, NarrowCode); + } catch (const std::exception& error) { + refusal = error.what(); + } + Require(refusal.find("too few coordinate components") != std::string::npos, "image_sample_l 1d on a 2D texture was not refused: " + refusal); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + FillInput(); + FillTexture(); + Run(*device, Code); + Check(); + RequireRefused(*device); + std::puts("image address dimension tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomics.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomics.cpp index 7eaf3d20f..f27314c9f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomics.cpp @@ -21,6 +21,8 @@ constexpr std::uint32_t Results = 16; constexpr std::uint32_t Operations = 6; constexpr std::uint32_t TextureHeight = 8; constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t Format32SInt = 21; +constexpr std::uint32_t Format32Float = 22; constexpr std::uint32_t Type2D = 9; alignas(256) std::array Input{}; alignas(256) std::array Output{}; @@ -71,24 +73,24 @@ std::array BufferDescriptor(const void* data, std::uint32_t by return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; } -std::array TextureDescriptor(const void* data, std::uint32_t width, std::uint32_t height) { +std::array TextureDescriptor(const void* data, std::uint32_t width, std::uint32_t height, std::uint32_t format) { const auto address = static_cast(reinterpret_cast(data)); return { static_cast(address >> 8u), - static_cast((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((width - 1u) & 3u) << 30u), + static_cast((address >> 40u) & 0xffu) | (format << 20u) | (((width - 1u) & 3u) << 30u), ((width - 1u) >> 2u) | ((height - 1u) << 14u), 0xfacu | (Type2D << 28u), 0u, 0u, 0u, 0u, }; } -void Run(AgcDriver::VulkanDevice& device) { +void Run(AgcDriver::VulkanDevice& device, std::uint32_t format) { Output.fill(0xdeadbeefu); Texels.fill(0xdeadbeefu); std::vector userData(16, 0u); const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); - const auto texture = TextureDescriptor(Texels.data(), Threads, TextureHeight); + const auto texture = TextureDescriptor(Texels.data(), Threads, TextureHeight, format); std::copy(input.begin(), input.end(), userData.begin()); std::copy(output.begin(), output.end(), userData.begin() + 4); std::copy(texture.begin(), texture.end(), userData.begin() + 8); @@ -107,7 +109,7 @@ void Run(AgcDriver::VulkanDevice& device) { device.WaitIdle(); } -void Check() { +void Check(std::uint32_t format) { constexpr std::array names{"image_atomic_sub", "image_atomic_smin", "image_atomic_smax", "image_atomic_inc", "image_atomic_dec", "image_atomic_cmpswap"}; for (std::uint32_t tid = 0; tid < Threads; ++tid) { const auto* in = &Input[tid * Inputs]; @@ -127,8 +129,8 @@ void Check() { for (std::uint32_t j = 0; j < Operations; ++j) { const std::uint32_t returned = Output[tid * Results + 2u * j]; const std::uint32_t stored = Output[tid * Results + 2u * j + 1u]; - Require(returned == a, std::string(names[j]) + ": thread " + std::to_string(tid) + " returned " + std::to_string(returned) + ", expected " + std::to_string(a)); - Require(stored == expected[j], std::string(names[j]) + ": thread " + std::to_string(tid) + " stored " + std::to_string(stored) + ", expected " + std::to_string(expected[j])); + Require(returned == a, std::string(names[j]) + ", format " + std::to_string(format) + ": thread " + std::to_string(tid) + " returned " + std::to_string(returned) + ", expected " + std::to_string(a)); + Require(stored == expected[j], std::string(names[j]) + ", format " + std::to_string(format) + ": thread " + std::to_string(tid) + " stored " + std::to_string(stored) + ", expected " + std::to_string(expected[j])); } } } @@ -140,8 +142,10 @@ int main() { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; FillInput(); - Run(*device); - Check(); + for (const auto format : {Format32UInt, Format32SInt, Format32Float}) { + Run(*device, format); + Check(format); + } std::puts("image atomics tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsBitwise.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsBitwise.cpp new file mode 100644 index 000000000..59fbc3c05 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsBitwise.cpp @@ -0,0 +1,142 @@ +#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 = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Operations = 5; +constexpr std::uint32_t TextureHeight = 8; +constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t Type2D = 9; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; +alignas(4096) std::array Texels{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e100300, 0x7e120280, 0x7e140304, + 0xf03c0108, 0x00020a08, 0x7e160305, 0xf0602108, 0x00020b08, 0x7e180280, 0xf0442108, 0x00020c08, + 0xbf8c3f70, 0x7e28030b, 0x7e2a030c, 0x7e120281, 0x7e140304, 0xf03c0108, 0x00020a08, 0x7e160305, + 0xf0642108, 0x00020b08, 0x7e180280, 0xf0442108, 0x00020c08, 0xbf8c3f70, 0x7e2c030b, 0x7e2e030c, + 0x7e120282, 0x7e140304, 0xf03c0108, 0x00020a08, 0x7e160305, 0xf0682108, 0x00020b08, 0x7e180280, + 0xf0442108, 0x00020c08, 0xbf8c3f70, 0x7e30030b, 0x7e32030c, 0x7e120283, 0x7e140304, 0xf03c0108, + 0x00020a08, 0x7e160305, 0xf0542108, 0x00020b08, 0x7e180280, 0xf0442108, 0x00020c08, 0xbf8c3f70, + 0x7e34030b, 0x7e36030c, 0x7e120284, 0x7e140304, 0xf03c0108, 0x00020a08, 0x7e160305, 0xf05c2108, + 0x00020b08, 0x7e180280, 0xf0442108, 0x00020c08, 0xbf8c3f70, 0x7e38030b, 0x7e3a030c, 0xe0701000, + 0x80011403, 0xe0701004, 0x80011503, 0xe0701008, 0x80011603, 0xe070100c, 0x80011703, 0xe0701010, + 0x80011803, 0xe0701014, 0x80011903, 0xe0701018, 0x80011a03, 0xe070101c, 0x80011b03, 0xe0701020, + 0x80011c03, 0xe0701024, 0x80011d03, 0xbf810000, +}; + +constexpr std::array, 14> Edges{{ + {0x0u, 0x0u}, {0x0u, 0xffffffffu}, {0xffffffffu, 0x0u}, {0xffffffffu, 0xffffffffu}, {0x7fffffffu, 0x80000000u}, {0x80000000u, 0x7fffffffu}, + {0xffffffffu, 0x1u}, {0x1u, 0xffffffffu}, {0xaaaaaaaau, 0x55555555u}, {0xf0f0f0f0u, 0xff00ff00u}, {0x5u, 0x5u}, {0x4u, 0x5u}, + {0x6u, 0x5u}, {0x80000000u, 0x80000000u}, +}}; + +void FillInput() { + std::uint64_t state = 0x9e3779b97f4a7c15ull; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + auto* words = &Input[tid * Inputs]; + for (std::uint32_t j = 0; j < 2u; ++j) { + state = state * 6364136223846793005ull + 1442695040888963407ull; + words[j] = static_cast(state >> 32u); + } + if (tid < Edges.size()) { + words[0] = Edges[tid][0]; + words[1] = Edges[tid][1]; + } + } +} + +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::array TextureDescriptor(const void* data, std::uint32_t width, std::uint32_t height) { + const auto address = static_cast(reinterpret_cast(data)); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((width - 1u) & 3u) << 30u), + ((width - 1u) >> 2u) | ((height - 1u) << 14u), + 0xfacu | (Type2D << 28u), + 0u, 0u, 0u, 0u, + }; +} + +void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + Output.fill(0xdeadbeefu); + Texels.fill(0xdeadbeefu); + std::vector userData(16, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + const auto texture = TextureDescriptor(Texels.data(), Threads, TextureHeight); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + std::copy(texture.begin(), texture.end(), userData.begin() + 8); + 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, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + 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(const char* run) { + constexpr std::array names{"image_atomic_and", "image_atomic_or", "image_atomic_xor", "image_atomic_umin", "image_atomic_umax"}; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto* in = &Input[tid * Inputs]; + const std::uint32_t a = in[0]; + const std::uint32_t b = in[1]; + const std::array expected{a & b, a | b, a ^ b, std::min(a, b), std::max(a, b)}; + for (std::uint32_t j = 0; j < Operations; ++j) { + const std::uint32_t returned = Output[tid * Results + 2u * j]; + const std::uint32_t stored = Output[tid * Results + 2u * j + 1u]; + const auto where = std::string(names[j]) + " " + run + ": thread " + std::to_string(tid); + Require(returned == a, where + " returned " + std::to_string(returned) + ", expected " + std::to_string(a)); + Require(stored == expected[j], where + " stored " + std::to_string(stored) + ", expected " + std::to_string(expected[j])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + FillInput(); + Run(*device, 32, device->Target()); + Check("wave32"); + Run(*device, 64, device->Target()); + Check("wave64"); + Run(*device, 64, device->ComputeTarget(32)); + Check("wave64 split"); + std::puts("image atomics bitwise tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsFloat.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsFloat.cpp index ff553a2b3..1799bebc2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsFloat.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsFloat.cpp @@ -24,6 +24,9 @@ constexpr std::uint32_t Rows = 800; constexpr std::uint32_t TextureWidth = 64; constexpr std::uint32_t TextureHeight = 4; constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t Format32SInt = 21; +constexpr std::uint32_t Format32Float = 22; +constexpr std::uint32_t Format16_16Float = 29; constexpr std::uint32_t Type2D = 9; alignas(256) std::array Buffer{}; alignas(4096) std::array Texels{}; @@ -474,22 +477,22 @@ std::array BufferDescriptor(const void* data, std::uint32_t by return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; } -std::array TextureDescriptor(const void* data) { +std::array TextureDescriptor(const void* data, std::uint32_t format) { const auto address = static_cast(reinterpret_cast(data)); return { static_cast(address >> 8u), - static_cast((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((TextureWidth - 1u) & 3u) << 30u), + static_cast((address >> 40u) & 0xffu) | (format << 20u) | (((TextureWidth - 1u) & 3u) << 30u), ((TextureWidth - 1u) >> 2u) | ((TextureHeight - 1u) << 14u), 0xfacu | (Type2D << 28u), 0u, 0u, 0u, 0u, }; } -void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target, const char* name) { +ShaderRecompiler::RecompileResult Compile(std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target, std::uint32_t format) { const std::span code(Code); std::vector userData(16, 0u); const auto buffer = BufferDescriptor(Buffer.data(), static_cast(Buffer.size() * 4u)); - const auto texture = TextureDescriptor(Texels.data()); + const auto texture = TextureDescriptor(Texels.data(), format); std::copy(buffer.begin(), buffer.end(), userData.begin()); std::copy(texture.begin(), texture.end(), userData.begin() + 4); const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; @@ -501,7 +504,12 @@ void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderRe {0, 0, 0, 128} }; request.useCache = false; - const auto result = ShaderRecompiler::Recompile(request); + return ShaderRecompiler::Recompile(request); +} + +void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target, const char* name, std::uint32_t format) { + const std::span code(Code); + const auto result = Compile(waveSize, target, format); Texels.fill(0xdeadbeefu); for (std::uint32_t first = 0; first < Rows; first += waveSize) { Buffer.fill(0xdeadbeefu); @@ -521,7 +529,7 @@ void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderRe for (std::uint32_t j = 0; j < Checked; ++j) { const auto actual = Buffer[MaxLanes * Inputs + lane * Results + j]; char message[192]; - std::snprintf(message, sizeof(message), "image float atomics %s: row %u %s is 0x%08x, expected 0x%08x", name, row, names[j], actual, expected[j]); + std::snprintf(message, sizeof(message), "image float atomics %s, format %u: row %u %s is 0x%08x, expected 0x%08x", name, format, row, names[j], actual, expected[j]); Require(actual == expected[j], message); } } @@ -534,9 +542,18 @@ int main() { try { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; - Run(*device, 32, device->Target(), "wave32"); - Run(*device, 64, device->Target(), "wave64"); - Run(*device, 64, device->ComputeTarget(32), "wave64 split"); + for (const auto format : {Format32UInt, Format32SInt, Format32Float}) { + Run(*device, 32, device->Target(), "wave32", format); + Run(*device, 64, device->Target(), "wave64", format); + Run(*device, 64, device->ComputeTarget(32), "wave64 split", format); + } + bool refused = false; + try { + static_cast(Compile(32, device->Target(), Format16_16Float)); + } catch (const std::exception& error) { + refused = std::string(error.what()).find("atomic image descriptor uses an unsupported format 29") != std::string::npos; + } + Require(refused, "image float atomics on a 16_16 float surface were not refused"); std::puts("image float atomics tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SdwaFloatSelectors.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaFloatSelectors.cpp new file mode 100644 index 000000000..10863bc09 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaFloatSelectors.cpp @@ -0,0 +1,218 @@ +#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 +#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, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x06140af9, 0x06000604, 0x7e160307, + 0x08160af9, 0x05061504, 0x0a180af9, 0x06142a04, 0x101a0af9, 0x03260404, 0x0e1c0af9, 0x06010604, + 0x1e1e0af9, 0x160a0604, 0x7e200307, 0x20200af9, 0x08061104, 0x20220af9, 0x060d2604, 0x64240af9, + 0x05010004, 0x66260af9, 0x02150d04, 0x7e280307, 0x68280af9, 0x24031304, 0x6a2a0af9, 0x00040604, + 0x7e2c0307, 0x742c0af9, 0x05081404, 0x722e0af9, 0x19050204, 0x06300af9, 0x06052b04, 0x6a320af9, + 0x06020904, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, + 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000000u, 0x40404040u, 0x8b2af49fu}, + {0x00000000u, 0x10663f9du, 0x8b35e743u, 0x7d7d9db4u}, + {0x0008dbf2u, 0x47004700u, 0x3c000000u, 0x80000000u}, + {0x05c21fabu, 0x00470047u, 0x80000000u, 0x47004700u}, + {0x1a0e7cdeu, 0x00000000u, 0xefc2742bu, 0xcd8d3db8u}, + {0xc4de998au, 0xaf1fba34u, 0x0000bc00u, 0xbc00bc00u}, + {0x00000000u, 0x00470047u, 0xc2c2c2c2u, 0xfda0095du}, + {0x3c000000u, 0x80808080u, 0x5960b098u, 0xadd5eff7u}, + {0xbc00bc00u, 0x00000000u, 0x42a502c0u, 0x3c000000u}, + {0x80808080u, 0x1a64a58eu, 0xfe242714u, 0x3c003c00u}, + {0x00000000u, 0x7c007c00u, 0x7f7f7f7fu, 0x3c003c00u}, + {0x00470047u, 0x00000000u, 0x3468787du, 0x7e7103e1u}, + {0x00470047u, 0x80000000u, 0x702bd672u, 0x47004700u}, + {0x00000000u, 0x00800080u, 0x4659ff82u, 0xc2c2c2c2u}, + {0x7f800000u, 0x00000000u, 0x01000100u, 0x1b1c3f27u}, + {0x80000000u, 0x00800080u, 0xbf800000u, 0x829ea49cu}, + {0x00000000u, 0x0000bc00u, 0x3343e632u, 0x13e60ef5u}, + {0x00000000u, 0x00470047u, 0xb5a54b0bu, 0x6769ec45u}, + {0x00000000u, 0x00800080u, 0xa2e7068bu, 0x871c3c9cu}, + {0x00000000u, 0x00800080u, 0xafe5c4b5u, 0x3c003c00u}, + {0x00000000u, 0x00ff00ffu, 0x0000bc00u, 0x40404040u}, + {0x00000000u, 0x00ff00ffu, 0x7f800000u, 0x00ff00ffu}, + {0x00000000u, 0x00ff00ffu, 0xb4ad5866u, 0xe4ea7accu}, + {0x00000000u, 0x00ff00ffu, 0xd03c7dbeu, 0x5de25876u}, + {0x00000000u, 0x01000100u, 0x52eff50fu, 0x00000000u}, + {0x00000000u, 0x182f8e4au, 0xe117e1fau, 0x0000bc00u}, + {0x00000000u, 0x20d8e2bcu, 0xe73074b1u, 0xbf800000u}, + {0x00000000u, 0x2333ad9cu, 0x00800080u, 0x3f800000u}, + {0x00000000u, 0x24f70eabu, 0xfdb88e95u, 0x17e6487au}, + {0x00000000u, 0x2a778095u, 0x00470047u, 0x7c007c00u}, + {0x00000000u, 0x2b119b05u, 0x0000bc00u, 0x4ce5144fu}, + {0x00000000u, 0x2b5d55fdu, 0x03f55439u, 0x7f7f7f7fu} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000000u, 0x0000f49fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x8b2a009fu, 0x00000000u, 0x00000000u, 0x80000000u, 0x002af49fu, 0x00000000u, 0x8b2a0000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x10663f9du, 0x10669db4u, 0xff9d0000u, 0x00000000u, 0x00000000u, 0x90663f9du, 0x7d7d00b4u, 0x10663f9du, 0x00000066u, 0x80660000u, 0x9d7d9db4u, 0x00000000u, 0x7d7d0000u, 0x00000000u, 0x9d000000u, 0x00000000u}, + {0x47004700u, 0x94f20000u, 0x00000000u, 0x00000000u, 0x006dbcbdu, 0xc7004700u, 0x8000f200u, 0x3f800000u, 0x00000000u, 0x80080000u, 0x00000000u, 0x00008000u, 0x80004700u, 0x00080000u, 0x00000000u, 0x00003800u}, + {0x004700f2u, 0x1fab4700u, 0xfff20000u, 0x00000000u, 0x00000000u, 0x80470047u, 0x4700ab00u, 0x00470047u, 0x00000066u, 0x86090000u, 0x42004700u, 0x00000001u, 0x47000047u, 0x00c20000u, 0x09000000u, 0x00000000u}, + {0x000000deu, 0x7cde3db8u, 0xffde0000u, 0x00000000u, 0x00000000u, 0x80000000u, 0xcd8ddeb8u, 0x00001a0eu, 0x0000007cu, 0x9a0e0000u, 0x1a8d3db8u, 0x00007edeu, 0xcd8d0000u, 0x000e0000u, 0x0e000000u, 0x00000000u}, + {0xaf1fba34u, 0x998abc00u, 0x00000000u, 0x0000c158u, 0x80000000u, 0x2f1fba34u, 0xbc003400u, 0x00000000u, 0x0000001fu, 0x44de0000u, 0x3400bc00u, 0x00008000u, 0xbc00af1fu, 0x00de0000u, 0x00000000u, 0xffffac00u}, + {0x00470047u, 0x0047095du, 0x00470000u, 0x00000000u, 0x00000000u, 0x80470047u, 0xfda0475du, 0x00470047u, 0x00000047u, 0x80470000u, 0x47a0095du, 0x00000000u, 0xfda00000u, 0x00000000u, 0x47000000u, 0x00000000u}, + {0x80808080u, 0x0000eff7u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0xadd500f7u, 0x00003c00u, 0x00000080u, 0xbc000000u, 0x44d5eff7u, 0x00000000u, 0xadd58080u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xbc000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x3c000000u, 0x00000000u, 0x000000bcu, 0x3c000000u, 0xbc000000u, 0x00008000u, 0x3c000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x1a64a58eu, 0x9ae43c00u, 0xff8e0000u, 0x00000000u, 0x00000000u, 0x9a64a58eu, 0x3c008000u, 0x1a64a58eu, 0x00000068u, 0x001c0000u, 0x8e003c00u, 0x00008000u, 0x3c001a64u, 0x00800000u, 0x8e000000u, 0x00000300u}, + {0x7c007c00u, 0x7c003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfc007c00u, 0x3c000000u, 0x3f800000u, 0x00000000u, 0x80000000u, 0x00003c00u, 0x00000000u, 0x3c000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000047u, 0x004703e1u, 0x00470000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x7e7147e1u, 0x00000047u, 0x00000000u, 0x80470000u, 0x007103e1u, 0x00000000u, 0x7e710000u, 0x00470000u, 0x47000000u, 0x00000000u}, + {0x00000047u, 0x80474700u, 0x00470000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x47004700u, 0x00000047u, 0x00000000u, 0x80470000u, 0x00004700u, 0x00000000u, 0x47008000u, 0x00470000u, 0x47000000u, 0x00000000u}, + {0x00800080u, 0x0080c2c2u, 0xff800000u, 0x00000000u, 0x00000000u, 0x80800080u, 0xc2c200c2u, 0x00800080u, 0x00000080u, 0x80800000u, 0x80c2c2c2u, 0x00000000u, 0xc2c20000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x00003f27u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x1b1c0027u, 0x00007f80u, 0x00000000u, 0xff800000u, 0x7f1c3f27u, 0x00000000u, 0x1b1c0000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00800080u, 0x0080a49cu, 0xff800000u, 0x00000000u, 0x00000000u, 0x80800080u, 0x829e009cu, 0x00800080u, 0x00000080u, 0x80800000u, 0x009ea49cu, 0x00000000u, 0x829e0000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x0000bc00u, 0x00000ef5u, 0x00000000u, 0x00000000u, 0x00000000u, 0x8000bc00u, 0x13e600f5u, 0x0000bc00u, 0x00000000u, 0x80000000u, 0x00e60ef5u, 0x00000000u, 0x13e60000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00470047u, 0x0047ec45u, 0x00470000u, 0x00000000u, 0x00000000u, 0x80470047u, 0x67694745u, 0x00470047u, 0x00000047u, 0x80470000u, 0x4769ec45u, 0x00000000u, 0x67690000u, 0x00000000u, 0x47000000u, 0x00000000u}, + {0x00800080u, 0x00803c9cu, 0xff800000u, 0x00000000u, 0x00000000u, 0x80800080u, 0x871c009cu, 0x00800080u, 0x00000080u, 0x80800000u, 0x801c3c9cu, 0x00000000u, 0x871c0000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00800080u, 0x00803c00u, 0xff800000u, 0x00000000u, 0x00000000u, 0x80800080u, 0x3c000000u, 0x00800080u, 0x00000080u, 0x80800000u, 0x80003c00u, 0x00000000u, 0x3c000000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00ff00ffu, 0x00ff4040u, 0xffff0000u, 0x00000000u, 0x00000000u, 0x80ff00ffu, 0x40400040u, 0x00ff00ffu, 0x000000ffu, 0x80ff0000u, 0xff404040u, 0x00000000u, 0x40400000u, 0x00000000u, 0xff000000u, 0x00000000u}, + {0x00ff00ffu, 0x00ff00ffu, 0xffff0000u, 0x00000000u, 0x00000000u, 0x80ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x000000ffu, 0x80ff0000u, 0xffff00ffu, 0x00000000u, 0x00ff0000u, 0x00000000u, 0xff000000u, 0x00000000u}, + {0x00ff00ffu, 0x00ff7accu, 0xffff0000u, 0x00000000u, 0x00000000u, 0x80ff00ffu, 0xe4ea00ccu, 0x00ff00ffu, 0x000000ffu, 0x80ff0000u, 0xffea7accu, 0x00000000u, 0xe4ea0000u, 0x00000000u, 0xff000000u, 0x00000000u}, + {0x00ff00ffu, 0x00ff5876u, 0xffff0000u, 0x00000000u, 0x00000000u, 0x80ff00ffu, 0x5de20076u, 0x00ff00ffu, 0x000000ffu, 0x80ff0000u, 0xffe25876u, 0x00000000u, 0x5de20000u, 0x00000000u, 0xff000000u, 0x00000000u}, + {0x01000100u, 0x01000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x81000100u, 0x00000000u, 0x01000100u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x182f8e4au, 0x182fbc00u, 0x004a0000u, 0x00000000u, 0x00000000u, 0x982f8e4au, 0x00004a00u, 0x182f8e4au, 0x0000002fu, 0x802f0000u, 0x4a00bc00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x4a000000u, 0x00000000u}, + {0x20d8e2bcu, 0x20d80000u, 0xffbc0000u, 0x00000000u, 0x00000000u, 0xa0d8e2bcu, 0xbf800000u, 0x20d8e2bcu, 0x000000d8u, 0x80d80000u, 0xbc800000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0xbc000000u, 0x00000000u}, + {0x2333ad9cu, 0x23330000u, 0xff9c0000u, 0x00000000u, 0x00000000u, 0xa333ad9cu, 0x3f800000u, 0x2333ad9cu, 0x00000033u, 0x80330000u, 0x9c800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x9c000000u, 0x00000000u}, + {0x24f70eabu, 0x24f7487au, 0xffab0000u, 0x00000000u, 0x00000000u, 0xa4f70eabu, 0x17e6007au, 0x24f70eabu, 0x000000f7u, 0x80f70000u, 0xabe6487au, 0x00000000u, 0x17e60000u, 0x00000000u, 0xab000000u, 0x00000000u}, + {0x2a778095u, 0x2a777c00u, 0xff950000u, 0x00000000u, 0x00000000u, 0xaa778095u, 0x7c000000u, 0x2a778095u, 0x00000077u, 0x80770000u, 0x95007c00u, 0x00000000u, 0x7c000000u, 0x00000000u, 0x95000000u, 0x00000000u}, + {0x2b119b05u, 0x2b11144fu, 0x00050000u, 0x00000000u, 0x00000000u, 0xab119b05u, 0x4ce5054fu, 0x2b119b05u, 0x00000011u, 0x80110000u, 0x05e5144fu, 0x00000000u, 0x4ce50000u, 0x00000000u, 0x05000000u, 0x00000000u}, + {0x2b5d55fdu, 0x2b5d7f7fu, 0xfffd0000u, 0x00000000u, 0x00000000u, 0xab5d55fdu, 0x7f7f007fu, 0x2b5d55fdu, 0x0000005du, 0x805d0000u, 0xfd7f7f7fu, 0x00000000u, 0x7f7f0000u, 0x00000000u, 0xfd000000u, 0x00000000u} +}; +constexpr std::uint32_t Checked[32] = {0xffffu, 0xfdfdu, 0x5dadu, 0xa05au, 0xb27au, 0x7fa7u, 0xb818u, 0xea77u, 0xfaffu, 0x5cfdu, 0x7ffdu, 0x9d38u, 0x9d38u, 0xf8fdu, 0xb977u, 0xfcfdu, 0xff5eu, 0xb818u, 0xf8fdu, 0xf8fdu, 0xf8fdu, 0xf8fdu, 0xf8fdu, 0xf8fdu, 0xfffdu, 0xfdbdu, 0xfdfdu, 0xfdfdu, 0xfdfdu, 0xf9fdu, 0xfdbdu, 0xfdfdu}; +constexpr const char* Names[16] = { + "add_f32 BYTE_0/DWORD -> DWORD", + "sub_f32 DWORD/WORD_1 -> WORD_1", + "subrev_f32 WORD_0/DWORD -> BYTE_2", + "mul_f32 DWORD/BYTE_3 -> WORD_0", + "mul_legacy_f32 BYTE_1/DWORD -> DWORD", + "min_f32 BYTE_2/DWORD -> DWORD", + "max_f32 DWORD/BYTE_0 -> BYTE_1", + "max_f32 WORD_1/DWORD -> DWORD", + "add_f16 BYTE_1/WORD_1 -> BYTE_0", + "sub_f16 WORD_1/BYTE_2 -> WORD_1", + "subrev_f16 BYTE_3/WORD_0 -> BYTE_3", + "mul_f16 WORD_0/BYTE_0 -> DWORD", + "min_f16 BYTE_0/WORD_1 -> WORD_0", + "max_f16 WORD_1/BYTE_1 -> BYTE_2", + "add_f32 WORD_1/DWORD -> BYTE_3", + "mul_f16 BYTE_2/DWORD -> BYTE_1", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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("sdwa float selectors: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +ShaderRecompiler::RecompileResult Compile(AgcDriver::VulkanDevice& device, std::span code) { + 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::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; + return ShaderRecompiler::Recompile(request); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + const std::span code(Code); + const auto result = Compile(device, code); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void CheckByteSelectorClampRefused(AgcDriver::VulkanDevice& device) { + constexpr std::array, 6> forms{{ + {0x640a0cf9u, 0x04052004u}, + {0x660a0cf9u, 0x04022504u}, + {0x680a0cf9u, 0x03042604u}, + {0x6a0a0cf9u, 0x05043304u}, + {0x740a0cf9u, 0x06012404u}, + {0x720a0cf9u, 0x00062604u}, + }}; + for (const auto& form : forms) { + alignas(256) const std::array code{form[0], form[1], 0xbf810000u}; + std::string refusal; + try { + static_cast(Compile(device, code)); + } catch (const std::exception& error) { + refusal = error.what(); + } + Require(refusal.find("VOP2 SDWA clamp with byte selectors is not implemented") != std::string::npos, "f16 SDWA form " + Hex(form[0]) + " " + Hex(form[1]) + " with clamp was not refused"); + } +} + +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]; + for (std::uint32_t i = 0; i < 16; ++i) { + if ((Checked[tid] >> i) & 1u) { + Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + CheckByteSelectorClampRefused(*device); + std::puts("sdwa float selectors tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClamp.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClamp.cpp new file mode 100644 index 000000000..4bc1207d3 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClamp.cpp @@ -0,0 +1,188 @@ +#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 +#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, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd543800a, 0x041a0b04, 0xd542800b, + 0x041a0b04, 0xd740800c, 0x041a0b04, 0xd75e800d, 0x041a0b04, 0xd773800e, 0x041a0b04, 0xd775800f, + 0x041a0b04, 0xd55a8010, 0x041a0b04, 0xd55b8011, 0x041a0b04, 0xd55c8012, 0x041a0b04, 0xd55d8013, + 0x041a0b04, 0xd5718014, 0x041a0b04, 0x7e2a0307, 0xd740d815, 0x041a0b04, 0xd75eb016, 0x041a0b04, + 0xd7758817, 0x041a0b04, 0xd7739018, 0x041a0b04, 0x7e320307, 0xd75ef819, 0x041a0b04, 0xe0781000, + 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000000u, 0x00000001u, 0x0000ffffu}, + {0x00000000u, 0x00007fffu, 0xffffffffu, 0x0000fff0u}, + {0x01010101u, 0x01010101u, 0xffffffffu, 0xff00ff00u}, + {0x00000001u, 0xff00ff00u, 0x80000000u, 0x7fffffffu}, + {0x00000000u, 0x00000000u, 0x506377feu, 0x09418c53u}, + {0x00000000u, 0x00000000u, 0xad078efau, 0x00000002u}, + {0x00000000u, 0x00000001u, 0x00000002u, 0x293b6032u}, + {0x00000000u, 0x00000001u, 0x0000fff0u, 0x00ff00ffu}, + {0x00000000u, 0x00000001u, 0x00ff00ffu, 0xdbd16e44u}, + {0x00000000u, 0x00000001u, 0x671b91d7u, 0xb1836b5au}, + {0x00000000u, 0x00000001u, 0xb51b0523u, 0x54b074b8u}, + {0x00000000u, 0x00000001u, 0xff0ec345u, 0x00007fffu}, + {0x00000000u, 0x00000002u, 0x00000000u, 0xfefefefeu}, + {0x00000000u, 0x0000007fu, 0x00000002u, 0xcee69ed0u}, + {0x00000000u, 0x0000007fu, 0x12345678u, 0x40778188u}, + {0x00000000u, 0x0000007fu, 0x1bf35776u, 0x12345678u}, + {0x00000000u, 0x0000007fu, 0x29c0ba3bu, 0x48e98a0au}, + {0x00000000u, 0x0000007fu, 0x9d299c7bu, 0x39cdd549u}, + {0x00000000u, 0x0000007fu, 0x9ff9949eu, 0x00ffffffu}, + {0x00000000u, 0x00000080u, 0x000000ffu, 0x12345678u}, + {0x00000000u, 0x00000080u, 0x00008000u, 0x80000001u}, + {0x00000000u, 0x00000080u, 0x0000fff0u, 0xae15989du}, + {0x00000000u, 0x00000080u, 0x0000ffffu, 0x00000080u}, + {0x00000000u, 0x00000080u, 0x80000001u, 0x0000007fu}, + {0x00000000u, 0x00000080u, 0xaf4a9e38u, 0xd42344f4u}, + {0x00000000u, 0x00000080u, 0xea4d8c45u, 0x00625618u}, + {0x00000000u, 0x00000080u, 0xfd4fb563u, 0xff00ff00u}, + {0x00000000u, 0x000000ffu, 0x00000001u, 0x310733d9u}, + {0x00000000u, 0x000000ffu, 0x2f3bacc0u, 0xffff0000u}, + {0x00000000u, 0x000000ffu, 0x85f55b11u, 0xffff0000u}, + {0x00000000u, 0x000000ffu, 0xff00ff00u, 0x9fc408e0u}, + {0x00000000u, 0x000000ffu, 0xffff0000u, 0xffff0000u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x0001ffffu, 0x00000000u, 0x00000001u, 0x00000001u, 0x0000ffffu}, + {0xffffffffu, 0xffffffffu, 0x0000ffffu, 0x0000ffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xfffffff0u, 0x0000ffffu, 0xffffffffu, 0xffffffffu, 0xfffffff0u}, + {0xffffffffu, 0x7fffffffu, 0x0000ffffu, 0x00007fffu, 0xffffffffu, 0x00010200u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffff00u, 0x00007fffu, 0x00010200u, 0xffffffffu, 0x7fffff00u}, + {0x8000ff00u, 0x8000ff00u, 0x0000ff00u, 0x0000ff00u, 0x8000ff00u, 0x80000000u, 0x800001ffu, 0x81ff0000u, 0x8001fdffu, 0xffffffffu, 0x800001feu, 0x0000ffffu, 0x00008000u, 0x80000000u, 0x8000ff00u, 0x8000ffffu}, + {0x506377feu, 0x506377feu, 0x000077feu, 0x000077feu, 0x506377feu, 0x506377feu, 0x506377feu, 0x506377feu, 0x506377feu, 0x506377feu, 0x506377feu, 0x77fe8c53u, 0x00005063u, 0x506377feu, 0x506377feu, 0x50638c53u}, + {0xad078efau, 0xad078efau, 0x00008efau, 0x00008efau, 0xad078efau, 0xad078efau, 0xad078efau, 0xad078efau, 0xad078efau, 0xad078efau, 0xad078efau, 0x8efa0002u, 0x0000ad07u, 0xad078efau, 0xad078efau, 0xad070002u}, + {0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000003u, 0x00010002u, 0x00000003u, 0x00000003u, 0x00000003u, 0x00026032u, 0x00000000u, 0x00000002u, 0x00000002u, 0x00006032u}, + {0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff1u, 0x0001fff0u, 0x0000fff1u, 0x0000fff1u, 0x0000fff1u, 0xfff000ffu, 0x00000000u, 0x0000fff0u, 0x0000fff0u, 0x000000ffu}, + {0x00ff00ffu, 0x00ff00ffu, 0x000000ffu, 0x000000ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff0100u, 0x010000ffu, 0x00ff0100u, 0x00ff0100u, 0x00ff0100u, 0x00ff6e44u, 0x000000ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff6e44u}, + {0x671b91d7u, 0x671b91d7u, 0x000091d7u, 0x000091d7u, 0x671b91d7u, 0x671b91d7u, 0x671b91d8u, 0x671c91d7u, 0x671b91d8u, 0x671b91d8u, 0x671b91d8u, 0x91d76b5au, 0x0000671bu, 0x671b91d7u, 0x671b91d7u, 0x671b6b5au}, + {0xb51b0523u, 0xb51b0523u, 0x00000523u, 0x00000523u, 0xb51b0523u, 0xb51b0523u, 0xb51b0524u, 0xb51c0523u, 0xb51b0524u, 0xb51b0524u, 0xb51b0524u, 0x052374b8u, 0x0000b51bu, 0xb51b0523u, 0xb51b0523u, 0xb51b74b8u}, + {0xff0ec345u, 0xff0ec345u, 0x0000c345u, 0x0000c345u, 0xff0ec345u, 0xff0ec345u, 0xff0ec346u, 0xff0fc345u, 0xff0ec346u, 0xff0ec346u, 0xff0ec346u, 0xc3457fffu, 0x0000ff0eu, 0xff0ec345u, 0xff0ec345u, 0xff0e7fffu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000002u, 0x00020000u, 0x00000002u, 0x00000002u, 0x00000002u, 0x0000fefeu, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000fefeu}, + {0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000081u, 0x007f0002u, 0x00000081u, 0x00000081u, 0x00000081u, 0x00029ed0u, 0x00000000u, 0x00000002u, 0x00000002u, 0x00009ed0u}, + {0x12345678u, 0x12345678u, 0x00005678u, 0x00005678u, 0x12345678u, 0x12345678u, 0x123456f7u, 0x12b35678u, 0x123456f7u, 0x123456f7u, 0x123456f7u, 0x56788188u, 0x00001234u, 0x12345678u, 0x12345678u, 0x12348188u}, + {0x1bf35776u, 0x1bf35776u, 0x00005776u, 0x00005776u, 0x1bf35776u, 0x1bf35776u, 0x1bf357f5u, 0x1c725776u, 0x1bf357f5u, 0x1bf357f5u, 0x1bf357f5u, 0x57765678u, 0x00001bf3u, 0x1bf35776u, 0x1bf35776u, 0x1bf35678u}, + {0x29c0ba3bu, 0x29c0ba3bu, 0x0000ba3bu, 0x0000ba3bu, 0x29c0ba3bu, 0x29c0ba3bu, 0x29c0babau, 0x2a3fba3bu, 0x29c0babau, 0x29c0babau, 0x29c0babau, 0xba3b8a0au, 0x000029c0u, 0x29c0ba3bu, 0x29c0ba3bu, 0x29c08a0au}, + {0x9d299c7bu, 0x9d299c7bu, 0x00009c7bu, 0x00009c7bu, 0x9d299c7bu, 0x9d299c7bu, 0x9d299cfau, 0x9da89c7bu, 0x9d299cfau, 0x9d299cfau, 0x9d299cfau, 0x9c7bd549u, 0x00009d29u, 0x9d299c7bu, 0x9d299c7bu, 0x9d29d549u}, + {0x9ff9949eu, 0x9ff9949eu, 0x0000949eu, 0x0000949eu, 0x9ff9949eu, 0x9ff9949eu, 0x9ff9951du, 0xa078949eu, 0x9ff9951du, 0x9ff9951du, 0x9ff9951du, 0x949effffu, 0x00009ff9u, 0x9ff9949eu, 0x9ff9949eu, 0x9ff9ffffu}, + {0x000000ffu, 0x000000ffu, 0x000000ffu, 0x000000ffu, 0x000000ffu, 0x000000ffu, 0x0000017fu, 0x008000ffu, 0x0000017fu, 0x0000017fu, 0x0000017fu, 0x00ff5678u, 0x00000000u, 0x000000ffu, 0x000000ffu, 0x00005678u}, + {0x00008000u, 0x00008000u, 0x00008000u, 0x00008000u, 0x00008000u, 0x00008000u, 0x00008080u, 0x00808000u, 0x00008080u, 0x00008080u, 0x00008080u, 0x80000001u, 0x00000000u, 0x00008000u, 0x00008000u, 0x00000001u}, + {0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x00010070u, 0x0080fff0u, 0x00010070u, 0x00010070u, 0x00010070u, 0xfff0989du, 0x00000000u, 0x0000fff0u, 0x0000fff0u, 0x0000989du}, + {0x0000ffffu, 0x0000ffffu, 0x0000ffffu, 0x0000ffffu, 0x0000ffffu, 0x0000ffffu, 0x0001007fu, 0x0080ffffu, 0x0001007fu, 0x0001007fu, 0x0001007fu, 0xffff0080u, 0x00000000u, 0x0000ffffu, 0x0000ffffu, 0x00000080u}, + {0x80000001u, 0x80000001u, 0x00000001u, 0x00000001u, 0x80000001u, 0x80000001u, 0x80000081u, 0x80800001u, 0x80000081u, 0x80000081u, 0x80000081u, 0x0001007fu, 0x00008000u, 0x80000001u, 0x80000001u, 0x8000007fu}, + {0xaf4a9e38u, 0xaf4a9e38u, 0x00009e38u, 0x00009e38u, 0xaf4a9e38u, 0xaf4a9e38u, 0xaf4a9eb8u, 0xafca9e38u, 0xaf4a9eb8u, 0xaf4a9eb8u, 0xaf4a9eb8u, 0x9e3844f4u, 0x0000af4au, 0xaf4a9e38u, 0xaf4a9e38u, 0xaf4a44f4u}, + {0xea4d8c45u, 0xea4d8c45u, 0x00008c45u, 0x00008c45u, 0xea4d8c45u, 0xea4d8c45u, 0xea4d8cc5u, 0xeacd8c45u, 0xea4d8cc5u, 0xea4d8cc5u, 0xea4d8cc5u, 0x8c455618u, 0x0000ea4du, 0xea4d8c45u, 0xea4d8c45u, 0xea4d5618u}, + {0xfd4fb563u, 0xfd4fb563u, 0x0000b563u, 0x0000b563u, 0xfd4fb563u, 0xfd4fb563u, 0xfd4fb5e3u, 0xfdcfb563u, 0xfd4fb5e3u, 0xfd4fb5e3u, 0xfd4fb5e3u, 0xb563ff00u, 0x0000fd4fu, 0xfd4fb563u, 0xfd4fb563u, 0xfd4fff00u}, + {0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000100u, 0x00ff0001u, 0x00000100u, 0x00000100u, 0x00000100u, 0x000133d9u, 0x00000000u, 0x00000001u, 0x00000001u, 0x000033d9u}, + {0x2f3bacc0u, 0x2f3bacc0u, 0x0000acc0u, 0x0000acc0u, 0x2f3bacc0u, 0x2f3bacc0u, 0x2f3badbfu, 0x303aacc0u, 0x2f3badbfu, 0x2f3badbfu, 0x2f3badbfu, 0xacc00000u, 0x00002f3bu, 0x2f3bacc0u, 0x2f3bacc0u, 0x2f3b0000u}, + {0x85f55b11u, 0x85f55b11u, 0x00005b11u, 0x00005b11u, 0x85f55b11u, 0x85f55b11u, 0x85f55c10u, 0x86f45b11u, 0x85f55c10u, 0x85f55c10u, 0x85f55c10u, 0x5b110000u, 0x000085f5u, 0x85f55b11u, 0x85f55b11u, 0x85f50000u}, + {0xff00ff00u, 0xff00ff00u, 0x0000ff00u, 0x0000ff00u, 0xff00ff00u, 0xff00ff00u, 0xff00ffffu, 0xffffff00u, 0xff00ffffu, 0xff00ffffu, 0xff00ffffu, 0xff0008e0u, 0x0000ff00u, 0xff00ff00u, 0xff00ff00u, 0xff0008e0u}, + {0xffff0000u, 0xffff0000u, 0x00000000u, 0x00000000u, 0xffff0000u, 0xffff0000u, 0xffff00ffu, 0xffffffffu, 0xffff00ffu, 0xffff00ffu, 0xffff00ffu, 0x00000000u, 0x0000ffffu, 0xffff0000u, 0xffff0000u, 0xffff0000u} +}; +constexpr const char* Names[16] = { + "mad_u32_u24", + "mad_i32_i24", + "mad_u16", + "mad_i16", + "mad_u32_u16", + "mad_i32_i16", + "sad_u8", + "sad_hi_u8", + "sad_u16", + "sad_u32", + "msad_u8", + "mad_u16 op_sel", + "mad_i16 op_sel", + "mad_i32_i16 op_sel", + "mad_u32_u16 op_sel", + "mad_i16 op_sel", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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("vop3 integer clamp: 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]; + for (std::uint32_t i = 0; i < 16; ++i) { + Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop3 integer clamp tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClampWide.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClampWide.cpp new file mode 100644 index 000000000..adc82c757 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClampWide.cpp @@ -0,0 +1,187 @@ +#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 +#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, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd572800a, 0x041a0d04, 0xd573800c, + 0x041a0d04, 0xd575800e, 0x04120d04, 0xd7558012, 0x041a0b04, 0xd7568013, 0x041a0b04, 0xd7528014, + 0x041a0b04, 0xd7538015, 0x041a0b04, 0xd7588016, 0x041a0b04, 0xd7598017, 0x041a0b04, 0xd54e8018, + 0x041a0b04, 0xd54f8019, 0x041a0b04, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, + 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000000u, 0x00800000u, 0x0000ffffu}, + {0x0000007fu, 0xe3718038u, 0xfffffffeu, 0xfd7c84feu}, + {0x0000007fu, 0xfffffffeu, 0x0000007fu, 0xfffffffeu}, + {0xfffffffeu, 0x00000002u, 0xb93201bfu, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3ff51543u, 0x8ca197b1u}, + {0x00000000u, 0x00000001u, 0x007fffffu, 0x29813b74u}, + {0x00000000u, 0x00000001u, 0x00ff00ffu, 0x00007fffu}, + {0x00000000u, 0x00000001u, 0x01010101u, 0xa48d7f36u}, + {0x00000000u, 0x00000001u, 0x1ef5acc3u, 0x5d780492u}, + {0x00000000u, 0x00000001u, 0x41f8bfb7u, 0x3f57a9d3u}, + {0x00000000u, 0x00000001u, 0xfefefefeu, 0x0000007fu}, + {0x00000000u, 0x00000002u, 0x00ffffffu, 0x80000000u}, + {0x00000000u, 0x00000002u, 0x7351a059u, 0x00007fffu}, + {0x00000000u, 0x00000002u, 0xcb50b50fu, 0x7fffffffu}, + {0x00000000u, 0x0000007fu, 0x0000ffffu, 0x01010101u}, + {0x00000000u, 0x0000007fu, 0x0ac13011u, 0xffff0000u}, + {0x00000000u, 0x0000007fu, 0x4b88f735u, 0x000000ffu}, + {0x00000000u, 0x0000007fu, 0xffffffffu, 0x0000fff0u}, + {0x00000000u, 0x00000080u, 0x00000000u, 0x00ffffffu}, + {0x00000000u, 0x00000080u, 0xffffffffu, 0xde945808u}, + {0x00000000u, 0x000000ffu, 0x000000ffu, 0x00000080u}, + {0x00000000u, 0x000000ffu, 0x7fffffffu, 0xca9c52c9u}, + {0x00000000u, 0x000000ffu, 0x80000001u, 0x0000007fu}, + {0x00000000u, 0x000000ffu, 0xffff0000u, 0x00007fffu}, + {0x00000000u, 0x000000ffu, 0xffffffffu, 0xffffffffu}, + {0x00000000u, 0x00007fffu, 0x01010101u, 0xcff6ab04u}, + {0x00000000u, 0x00007fffu, 0x44e9da39u, 0x63548e6fu}, + {0x00000000u, 0x00007fffu, 0x4944fa2au, 0xf5524d50u}, + {0x00000000u, 0x00007fffu, 0xa1dfdb1eu, 0xa9e05a10u}, + {0x00000000u, 0x00007fffu, 0xffff0000u, 0x01010101u}, + {0x00000000u, 0x00007fffu, 0xffffffffu, 0x0000007fu}, + {0x00000000u, 0x0000fff0u, 0x00000080u, 0x01010101u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x01000080u, 0x0080ffffu, 0x01000080u, 0x0080ffffu, 0x00000080u, 0x00000080u, 0x00800080u, 0x0001007fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xffffffffu, 0xffff8841u, 0xffffffffu, 0xffff8841u, 0x000003fbu, 0xe37183fbu, 0xffffffffu, 0xfd7c87d0u, 0x0000007fu, 0x0000fffeu, 0x00008038u, 0x0000007fu, 0x0000fffeu, 0x00008038u, 0x000001ffu, 0x007fe371u}, + {0x017d007fu, 0xffffffffu, 0x007f007fu, 0xffffffffu, 0x0000007fu, 0xffffffffu, 0x000000feu, 0xffffffffu, 0x0000007fu, 0x0000fffeu, 0x0000fffeu, 0x0000007fu, 0x0000007fu, 0x0000007fu, 0x000000ffu, 0x00007fffu}, + {0xbbf4040fu, 0x012c0227u, 0xbbf4040fu, 0x012c0227u, 0xffffffffu, 0x000002c4u, 0xb93203e6u, 0x0000012cu, 0x000001bfu, 0x0000fffeu, 0x0000fffeu, 0x00000002u, 0x00000002u, 0x000001bfu, 0xfffffffcu, 0xfffffe00u}, + {0x418116cfu, 0x8e2d993du, 0x418116cfu, 0x8e2d993du, 0x0000018cu, 0x0000018cu, 0x3ff516cfu, 0x8ca1993du, 0x00001543u, 0x00001543u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x02fdffffu, 0x2bfd3df0u, 0x02fcffffu, 0x2bfd3df0u, 0x0000027du, 0x0000027eu, 0x0080027bu, 0x29813df0u, 0x00000001u, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x02fe02fdu, 0x01ff81fcu, 0x02fd02fdu, 0x01fe81fcu, 0x000001feu, 0x000001ffu, 0x00ff02fcu, 0x000081fdu, 0x000000ffu, 0x000000ffu, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x01040105u, 0xa4907f39u, 0x01040105u, 0xa4907f39u, 0x00000004u, 0x00000004u, 0x01010104u, 0xa48d7f39u, 0x00000101u, 0x00000101u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x2176af45u, 0x5ff90713u, 0x2176af45u, 0x5ff90713u, 0x00000282u, 0x00000282u, 0x1ef5af44u, 0x5d780713u, 0x00000001u, 0x0000acc3u, 0x0000acc3u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x44a6c266u, 0x4205ac81u, 0x44a6c266u, 0x4205ac81u, 0x000002afu, 0x000002afu, 0x41f8c265u, 0x3f57ac81u, 0x00000001u, 0x0000bfb7u, 0x0000bfb7u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0xffffffffu, 0x03f70476u, 0xffffffffu, 0x03f70476u, 0x000003f8u, 0x000003f8u, 0xfeff02f5u, 0x00000476u, 0x00000001u, 0x0000fefeu, 0x0000fefeu, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x03feffffu, 0x82fb02fbu, 0x03fcffffu, 0x82fb02fbu, 0x000002fdu, 0x000002ffu, 0x010002fau, 0x800002fbu, 0x00000002u, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x00000002u, 0x00000000u, 0x00000000u}, + {0x750ca216u, 0x01bb81bau, 0x750ca216u, 0x01bb81bau, 0x000001bdu, 0x000001bdu, 0x7351a214u, 0x000081bau, 0x00000002u, 0x0000a059u, 0x0000a059u, 0x00000000u, 0x00000000u, 0x00000002u, 0x00000000u, 0x00000000u}, + {0xcd2db6eeu, 0x81dcffffu, 0xcd2db6eeu, 0x81dcffffu, 0x000001dfu, 0x000001dfu, 0xcb50b6ecu, 0x800001dcu, 0x00000002u, 0x0000b50fu, 0x0000b50fu, 0x00000000u, 0x00000000u, 0x00000002u, 0x00000000u, 0x00000000u}, + {0x027dffffu, 0x0280037eu, 0x01feffffu, 0x028002ffu, 0x000001feu, 0x0000027du, 0x000101fdu, 0x01010280u, 0x0000007fu, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x00000000u}, + {0x0c38311du, 0xffff008du, 0x0c38311du, 0xffff008du, 0x0000010cu, 0x000001f6u, 0x0ac1309eu, 0xffff012bu, 0x00003011u, 0x00003011u, 0x00000000u, 0x00000000u, 0x0000007fu, 0x0000007fu, 0x00000000u, 0x00000000u}, + {0x4d70f934u, 0x0180027fu, 0x4d70f934u, 0x0180027fu, 0x000001ffu, 0x00000267u, 0x4b88f8b5u, 0x0000027fu, 0x0000007fu, 0x0000f735u, 0x0000f735u, 0x00000000u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x00000000u}, + {0xffffffffu, 0x037dffffu, 0xffffffffu, 0x037dffffu, 0x000003fcu, 0x000003fcu, 0xffffffffu, 0x0001036du, 0x0000007fu, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x00000000u}, + {0x00800000u, 0x017fffffu, 0x00000000u, 0x00ffffffu, 0x00000000u, 0x00000080u, 0x00000000u, 0x00ffffffu, 0x00000080u, 0x00000080u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000080u, 0x00000080u}, + {0xffffffffu, 0xe2105b84u, 0xffffffffu, 0xe2105b84u, 0x000003fcu, 0x000003fcu, 0xffffffffu, 0xde945b84u, 0x00000080u, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x00000080u, 0x00000000u, 0x00000000u}, + {0x01fe01feu, 0x01fe027eu, 0x00ff01feu, 0x00ff017fu, 0x000000ffu, 0x000001feu, 0x000001feu, 0x0000017fu, 0x000000ffu, 0x000000ffu, 0x00000000u, 0x00000000u, 0x000000ffu, 0x000000ffu, 0x00000000u, 0x00000000u}, + {0x837cffffu, 0xcd195546u, 0x837cffffu, 0xcd195546u, 0x0000037cu, 0x0000047cu, 0x8000027cu, 0xca9c5546u, 0x000000ffu, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00000000u, 0x00000000u}, + {0x80800082u, 0x018001ffu, 0x80800082u, 0x00810100u, 0x00000081u, 0x0000017fu, 0x80000082u, 0x00000100u, 0x000000ffu, 0x000000ffu, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000001u, 0x0000007fu, 0x00000000u}, + {0xffff01feu, 0x02fd80feu, 0xffff01feu, 0x01fe80feu, 0x000001feu, 0x000001feu, 0xffff00ffu, 0x000081fdu, 0x000000ffu, 0x000000ffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x000000ffu}, + {0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x000003fcu, 0x000003fcu, 0xffffffffu, 0xffffffffu, 0x000000ffu, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00000000u, 0x00000000u}, + {0x02020105u, 0xd174ac82u, 0x02020105u, 0xd174ac82u, 0x00000004u, 0x00008100u, 0x0101027fu, 0xcff6ac82u, 0x00007fffu, 0x00007fffu, 0x00000000u, 0x00000000u, 0x00000101u, 0x00000101u, 0x00003fffu, 0x0000007fu}, + {0x47a0dc79u, 0x64608fd3u, 0x47a0dc79u, 0x64608fd3u, 0x00000240u, 0x000082b6u, 0x44e9db9du, 0x63548f7bu, 0x00007fffu, 0x0000da39u, 0x0000da39u, 0x00000000u, 0x00000000u, 0x00007fffu, 0x00000000u, 0x0000007fu}, + {0x4b62fbdbu, 0xf6054f65u, 0x4b62fbdbu, 0xf6054f65u, 0x000001b1u, 0x0000821du, 0x4944fc3fu, 0xf5524e03u, 0x00007fffu, 0x0000fa2au, 0x0000fa2au, 0x00000000u, 0x00000000u, 0x00007fffu, 0x0000001fu, 0x00000000u}, + {0xa415dd97u, 0xab235b4bu, 0xa415dd97u, 0xab235b4bu, 0x00000279u, 0x00008235u, 0xa1dfdc59u, 0xa9e05b53u, 0x00007fffu, 0x0000db1eu, 0x0000db1eu, 0x00000000u, 0x00000000u, 0x00007fffu, 0x00000000u, 0x00000000u}, + {0xffff01feu, 0x037f0181u, 0xffff01feu, 0x02800181u, 0x000001feu, 0x000080feu, 0xffff0080u, 0x01010280u, 0x00007fffu, 0x00007fffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00007fffu, 0x00007fffu}, + {0xffffffffu, 0x027e02fdu, 0xffffffffu, 0x027e02fdu, 0x000003fcu, 0x000082fcu, 0xffffffffu, 0x000002fdu, 0x00007fffu, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x00007fffu, 0x00000000u, 0x00000000u}, + {0x01700100u, 0x03700370u, 0x00800100u, 0x01810181u, 0x00000080u, 0x00010070u, 0x00000100u, 0x01010181u, 0x00000080u, 0x0000fff0u, 0x0000fff0u, 0x00000000u, 0x00000000u, 0x00000080u, 0x0000fff0u, 0x0000fff0u} +}; +constexpr const char* Names[16] = { + "qsad_pk_u16_u8 0", + "qsad_pk_u16_u8 1", + "mqsad_pk_u16_u8 0", + "mqsad_pk_u16_u8 1", + "mqsad_u32_u8 0", + "mqsad_u32_u8 1", + "mqsad_u32_u8 2", + "mqsad_u32_u8 3", + "max3_i16", + "max3_u16", + "min3_i16", + "min3_u16", + "med3_i16", + "med3_u16", + "alignbit_b32", + "alignbyte_b32", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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("vop3 integer clamp wide: 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]; + for (std::uint32_t i = 0; i < 16; ++i) { + Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop3 integer clamp wide tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3Sad.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3Sad.cpp index 00055340c..8d4c14553 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3Sad.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3Sad.cpp @@ -90,19 +90,6 @@ void Run(AgcDriver::VulkanDevice& device) { device.WaitIdle(); } -void CheckClampRefused(AgcDriver::VulkanDevice& device) { - for (const std::uint32_t opcode : {0x15au, 0x15bu, 0x15cu, 0x171u}) { - alignas(256) const std::array code{0xd400800au | (opcode << 16u), 0x041a0b04u, 0xbf810000u}; - std::string refusal; - try { - static_cast(Compile(device, code)); - } catch (const std::exception& error) { - refusal = error.what(); - } - Require(refusal.find("VOP3 source modifiers are not implemented") != std::string::npos, "VOP3 opcode " + Hex(opcode) + " with clamp was not refused"); - } -} - std::uint32_t Sad(std::uint32_t lhs, std::uint32_t rhs, std::uint32_t fieldBits, bool masked) { const std::uint32_t fieldMask = (1u << fieldBits) - 1u; std::uint32_t sum = 0u; @@ -154,7 +141,6 @@ int main() { FillInput(); Run(*device); Check(); - CheckClampRefused(*device); std::puts("vop3 sad tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAjm.native/src/Ajm.cpp b/core/libs/prx/libSceAjm.native/src/Ajm.cpp index 50cbee420..3b30d8d53 100644 --- a/core/libs/prx/libSceAjm.native/src/Ajm.cpp +++ b/core/libs/prx/libSceAjm.native/src/Ajm.cpp @@ -16,6 +16,7 @@ extern "C" { #include #include #include +#include #include #include #include @@ -26,6 +27,7 @@ extern "C" { namespace { constexpr int SCE_AJM_ERROR_INVALID_INSTANCE = static_cast(0x80930003); +constexpr int SCE_AJM_ERROR_INVALID_BATCH = static_cast(0x80930004); constexpr int SCE_AJM_ERROR_INVALID_PARAMETER = static_cast(0x80930005); constexpr int SCE_AJM_ERROR_OUT_OF_RESOURCES = static_cast(0x80930007); @@ -127,6 +129,8 @@ std::map> g_instances; std::atomic g_nextContext{1}; std::atomic g_nextInstance{1}; std::atomic g_nextBatch{1}; +std::mutex g_batchLock; +std::set g_batches; enum class JobKind : std::uint32_t { Initialize = 1, @@ -985,14 +989,22 @@ int APS5_VABI sceAjmBatchStart(uint32_t context, const AjmBatchInfo* info, int p cursor += record.bytes; } if (error) std::memset(error, 0, sizeof(*error)); - *batch = g_nextBatch.fetch_add(1, std::memory_order_relaxed); + const auto id = g_nextBatch.fetch_add(1, std::memory_order_relaxed); + { + std::lock_guard lock(g_batchLock); + g_batches.insert(id); + } + *batch = id; return 0; } int APS5_VABI sceAjmBatchWait(uint32_t context, uint32_t batch, uint32_t timeout, AjmBatchError* error) { (void)context; - (void)batch; (void)timeout; + { + std::lock_guard lock(g_batchLock); + if (g_batches.erase(batch) == 0) return SCE_AJM_ERROR_INVALID_BATCH; + } if (error) std::memset(error, 0, sizeof(*error)); return 0; } diff --git a/core/libs/prx/libSceAudioOut/CMakeLists.txt b/core/libs/prx/libSceAudioOut/CMakeLists.txt index 024083dad..6251abea9 100644 --- a/core/libs/prx/libSceAudioOut/CMakeLists.txt +++ b/core/libs/prx/libSceAudioOut/CMakeLists.txt @@ -52,4 +52,14 @@ if(BUILD_TESTING) set_tests_properties(audio_out_last_output_time PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(audio_out2_latency_tests tests/AudioOut2Latency.cpp) + target_include_directories(audio_out2_latency_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(audio_out2_latency_tests PRIVATE libSceAudioOut libc) + configure_windows_unwind(audio_out2_latency_tests) + add_test(NAME audio_out2_latency COMMAND audio_out2_latency_tests) + if(WIN32) + set_tests_properties(audio_out2_latency PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp index d8b0455c2..ac3f33976 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp @@ -5,8 +5,7 @@ extern "C" { int APS5_VABI sceAudioOut2MasteringInit(uint32_t flags) { - (void)flags; - NotImplemented_nid_no_patch(__func__); + if (flags != 0) NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp index b690814de..f658ddf10 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp @@ -1,12 +1,21 @@ +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +static constexpr int USER_ID_SYSTEM = 0xFF; +static constexpr std::uint32_t SUPPORTED_3D_LATENCY = 2; + extern "C" { int APS5_VABI sceAudioOut2Initialize(void) { return 0; } +int APS5_VABI sceAudioOut2Set3DLatency(int userId, std::uint32_t latency) { + if (userId != USER_ID_SYSTEM || latency != SUPPORTED_3D_LATENCY) NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceAudioOut2GetSystemState(AudioOut2SystemState* state) { if (!state) return static_cast(0x80260502); state->loudness = 0.0f; diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp new file mode 100644 index 000000000..7f3404f2a --- /dev/null +++ b/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp @@ -0,0 +1,55 @@ +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceAudioOut2Initialize(); +int APS5_VABI sceAudioOut2Set3DLatency(int, std::uint32_t); +int APS5_VABI sceAudioOut2MasteringInit(std::uint32_t); +} + +static void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +template +static bool ThrowsRuntimeError(TFunction function) { + try { + function(); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +namespace { + +constexpr int systemUser = 0xFF; +constexpr int user = 0x10000000; + +void TestSet3DLatency() { + Require(sceAudioOut2Set3DLatency(systemUser, 2) == 0, "latency 2 for the system user must be accepted"); + Require(sceAudioOut2Set3DLatency(systemUser, 2) == 0, "latency 2 must be accepted again"); + Require(ThrowsRuntimeError([] { sceAudioOut2Set3DLatency(systemUser, 1); }), "latency 1 must throw"); + Require(ThrowsRuntimeError([] { sceAudioOut2Set3DLatency(systemUser, 3); }), "latency 3 must throw"); + Require(ThrowsRuntimeError([] { sceAudioOut2Set3DLatency(user, 2); }), "a user other than the system user must throw"); +} + +void TestMasteringInit() { + Require(sceAudioOut2MasteringInit(0) == 0, "flags 0 must be accepted"); + Require(ThrowsRuntimeError([] { sceAudioOut2MasteringInit(1); }), "flags 1 must throw"); +} + +} + +int main() { + Require(sceAudioOut2Initialize() == 0, "initialization must succeed"); + TestSet3DLatency(); + TestMasteringInit(); + return 0; +} diff --git a/core/libs/prx/libSceHttp/CMakeLists.txt b/core/libs/prx/libSceHttp/CMakeLists.txt index 0093dd1c0..473651532 100644 --- a/core/libs/prx/libSceHttp/CMakeLists.txt +++ b/core/libs/prx/libSceHttp/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceHttp SHARED EXCLUDE_FROM_ALL Export.cpp src/Uri.cpp) +add_library(libSceHttp SHARED EXCLUDE_FROM_ALL Export.cpp src/Parse.cpp src/Uri.cpp) target_include_directories(libSceHttp PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libSceHttp) diff --git a/core/libs/prx/libSceHttp/Export.cpp b/core/libs/prx/libSceHttp/Export.cpp index d84ed5a4c..0bbfa9e09 100644 --- a/core/libs/prx/libSceHttp/Export.cpp +++ b/core/libs/prx/libSceHttp/Export.cpp @@ -246,15 +246,14 @@ int APS5_VABI sceHttpSetChunkedTransferEnabled(int id, int enable) { } -APS5_EXPORT("i9mhafzkEi8", sceHttpUnknown00); -int APS5_VABI sceHttpUnknown00(void) { - NotImplemented_nid_no_patch("i9mhafzkEi8"); +int APS5_VABI sceHttpSetInflateGZIPEnabled(int id, int enable) { + (void)id; + if (static_cast(enable) > 1) return ERROR_INVALID_VALUE; return 0; } -APS5_EXPORT("vO4B-42ef-k", sceHttpUnknown01); -int APS5_VABI sceHttpUnknown01(void) { - NotImplemented_nid_no_patch("vO4B-42ef-k"); +int APS5_VABI sceHttpSetRequestStatusCallback(void) { + NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceHttp/src/HttpErrors.hpp b/core/libs/prx/libSceHttp/src/HttpErrors.hpp index 66d70c7e4..b95f8d25b 100644 --- a/core/libs/prx/libSceHttp/src/HttpErrors.hpp +++ b/core/libs/prx/libSceHttp/src/HttpErrors.hpp @@ -5,5 +5,7 @@ inline constexpr int ERROR_OUT_OF_MEMORY = static_cast(0x80431022); inline constexpr int ERROR_NETWORK = static_cast(0x80431063); inline constexpr int ERROR_INVALID_VALUE = static_cast(0x804311FE); inline constexpr int ERROR_INVALID_URL = static_cast(0x80433060); +inline constexpr int ERROR_PARSE_HTTP_INVALID_RESPONSE = static_cast(0x80432060); +inline constexpr int ERROR_PARSE_HTTP_INVALID_VALUE = static_cast(0x804321FE); #endif diff --git a/core/libs/prx/libSceHttp/src/Parse.cpp b/core/libs/prx/libSceHttp/src/Parse.cpp new file mode 100644 index 000000000..93b9d109b --- /dev/null +++ b/core/libs/prx/libSceHttp/src/Parse.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include "prx/libc/include/General.hpp" +#include "prx/libSceHttp/src/HttpErrors.hpp" + +namespace { + +bool isAsciiDigit(char c) { + return c >= '0' && c <= '9'; +} + +bool parseVersionNumber(const char* line, size_t lineLen, size_t& index, int32_t* value) { + if (index >= lineLen || !isAsciiDigit(line[index])) return false; + uint32_t number = 0; + while (index < lineLen && isAsciiDigit(line[index])) { + number = number * 10 + static_cast(line[index] - '0'); + ++index; + } + *value = static_cast(number); + return true; +} + +} + +extern "C" { + +int APS5_VABI sceHttpParseStatusLine(const char* statusLine, size_t lineLen, int32_t* httpMajorVer, int32_t* httpMinorVer, + int32_t* responseCode, const char** reasonPhrase, size_t* phraseLen) { + if (!statusLine) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (!httpMajorVer || !httpMinorVer || !responseCode || !reasonPhrase || !phraseLen) return ERROR_PARSE_HTTP_INVALID_VALUE; + + *httpMajorVer = 0; + *httpMinorVer = 0; + if (lineLen < 8 || std::memcmp(statusLine, "HTTP/", 5) != 0) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + + size_t index = 5; + if (!parseVersionNumber(statusLine, lineLen, index, httpMajorVer)) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (index >= lineLen || statusLine[index] != '.') return ERROR_PARSE_HTTP_INVALID_RESPONSE; + ++index; + if (!parseVersionNumber(statusLine, lineLen, index, httpMinorVer)) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (index >= lineLen || statusLine[index] != ' ') return ERROR_PARSE_HTTP_INVALID_RESPONSE; + ++index; + + if (lineLen - index < 3) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (!isAsciiDigit(statusLine[index]) || !isAsciiDigit(statusLine[index + 1]) || !isAsciiDigit(statusLine[index + 2])) { + return ERROR_PARSE_HTTP_INVALID_RESPONSE; + } + *responseCode = (statusLine[index] - '0') * 100 + (statusLine[index + 1] - '0') * 10 + (statusLine[index + 2] - '0'); + index += 3; + + const char* phrase = statusLine + index; + const void* lineFeed = std::memchr(phrase, '\n', lineLen - index); + if (!lineFeed) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + + size_t length = static_cast(static_cast(lineFeed) - phrase); + const size_t consumed = index + length + 1; + if (consumed > static_cast(INT32_MAX)) { + NotImplemented_nid_no_patch("sceHttpParseStatusLine: status line longer than INT32_MAX"); + } + if (length > 0 && phrase[length - 1] == '\r') --length; + + *reasonPhrase = phrase; + *phraseLen = length; + return static_cast(consumed); +} + +} diff --git a/core/libs/prx/libSceLibcInternal/CMakeLists.txt b/core/libs/prx/libSceLibcInternal/CMakeLists.txt new file mode 100644 index 000000000..3aae2c755 --- /dev/null +++ b/core/libs/prx/libSceLibcInternal/CMakeLists.txt @@ -0,0 +1,23 @@ +add_library(libSceLibcInternal SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceLibcInternal PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(libSceLibcInternal PRIVATE ${CMAKE_DL_LIBS}) +configure_windows_unwind(libSceLibcInternal) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_thread_atexit_tests ${LIBS_INCLUDE_DIR}/tests/GuestThreadAtexit.cpp) + target_include_directories(guest_thread_atexit_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_thread_atexit_tests PRIVATE libSceLibcInternal libkernel libc) + configure_windows_unwind(guest_thread_atexit_tests) + add_test(NAME guest_thread_atexit COMMAND guest_thread_atexit_tests) + set_tests_properties(guest_thread_atexit PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_thread_atexit PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceLibcInternal/Export.cpp b/core/libs/prx/libSceLibcInternal/Export.cpp index 49699b6ca..c84ee09a1 100644 --- a/core/libs/prx/libSceLibcInternal/Export.cpp +++ b/core/libs/prx/libSceLibcInternal/Export.cpp @@ -1,5 +1,69 @@ +#include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/HeapDiagnostics.hpp" +#include "prx/libc/include/HostThreadLocal.hpp" +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +extern "C" void APS5_VABI sceKernelSetThreadDtors(thread_dtors_func_t dtors); + +namespace { + +using ThreadDestructorFunction = void (APS5_VABI*)(void*); + +struct ThreadDestructor { + ThreadDestructorFunction function; + void* object; + void* dsoSymbol; +}; + +struct ThreadDestructorsTag {}; + +std::vector& ThreadDestructors() { + return HostThreadLocal, ThreadDestructorsTag>(); +} + +bool IsInLoadedImage(const void* address) { + if (address == nullptr) + return false; +#ifdef _WIN32 + HMODULE module = nullptr; + return GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, static_cast(address), &module) != 0; +#else + Dl_info info{}; + return dladdr(address, &info) != 0; +#endif +} + +void APS5_VABI RunThreadDestructors_nid_no_patch() { + auto& destructors = ThreadDestructors(); + while (!destructors.empty()) { + const ThreadDestructor destructor = destructors.back(); + destructors.pop_back(); + const auto* function = reinterpret_cast(destructor.function); + if (!IsInLoadedImage(function)) { + std::ostringstream message; + message << "thread_local destructor " << function << " of dso " << destructor.dsoSymbol << " is not in a loaded image"; + throw std::runtime_error(message.str()); + } + destructor.function(destructor.object); + } +} + +void RegisterThreadExitHook() { + [[maybe_unused]] static const bool registered = [] { + sceKernelSetThreadDtors(RunThreadDestructors_nid_no_patch); + return true; + }(); +} + +} extern "C" { @@ -13,4 +77,20 @@ void APS5_VABI sceLibcHeapGetTraceInfo_nid_postfix(Info* info) { LibcHeapTraceInfo_nid_no_patch(info); } +int APS5_VABI _sceLibcInternalThreadAtexit_nid_postfix(ThreadDestructorFunction destructor, void* object, void* dsoSymbol) { + RegisterThreadExitHook(); + ThreadDestructors().push_back({destructor, object, dsoSymbol}); + return 0; +} + +void APS5_VABI _sceLibcInternalThreadDtors_nid_postfix() { + RunThreadDestructors_nid_no_patch(); +} + +int APS5_VABI _sceLibcInternalForceTlsDestructor_nid_postfix(KernelModule handle) { + (void)handle; + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/core/libs/prx/libSceMsgDialog.native/CMakeLists.txt b/core/libs/prx/libSceMsgDialog.native/CMakeLists.txt new file mode 100644 index 000000000..111746d44 --- /dev/null +++ b/core/libs/prx/libSceMsgDialog.native/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(libSceMsgDialog.native SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceMsgDialog.native PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceMsgDialog.native) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_msg_dialog_native_tests ${LIBS_INCLUDE_DIR}/tests/GuestMsgDialogNative.cpp) + target_include_directories(guest_msg_dialog_native_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_msg_dialog_native_tests PRIVATE libSceMsgDialog.native) + add_test(NAME guest_msg_dialog_native COMMAND guest_msg_dialog_native_tests) + if(WIN32) + set_tests_properties(guest_msg_dialog_native PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceMsgDialog.native/Export.cpp b/core/libs/prx/libSceMsgDialog.native/Export.cpp index 1423e8fed..027eff38d 100644 --- a/core/libs/prx/libSceMsgDialog.native/Export.cpp +++ b/core/libs/prx/libSceMsgDialog.native/Export.cpp @@ -8,10 +8,12 @@ namespace { std::atomic g_status{0}; +constexpr int COMMON_DIALOG_STATUS_RUNNING = 2; constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; constexpr int COMMON_DIALOG_RESULT_OK = 0; constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); +constexpr int COMMON_DIALOG_ERROR_NOT_RUNNING = static_cast(0x80B8000Bu); constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); constexpr int BUTTON_ID_OK = 1; } @@ -20,6 +22,12 @@ extern "C" { int SceMsgDialogNativeModuleLoaded_nid_no_patch = 1; +int APS5_VABI sceMsgDialogClose(void) { + int expected = COMMON_DIALOG_STATUS_RUNNING; + if (!g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_FINISHED)) return COMMON_DIALOG_ERROR_NOT_RUNNING; + return 0; +} + int APS5_VABI sceMsgDialogGetResult(MsgDialogResult* result) { const int status = g_status.load(); if (status == 0) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; diff --git a/core/libs/prx/libSceNpCommerce/Export.cpp b/core/libs/prx/libSceNpCommerce/Export.cpp index 6d8f3f581..6b2bd01d9 100644 --- a/core/libs/prx/libSceNpCommerce/Export.cpp +++ b/core/libs/prx/libSceNpCommerce/Export.cpp @@ -74,4 +74,9 @@ int APS5_VABI sceNpCommerceShowPsStoreIcon(int pos) { return 0; } +int APS5_VABI sceNpCommerceSetPsStoreIconLayout(int layout) { + (void)layout; + return 0; +} + } diff --git a/core/libs/prx/libSceNpSessionSignaling/CMakeLists.txt b/core/libs/prx/libSceNpSessionSignaling/CMakeLists.txt new file mode 100644 index 000000000..6446469df --- /dev/null +++ b/core/libs/prx/libSceNpSessionSignaling/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(libSceNpSessionSignaling SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceNpSessionSignaling PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceNpSessionSignaling) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_np_session_signaling_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpSessionSignaling.cpp) + target_include_directories(guest_np_session_signaling_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_session_signaling_tests PRIVATE libSceNpSessionSignaling) + add_test(NAME guest_np_session_signaling COMMAND guest_np_session_signaling_tests) + if(WIN32) + set_tests_properties(guest_np_session_signaling PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpSessionSignaling/Export.cpp b/core/libs/prx/libSceNpSessionSignaling/Export.cpp index d140181b6..eb3ee27e0 100644 --- a/core/libs/prx/libSceNpSessionSignaling/Export.cpp +++ b/core/libs/prx/libSceNpSessionSignaling/Export.cpp @@ -69,9 +69,14 @@ int32_t APS5_VABI sceNpSessionSignalingGetConnectionStatus(int32_t context_id, i int32_t APS5_VABI sceNpSessionSignalingGetLocalNetInfo(int32_t context_id, void* info) { (void)context_id; - (void)info; - NotImplemented_nid_no_patch(__func__); - return 0; + if (!info) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + return SCE_NP_SESSION_SIGNALING_ERROR_UNAVAILABLE; +} + +int APS5_VABI sceNpSessionSignalingRequestPrepare(uint32_t contextId, uint32_t* requestId) { + (void)contextId; + if (!requestId) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + return SCE_NP_SESSION_SIGNALING_ERROR_UNAVAILABLE; } int APS5_VABI sceNpSessionSignalingGetMemoryInfo(void) { diff --git a/core/libs/prx/libSceNpTrophy2/CMakeLists.txt b/core/libs/prx/libSceNpTrophy2/CMakeLists.txt index c86b204c3..06ccd898a 100644 --- a/core/libs/prx/libSceNpTrophy2/CMakeLists.txt +++ b/core/libs/prx/libSceNpTrophy2/CMakeLists.txt @@ -11,3 +11,21 @@ target_include_directories(libSceNpTrophy2 PRIVATE ) target_link_libraries(libSceNpTrophy2 PRIVATE libc) configure_windows_unwind(libSceNpTrophy2) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_nptrophy2_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpTrophy2.cpp) + target_include_directories(guest_nptrophy2_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_nptrophy2_tests PRIVATE libSceNpTrophy2 libc) + configure_windows_unwind(guest_nptrophy2_tests) + add_test(NAME guest_nptrophy2 COMMAND guest_nptrophy2_tests) + if(WIN32) + set_tests_properties(guest_nptrophy2 PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpTrophy2/src/Context.cpp b/core/libs/prx/libSceNpTrophy2/src/Context.cpp index 097a8fe0d..e259afb73 100644 --- a/core/libs/prx/libSceNpTrophy2/src/Context.cpp +++ b/core/libs/prx/libSceNpTrophy2/src/Context.cpp @@ -31,8 +31,7 @@ int APS5_VABI sceNpTrophy2RegisterContext(int context, int handle, uint64_t opti } int APS5_VABI sceNpTrophy2UnregisterUnlockCallback() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SCE_NP_TROPHY2_OK; } } diff --git a/core/libs/prx/libScePlayGoDialog/Export.cpp b/core/libs/prx/libScePlayGoDialog/Export.cpp index 5b85d52c3..16615b02e 100644 --- a/core/libs/prx/libScePlayGoDialog/Export.cpp +++ b/core/libs/prx/libScePlayGoDialog/Export.cpp @@ -74,10 +74,8 @@ int APS5_VABI scePlayGoDialogGetResult(void* result) { return 0; } -APS5_EXPORT("NOAMxY2EGS0", scePlayGoDialogUnknown00); -int APS5_VABI scePlayGoDialogUnknown00(void) { - NotImplemented_nid_no_patch("NOAMxY2EGS0"); - return 0; +int APS5_VABI scePlayGoDialogGetStatus(void) { + return g_status.load(); } } diff --git a/core/libs/prx/libScePsml_debug/CMakeLists.txt b/core/libs/prx/libScePsml_debug/CMakeLists.txt new file mode 100644 index 000000000..ee07e525e --- /dev/null +++ b/core/libs/prx/libScePsml_debug/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(libScePsml_debug SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libScePsml_debug PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libScePsml_debug) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_psml_tests ${LIBS_INCLUDE_DIR}/tests/GuestPsml.cpp) + target_include_directories(guest_psml_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_psml_tests PRIVATE libScePsml_debug) + add_test(NAME guest_psml COMMAND guest_psml_tests) + if(WIN32) + set_tests_properties(guest_psml PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libScePsml_debug/Export.cpp b/core/libs/prx/libScePsml_debug/Export.cpp index 67d7cf76b..2e8ee6e64 100644 --- a/core/libs/prx/libScePsml_debug/Export.cpp +++ b/core/libs/prx/libScePsml_debug/Export.cpp @@ -3,22 +3,45 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr std::int32_t SCE_PSML_ERROR_NOT_INITIALIZED = static_cast(0x8A810001); + +} + extern "C" { +std::int32_t APS5_VABI scePsmlMfsrGetContextBufferRequirement1100(void* requirement, const void* param) { + (void)requirement; + (void)param; + return SCE_PSML_ERROR_NOT_INITIALIZED; +} + +std::int32_t APS5_VABI scePsmlMfsrCreateContext1100(void** context, const void* param) { + (void)context; + (void)param; + return SCE_PSML_ERROR_NOT_INITIALIZED; +} + +std::int32_t APS5_VABI scePsmlMfsrGetDispatchMfsrPacket1100(void* context, void* commandBuffer, const void* param) { + (void)context; + (void)commandBuffer; + (void)param; + return SCE_PSML_ERROR_NOT_INITIALIZED; +} + APS5_EXPORT("+2KpvixvL6E", scePsmlUnknown__P2KpvixvL6E); int APS5_VABI scePsmlUnknown__P2KpvixvL6E() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("3WVD91e12ZQ", scePsmlUnknown_3WVD91e12ZQ); -int APS5_VABI scePsmlUnknown_3WVD91e12ZQ() { +int APS5_VABI scePsmlMfsrInit() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("AHalTX9wFZY", scePsmlUnknown_AHalTX9wFZY); -int APS5_VABI scePsmlUnknown_AHalTX9wFZY() { +int APS5_VABI scePsmlMfsrGetDispatchMfsrPacketSizeInDwords() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -47,8 +70,7 @@ int APS5_VABI scePsmlUnknown_GJY0MvuTcs8() { return 0; } -APS5_EXPORT("JaLBe0P3jSU", scePsmlUnknown_JaLBe0P3jSU); -int APS5_VABI scePsmlUnknown_JaLBe0P3jSU() { +int APS5_VABI scePsmlMfsrReleaseContext() { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceRazorCpu/Export.cpp b/core/libs/prx/libSceRazorCpu/Export.cpp index 83a295d21..0a5ec3953 100644 --- a/core/libs/prx/libSceRazorCpu/Export.cpp +++ b/core/libs/prx/libSceRazorCpu/Export.cpp @@ -24,4 +24,15 @@ int APS5_VABI sceRazorCpuJobManagerSequence(const void* args) { return 0; } +int APS5_VABI sceRazorCpuPushMarkerStatic(const char* name, uint32_t color, uint32_t flags) { + (void)name; + (void)color; + (void)flags; + return 0; +} + +int APS5_VABI sceRazorCpuPopMarker(void) { + return 0; +} + } diff --git a/core/libs/prx/libSceRtc/Export.cpp b/core/libs/prx/libSceRtc/Export.cpp index 3a0895ae1..08bf4bb06 100644 --- a/core/libs/prx/libSceRtc/Export.cpp +++ b/core/libs/prx/libSceRtc/Export.cpp @@ -272,6 +272,7 @@ int APS5_VABI sceRtcSetWin32FileTime(RtcDateTime* time, uint64_t win32_time) { int APS5_VABI sceRtcFormatRFC3339(char* date_time, const RtcTick* utc, int time_zone_minutes) { if (!date_time || !utc) return SCE_RTC_ERROR_INVALID_POINTER; + if (time_zone_minutes < -1439 || time_zone_minutes > 1439) return SCE_RTC_ERROR_INVALID_VALUE; RtcTick local{}; if (const int result = addTicks(&local, utc, time_zone_minutes, TICKS_PER_MINUTE); result != 0) return result; const RtcDateTime time = fromTick(local.tick); diff --git a/core/libs/prx/libSceSaveData.native/CMakeLists.txt b/core/libs/prx/libSceSaveData.native/CMakeLists.txt index 42ebeecd4..163948431 100644 --- a/core/libs/prx/libSceSaveData.native/CMakeLists.txt +++ b/core/libs/prx/libSceSaveData.native/CMakeLists.txt @@ -17,4 +17,14 @@ if(BUILD_TESTING) set_tests_properties(guest_savedata_delete PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_savedata_ps4_tests ${LIBS_INCLUDE_DIR}/tests/GuestSaveDataPs4.cpp) + target_include_directories(guest_savedata_ps4_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_savedata_ps4_tests PRIVATE libSceSaveData.native libc) + configure_windows_unwind(guest_savedata_ps4_tests) + add_test(NAME guest_savedata_ps4 COMMAND guest_savedata_ps4_tests) + if(WIN32) + set_tests_properties(guest_savedata_ps4 PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceSaveData.native/Export.cpp b/core/libs/prx/libSceSaveData.native/Export.cpp index 5c262309c..f0b798f5d 100644 --- a/core/libs/prx/libSceSaveData.native/Export.cpp +++ b/core/libs/prx/libSceSaveData.native/Export.cpp @@ -699,16 +699,24 @@ int APS5_VABI sceSaveDataUmount2(uint32_t mode, const SaveDataMountPoint* mount_ return rc; } - -APS5_EXPORT("RjMlsR8EXrw", sceSaveDataUnknown00); -int APS5_VABI sceSaveDataUnknown00(void) { - NotImplemented_nid_no_patch("RjMlsR8EXrw"); - return 0; +int APS5_VABI sceSaveDataTransferringMountPs4(const SaveDataTransferringMount* mount, SaveDataMountResult* mount_result) { + const int rc = transferringMount(mount, mount_result); + SAVEDATA_TRACE("transferringMountPs4 -> 0x%08x", static_cast(rc)); + return rc; } -APS5_EXPORT("X4MYzukPc3g", sceSaveDataUnknown01); -int APS5_VABI sceSaveDataUnknown01(void) { - NotImplemented_nid_no_patch("X4MYzukPc3g"); - return 0; +static int dirNameSearchPs4(const SaveDataDirNameSearchCond* cond, SaveDataDirNameSearchResult* result) { + if (cond == nullptr || result == nullptr) { + throw std::runtime_error("sceSaveDataDirNameSearchPs4: null argument"); + } + result->hit_num = 0; + result->set_num = 0; + return SAVE_DATA_OK; +} + +int APS5_VABI sceSaveDataDirNameSearchPs4(const SaveDataDirNameSearchCond* cond, SaveDataDirNameSearchResult* result) { + const int rc = dirNameSearchPs4(cond, result); + SAVEDATA_TRACE("dirNameSearchPs4 user=%d -> 0x%08x", cond->user_id, static_cast(rc)); + return rc; } } diff --git a/core/libs/prx/libSceShare/CMakeLists.txt b/core/libs/prx/libSceShare/CMakeLists.txt new file mode 100644 index 000000000..74ac02fb2 --- /dev/null +++ b/core/libs/prx/libSceShare/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(libSceShare SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceShare PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceShare) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_share_tests ${LIBS_INCLUDE_DIR}/tests/GuestShare.cpp) + target_include_directories(guest_share_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_share_tests PRIVATE libSceShare) + add_test(NAME guest_share COMMAND guest_share_tests) + if(WIN32) + set_tests_properties(guest_share PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceShare/Export.cpp b/core/libs/prx/libSceShare/Export.cpp index b75a31a22..caabed9b9 100644 --- a/core/libs/prx/libSceShare/Export.cpp +++ b/core/libs/prx/libSceShare/Export.cpp @@ -5,6 +5,13 @@ // Capture and sharing are not emulated; policy and parameter calls are accepted. +namespace { + +constexpr std::int32_t ERROR_NOT_SUPPORTED = static_cast(0x81960007); +constexpr std::int32_t REQUEST_ID_INVALID = -1; + +} + extern "C" { int APS5_VABI sceShareCaptureScreenshot(const void* param, int32_t* req_id) { @@ -85,10 +92,12 @@ int APS5_VABI sceShareUnregisterContentEventCallback(void* callback) { return 0; } -APS5_EXPORT("GQTObcITIXI", sceShareUnknown_GQTObcITIXI); -int32_t APS5_VABI sceShareUnknown_GQTObcITIXI(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceShareCaptureScreenshotExtended(const void* extended_param, int32_t* req_id) { + (void)extended_param; + if (req_id != nullptr) { + *req_id = REQUEST_ID_INVALID; + } + return ERROR_NOT_SUPPORTED; } } diff --git a/core/libs/prx/libSceSystemService/Export.cpp b/core/libs/prx/libSceSystemService/Export.cpp index 01a6365a5..205897fe2 100644 --- a/core/libs/prx/libSceSystemService/Export.cpp +++ b/core/libs/prx/libSceSystemService/Export.cpp @@ -80,12 +80,19 @@ int APS5_VABI sceSystemServiceParamGetInt(int paramId, int* value) { return SYSTEM_SERVICE_OK; } -int APS5_VABI sceSystemServiceParamGetString(int param_id, char* buf, size_t buf_size) { - (void)param_id; - (void)buf; - (void)buf_size; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSystemServiceParamGetString(int paramId, char* buf, size_t bufSize) { + if (buf == nullptr || bufSize == 0) { + return SYSTEM_SERVICE_ERROR_PARAMETER; + } + if (paramId != SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME) { + NotImplemented_nid_no_patch("sceSystemServiceParamGetString: parameter other than the system name"); + } + if (bufSize < SYSTEM_SERVICE_MAX_SYSTEM_NAME_LENGTH) { + NotImplemented_nid_no_patch("sceSystemServiceParamGetString: buffer shorter than 65 bytes"); + } + constexpr char SystemName[] = "PS5"; + std::memcpy(buf, SystemName, sizeof(SystemName)); + return SYSTEM_SERVICE_OK; } int APS5_VABI sceSystemServicePowerTick(void) { diff --git a/core/libs/prx/libSceSystemService/SystemService.hpp b/core/libs/prx/libSceSystemService/SystemService.hpp index 7a2c84a12..e86820da4 100644 --- a/core/libs/prx/libSceSystemService/SystemService.hpp +++ b/core/libs/prx/libSceSystemService/SystemService.hpp @@ -1,6 +1,7 @@ #ifndef CORE_LIBS_PRX_LIBSCESYSTEMSERVICE_SYSTEMSERVICE_HPP #define CORE_LIBS_PRX_LIBSCESYSTEMSERVICE_SYSTEMSERVICE_HPP +#include #include constexpr int SYSTEM_SERVICE_OK = 0; @@ -12,9 +13,12 @@ constexpr int SYSTEM_SERVICE_PARAM_ID_DATE_FORMAT = 2; constexpr int SYSTEM_SERVICE_PARAM_ID_TIME_FORMAT = 3; constexpr int SYSTEM_SERVICE_PARAM_ID_TIME_ZONE = 4; constexpr int SYSTEM_SERVICE_PARAM_ID_SUMMERTIME = 5; +constexpr int SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME = 6; constexpr int SYSTEM_SERVICE_PARAM_ID_GAME_PARENTAL_LEVEL = 7; constexpr int SYSTEM_SERVICE_PARAM_ID_ENTER_BUTTON_ASSIGN = 1000; +constexpr std::size_t SYSTEM_SERVICE_MAX_SYSTEM_NAME_LENGTH = 65; + constexpr int SYSTEM_SERVICE_PARAM_LANG_ENGLISH_US = 1; constexpr int SYSTEM_SERVICE_PARAM_DATE_FORMAT_DDMMYYYY = 1; constexpr int SYSTEM_SERVICE_PARAM_TIME_FORMAT_24HOUR = 1; diff --git a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp index ea75f8d80..adf59924a 100644 --- a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp +++ b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp @@ -62,6 +62,8 @@ static constexpr std::size_t VIDEO_OUT_FLIP_QUEUE_CAPACITY = 16; static constexpr int VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_UNCOMPRESSED = 0; static constexpr int VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_COMPRESSED = 1; +static constexpr std::uint64_t VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE = 0; +static constexpr std::uint64_t VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY = 8; static constexpr int VIDEO_OUT_EVENT_FLIP = 0; static constexpr int VIDEO_OUT_EVENT_VBLANK = 1; diff --git a/core/libs/prx/libSceVideoOut/src/Buffer.cpp b/core/libs/prx/libSceVideoOut/src/Buffer.cpp index aa40461e1..25fb6a241 100644 --- a/core/libs/prx/libSceVideoOut/src/Buffer.cpp +++ b/core/libs/prx/libSceVideoOut/src/Buffer.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -10,7 +11,7 @@ AgcDriver::DisplayBuffer DescribeVideoOutBuffer(const VideoOutBuffer& buffer, const BufferAttributeGroup& group) { const auto& attribute = group.attribute; if (attribute.reserved0 != 0 || attribute.pad0 != 0 || attribute.reserved1[0] != 0 || attribute.reserved1[1] != 0 || attribute.reserved1[2] != 0) throw std::runtime_error("VideoOut: reserved buffer attribute bits are set"); - if (attribute.tiling_mode > 1 || (attribute.tiling_mode == 0 && attribute.pitch_in_pixel != 0) || attribute.aspect_ratio != 0 || attribute.option != 0) throw std::runtime_error("VideoOut: unsupported tiling, pitch, aspect ratio or buffer option"); + if (attribute.tiling_mode > 1 || (attribute.tiling_mode == 0 && attribute.pitch_in_pixel != 0) || attribute.aspect_ratio != 0 || (attribute.option != VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE && attribute.option != VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY)) throw std::runtime_error("VideoOut: unsupported tiling " + std::to_string(attribute.tiling_mode) + ", pitch " + std::to_string(attribute.pitch_in_pixel) + ", aspect ratio " + std::to_string(attribute.aspect_ratio) + " or buffer option " + std::to_string(attribute.option)); if (group.category != VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_UNCOMPRESSED || buffer.metadataAddress != 0 || attribute.dcc_control != 0 || attribute.dcc_cb_register_clear_color != 0) throw std::runtime_error("VideoOut: DCC presentation is not implemented"); const AgcDriver::DisplayBuffer result{buffer.dataAddress, attribute.pixel_format, attribute.width, attribute.height, attribute.tiling_mode, attribute.pitch_in_pixel}; static_cast(AgcDriverDisplayBufferSize_nid_postfix(result)); diff --git a/core/libs/prx/libSceVideoOut/tests/Flip.cpp b/core/libs/prx/libSceVideoOut/tests/Flip.cpp index ad78a9798..9dd1463b0 100644 --- a/core/libs/prx/libSceVideoOut/tests/Flip.cpp +++ b/core/libs/prx/libSceVideoOut/tests/Flip.cpp @@ -162,6 +162,18 @@ void testDecode() { expectFailure([&] { AgcDriver::ReadDisplayBuffer(buffer); }); buffer.address = std::numeric_limits::max() & ~uint64_t{65535}; expectFailure([&] { AgcDriver::DisplayBufferSize(buffer); }); + const VideoOutBuffer registered{0, reinterpret_cast(tiled.data()), 0}; + BufferAttributeGroup group{}; + sceVideoOutSetBufferAttribute2(&group.attribute, 0x8000000000000000ull, 0, 259, 137, VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE, 0, 0); + group.occupied = true; + const auto plain = DescribeVideoOutBuffer(registered, group); + group.attribute.option = VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY; + const auto strict = DescribeVideoOutBuffer(registered, group); + check(strict.address == plain.address && strict.pixelFormat == plain.pixelFormat && strict.width == plain.width && strict.height == plain.height && strict.tilingMode == plain.tilingMode && strict.pitchInPixel == plain.pitchInPixel, "the STRICT_COLORIMETRY option changed the described buffer"); + for (const uint64_t option : {1ull, 32ull, 40ull}) { + group.attribute.option = option; + check(expectFailure([&] { DescribeVideoOutBuffer(registered, group); }).find("buffer option " + std::to_string(option)) != std::string::npos, "an unsupported buffer option was accepted"); + } } void testOpenParam() { diff --git a/core/libs/prx/libSceVoiceQoS/Export.cpp b/core/libs/prx/libSceVoiceQoS/Export.cpp index a0af71ff3..4cb9ab16f 100644 --- a/core/libs/prx/libSceVoiceQoS/Export.cpp +++ b/core/libs/prx/libSceVoiceQoS/Export.cpp @@ -13,4 +13,59 @@ int APS5_VABI sceVoiceQoSInit(void* mem_block, uint32_t mem_size, int32_t app_ty return 0; } +int APS5_VABI sceVoiceQoSEnd() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSCreateLocalEndpoint() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSDeleteLocalEndpoint() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSCreateRemoteEndpoint() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSDeleteRemoteEndpoint() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSConnect() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSDisconnect() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSSetLocalEndpointAttribute() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSSetRemoteEndpointAttribute() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSReadPacket() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSWritePacket() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 070d85afa..f5736578e 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -176,6 +176,15 @@ if(BUILD_TESTING) set_tests_properties(guest_strings PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_wcsrtombs_s_tests ${LIBS_INCLUDE_DIR}/tests/GuestWcsrtombsS.cpp) + target_include_directories(guest_wcsrtombs_s_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_wcsrtombs_s_tests PRIVATE libc) + configure_windows_unwind(guest_wcsrtombs_s_tests) + add_test(NAME guest_wcsrtombs_s COMMAND guest_wcsrtombs_s_tests) + if(WIN32) + set_tests_properties(guest_wcsrtombs_s PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() if(UNIX) add_test_executable(exception_runtime_tests ${LIBS_INCLUDE_DIR}/tests/ExceptionRuntime.cpp) target_include_directories(exception_runtime_tests PRIVATE ${LIBS_INCLUDE_DIR}) diff --git a/core/libs/prx/libc/Export.cpp b/core/libs/prx/libc/Export.cpp index 17128de2e..a9687082d 100644 --- a/core/libs/prx/libc/Export.cpp +++ b/core/libs/prx/libc/Export.cpp @@ -30,12 +30,6 @@ extern "C" { return 0; } -APS5_EXPORT("Pu0Ecyk-7FU", libcUnknown_Pu0Ecyk_M7FU); -int APS5_VABI libcUnknown_Pu0Ecyk_M7FU() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - // Dead import of Cyberpunk 2077 (PPSA04029): no call sites, but the // Windows loader resolves imports strictly, so it must be present. APS5_EXPORT("u2tMGOLaqnE", libcUnknown_u2tMGOLaqnE); @@ -70,12 +64,6 @@ int APS5_VABI libcCyberUnknown19(void) { } -APS5_EXPORT("2gbcltk3swE", libcCyberUnknown00); -std::uint64_t APS5_VABI libcCyberUnknown00(void) { - NotImplemented_nid_no_patch("2gbcltk3swE"); - return 0; -} - APS5_EXPORT("DiGVep5yB5w", libcCyberUnknown01); std::uint64_t APS5_VABI libcCyberUnknown01(void) { NotImplemented_nid_no_patch("DiGVep5yB5w"); @@ -94,24 +82,12 @@ std::uint64_t APS5_VABI libcCyberUnknown03(void) { return 0; } -APS5_EXPORT("79s2tnYQI6I", libcCyberUnknown04); -std::uint64_t APS5_VABI libcCyberUnknown04(void) { - NotImplemented_nid_no_patch("79s2tnYQI6I"); - return 0; -} - APS5_EXPORT("7yMFgcS8EPA", libcCyberUnknown05); std::uint64_t APS5_VABI libcCyberUnknown05(void) { NotImplemented_nid_no_patch("7yMFgcS8EPA"); return 0; } -APS5_EXPORT("Cj+Fw5q1tUo", libcCyberUnknown06); -std::uint64_t APS5_VABI libcCyberUnknown06(void) { - NotImplemented_nid_no_patch("Cj+Fw5q1tUo"); - return 0; -} - APS5_EXPORT("CyXs2l-1kNA", libcCyberUnknown07); std::uint64_t APS5_VABI libcCyberUnknown07(void) { NotImplemented_nid_no_patch("CyXs2l-1kNA"); diff --git a/core/libs/prx/libc/src/AllocatingStrings.cpp b/core/libs/prx/libc/src/AllocatingStrings.cpp index d3539587b..74779c475 100644 --- a/core/libs/prx/libc/src/AllocatingStrings.cpp +++ b/core/libs/prx/libc/src/AllocatingStrings.cpp @@ -30,6 +30,10 @@ char* APS5_VABI strndup_nid_postfix(const char* source, std::size_t limit) { } } +char* APS5_VABI strdup_nid_postfix(const char* source) { + return strndup_nid_postfix(source, static_cast(-1)); +} + int APS5_VABI asprintf_nid_postfix(char** destination, const char* format, ...) { if (!destination) { errno = 22; return -1; } *destination = nullptr; diff --git a/core/libs/prx/libc/src/GuestArena.cpp b/core/libs/prx/libc/src/GuestArena.cpp index b53450599..92f4877dc 100644 --- a/core/libs/prx/libc/src/GuestArena.cpp +++ b/core/libs/prx/libc/src/GuestArena.cpp @@ -21,7 +21,7 @@ namespace { constexpr std::uintptr_t ArenaStart = 0x0000000200000000ull; constexpr std::uintptr_t SystemReservedStart = 0x00000007FFFFC000ull; constexpr std::uintptr_t SystemReservedEnd = 0x0000001000000000ull; -constexpr std::size_t ArenaSize = 0x0000007000000000ull; +constexpr std::uintptr_t ApplicationAreaEnd = 0x000000FC00000000ull; std::uintptr_t alignUp(std::uintptr_t value, std::size_t alignment) { return (value + alignment - 1) & ~(static_cast(alignment) - 1); @@ -69,8 +69,11 @@ public: std::lock_guard lock(_lock); const auto start = reinterpret_cast(pointer); const auto next = _used.lower_bound(start); - if ((next != _used.end() && next->first < start + bytes) || (next != _used.begin() && std::prev(next)->second > start)) - throw std::runtime_error("fixed guest mapping overlaps a guest arena range"); + if ((next != _used.end() && next->first < start + bytes) || (next != _used.begin() && std::prev(next)->second > start)) { + char message[160]; + std::snprintf(message, sizeof(message), "fixed guest mapping 0x%llx+0x%zx overlaps %s", static_cast(start), bytes, OverlapsHostRegion(start, bytes) ? "a host region in the guest arena" : "a guest arena range"); + throw std::runtime_error(message); + } _used.emplace(start, start + bytes); } @@ -103,7 +106,7 @@ private: SYSTEM_INFO system{}; GetSystemInfo(&system); const std::uintptr_t granularity = system.dwAllocationGranularity; - const std::uintptr_t end = ArenaStart + ArenaSize; + const std::uintptr_t end = ApplicationAreaEnd; for (std::uintptr_t cursor = ArenaStart; cursor < end;) { MEMORY_BASIC_INFORMATION info{}; if (VirtualQuery(reinterpret_cast(cursor), &info, sizeof(info)) == 0) throw std::system_error(static_cast(GetLastError()), std::system_category(), "query the guest arena range"); @@ -197,18 +200,28 @@ bool GuestArenaHostRegionOverlaps_nid_postfix(std::uintptr_t address, std::size_ return Arena::Get().OverlapsHostRegion(address, bytes); } +namespace { + +std::invalid_argument OutsideArena(const char* operation, const void* pointer, std::size_t bytes) { + char message[128]; + std::snprintf(message, sizeof(message), "%s 0x%llx+0x%zx outside the guest arena", operation, static_cast(reinterpret_cast(pointer)), bytes); + return std::invalid_argument(message); +} + +} + void GuestArenaCommit_nid_postfix(void* pointer, std::size_t bytes, std::uint32_t protection, std::size_t granule) { - if (!Arena::Get().Contains(pointer, bytes)) throw std::invalid_argument("commit outside the guest arena"); + if (!Arena::Get().Contains(pointer, bytes)) throw OutsideArena("commit", pointer, bytes); WindowsMappings::Get().Commit(pointer, bytes, protection, granule, Arena::Get().WriteWatched()); } void GuestArenaReset_nid_postfix(void* pointer, std::size_t bytes) { - if (!Arena::Get().Contains(pointer, bytes)) throw std::invalid_argument("reset outside the guest arena"); + if (!Arena::Get().Contains(pointer, bytes)) throw OutsideArena("reset", pointer, bytes); WindowsMappings::Get().Reset(pointer, bytes); } void GuestArenaMap_nid_postfix(void* pointer, std::size_t bytes, void* section, std::uint64_t offset, std::uint32_t protection) { - if (!Arena::Get().Contains(pointer, bytes)) throw std::invalid_argument("shared mapping outside the guest arena"); + if (!Arena::Get().Contains(pointer, bytes)) throw OutsideArena("shared mapping", pointer, bytes); WindowsMappings::Get().Map(pointer, bytes, section, offset, protection); } diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 40eda10b4..f2fb249c4 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include "prx/libc/include/General.hpp" @@ -13,6 +15,7 @@ float APS5_VABI acosf_nid_postfix(float x) { return std::acos(x); } float APS5_VABI atan2f_nid_postfix(float y, float x) { return std::atan2(y, x); } float APS5_VABI tanf_nid_postfix(float x) { return std::tan(x); } float APS5_VABI log10f_nid_postfix(float x) { return std::log10(x); } +float APS5_VABI logbf_nid_postfix(float x) { return std::logb(x); } double APS5_VABI exp2_nid_postfix(double x) { return std::exp2(x); } double APS5_VABI ldexp_nid_postfix(double x, int exponent) { return std::ldexp(x, exponent); } double APS5_VABI scalbn_nid_postfix(double x, int exponent) { return std::scalbn(x, exponent); } @@ -78,6 +81,22 @@ double APS5_VABI tanh_nid_postfix(double x) { return std::tanh(x); } float APS5_VABI tanhf_nid_postfix(float x) { return std::tanh(x); } float APS5_VABI _FSinh_nid_postfix(float x, float y) { return y * std::sinh(x); } float APS5_VABI _FCosh_nid_postfix(float x, float y) { return y * std::cosh(x); } + +struct alignas(16) LibcFloatConstant { std::uint32_t bits[4]; }; +LibcFloatConstant _FInf_nid_postfix {{0x7f800000u, 0, 0, 0}}; +LibcFloatConstant _FNan_nid_postfix {{0x7fc00000u, 0, 0, 0}}; + +short APS5_VABI _FDtest_nid_postfix(const float* value) { + constexpr short Denormal = -2, Finite = -1, Zero = 0, Infinite = 1, NotANumber = 2; + if (value == nullptr) throw std::invalid_argument("_FDtest: null value"); + std::uint32_t bits; + std::memcpy(&bits, value, sizeof(bits)); + const auto exponent = bits & 0x7f800000u; + const auto fraction = bits & 0x007fffffu; + if (exponent == 0x7f800000u) return fraction != 0 ? NotANumber : Infinite; + if (exponent == 0) return fraction != 0 ? Denormal : Zero; + return Finite; +} int APS5_VABI __isnanf_nid_postfix(float x) { return std::isnan(x) ? 1 : 0; } int APS5_VABI __signbitf_nid_postfix(float x) { return std::signbit(x) ? 1 : 0; } diff --git a/core/libs/prx/libc/src/Multibyte.cpp b/core/libs/prx/libc/src/Multibyte.cpp index 0ea78acb3..c521ced22 100644 --- a/core/libs/prx/libc/src/Multibyte.cpp +++ b/core/libs/prx/libc/src/Multibyte.cpp @@ -3,6 +3,7 @@ #include #include #include +#include extern "C" { @@ -47,6 +48,78 @@ std::size_t APS5_VABI wcrtomb_nid_postfix(char* destination, std::uint16_t value return 1; } +int APS5_VABI wcsrtombs_s_nid_postfix( + std::size_t* result, char* destination, std::size_t capacity, const std::uint16_t** source, std::size_t limit, + void* state +) { + constexpr int GuestEinval = 22; + constexpr int GuestErange = 34; + constexpr int GuestEilseq = 86; + constexpr auto Failed = static_cast(-1); + if (!result || !source || !state || !*source || (destination && (static_cast(capacity) <= 0 + || static_cast(limit) < 0))) { + if (destination && static_cast(capacity) > 0) destination[0] = '\0'; + if (result) *result = Failed; + return GuestEinval; + } + if (!destination && capacity) { + *result = Failed; + return GuestEinval; + } + const auto* wide = *source; + char bytes[MB_LEN_MAX]; + if (!destination) { + std::size_t total = 0; + for (;; ++wide) { + const auto length = wcrtomb_nid_postfix(bytes, *wide, state); + if (length == Failed) { + *result = Failed; + return GuestEilseq; + } + if (length && bytes[length - 1] == '\0') { + *result = total + length - 1; + return 0; + } + total += length; + } + } + std::size_t written = 0; + while (limit) { + const auto length = wcrtomb_nid_postfix(bytes, *wide, state); + if (length == Failed) { + destination[written] = '\0'; + *result = Failed; + return GuestEilseq; + } + if (length > limit || length > capacity) { + *source = wide; + destination[0] = '\0'; + *result = Failed; + return GuestErange; + } + std::memcpy(destination + written, bytes, length); + written += length; + if (length && destination[written - 1] == '\0') { + *source = nullptr; + *result = written - 1; + return 0; + } + ++wide; + capacity -= length; + if (!capacity) { + *source = wide; + destination[0] = '\0'; + *result = Failed; + return GuestErange; + } + limit -= length; + } + *source = wide; + destination[written] = '\0'; + *result = written; + return 0; +} + std::size_t APS5_VABI mbsrtowcs_nid_postfix(std::uint16_t* destination, const char** source, std::size_t capacity, void*) { if (!destination) return std::strlen(*source); std::size_t converted = 0; diff --git a/core/libs/prx/libc/src/RuntimeSupport.cpp b/core/libs/prx/libc/src/RuntimeSupport.cpp index ef00dd455..e6bf99f61 100644 --- a/core/libs/prx/libc/src/RuntimeSupport.cpp +++ b/core/libs/prx/libc/src/RuntimeSupport.cpp @@ -93,6 +93,10 @@ unsigned int APS5_VABI _Atomic_load_4_nid_postfix(volatile unsigned int* target, return GccAtomicLoad(target); } +std::intmax_t APS5_VABI strtoimax_nid_postfix(const char* str, char** endptr, int base) { + return std::strtoimax(str, endptr, base); +} + std::uintmax_t APS5_VABI strtoumax_nid_postfix(const char* str, char** endptr, int base) { return std::strtoumax(str, endptr, base); } diff --git a/core/libs/prx/libc/src/Strings.cpp b/core/libs/prx/libc/src/Strings.cpp index 5bcad4d2a..7c9be2a2e 100644 --- a/core/libs/prx/libc/src/Strings.cpp +++ b/core/libs/prx/libc/src/Strings.cpp @@ -1,4 +1,3 @@ -#include "prx/libc/include/ApplicationHeap.hpp" #include #include #include @@ -114,6 +113,10 @@ int APS5_VABI atoi_nid_postfix(const char* str) { return std::atoi(str); } +std::div_t APS5_VABI div_nid_postfix(int numerator, int denominator) { + return std::div(numerator, denominator); +} + const wchar_t* APS5_VABI wmemchr_nid_postfix(const wchar_t* s, wchar_t c, size_t n) { return std::wmemchr(s, c, n); } @@ -156,13 +159,6 @@ int APS5_VABI strncasecmp_nid_postfix(const char* s1, const char* s2, size_t n) return static_cast(*s1) - static_cast(*s2); } -char* APS5_VABI strdup_nid_postfix(const char* s) { - std::size_t len = std::strlen(s) + 1; - char* copy = static_cast(ApplicationHeapAllocate_nid_no_patch(len)); - std::memcpy(copy, s, len); - return copy; -} - int APS5_VABI bcmp_nid_postfix(const void* s1, const void* s2, size_t n) { return std::memcmp(s1, s2, n); } diff --git a/core/libs/prx/libc/src/Time.cpp b/core/libs/prx/libc/src/Time.cpp index 64bc20ddb..53199eed7 100644 --- a/core/libs/prx/libc/src/Time.cpp +++ b/core/libs/prx/libc/src/Time.cpp @@ -21,6 +21,19 @@ int64_t APS5_VABI time_nid_postfix(int64_t* timer) { return libc_time_nid_postfix(timer); } +int64_t APS5_VABI _Xtime_get_ticks_nid_postfix() { +#ifdef _WIN32 + FILETIME ft{}; + GetSystemTimePreciseAsFileTime(&ft); + const uint64_t t = ((static_cast(ft.dwHighDateTime) << 32) | ft.dwLowDateTime) - 116444736000000000ULL; + return static_cast(t / 10); +#else + timespec now{}; + clock_gettime(CLOCK_REALTIME, &now); + return static_cast(now.tv_sec) * 1000000 + now.tv_nsec / 1000; +#endif +} + double APS5_VABI libc_difftime_nid_postfix(int64_t time1, int64_t time0) { return std::difftime(static_cast(time1), static_cast(time0)); } diff --git a/core/libs/prx/libc/src/exception/StandardExceptions.cpp b/core/libs/prx/libc/src/exception/StandardExceptions.cpp index 353538a5f..16da81ad5 100644 --- a/core/libs/prx/libc/src/exception/StandardExceptions.cpp +++ b/core/libs/prx/libc/src/exception/StandardExceptions.cpp @@ -267,6 +267,12 @@ void APS5_VABI _ZNSt11regex_errorC1ERKS__nid_postfix(LibcException::ExceptionObj void APS5_VABI _ZNSt11regex_errorC2ERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source) { LibcException::Copy(self, source, _ZTVSt11regex_error_nid_postfix); } LibcException::ExceptionObject* APS5_VABI _ZNSt11regex_erroraSERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source) { return LibcException::Assign(self, source); } +LibcException::TypeRecord _ZTISt11range_error_nid_postfix {LibcException::SingleTypeVtable + 2, "St11range_error", &_ZTISt13runtime_error_nid_postfix}; +LibcException::ExceptionVtable _ZTVSt11range_error_nid_postfix {0, &_ZTISt11range_error_nid_postfix, LibcException::DestroyMessage, LibcException::DeleteMessage, LibcException::MessageWhat}; +void APS5_VABI _ZNSt11range_errorD1Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DestroyMessage(self); } +void APS5_VABI _ZNSt11range_errorD2Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DestroyMessage(self); } +void APS5_VABI _ZNSt11range_errorD0Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DeleteMessage(self); } + LibcException::TypeRecord _ZTINSt8ios_base7failureE_nid_postfix {LibcException::SingleTypeVtable + 2, "NSt8ios_base7failureE", &_ZTISt12system_error_nid_postfix}; LibcException::ExceptionVtable _ZTVNSt8ios_base7failureE_nid_postfix {0, &_ZTINSt8ios_base7failureE_nid_postfix, LibcException::DestroyMessage, LibcException::DeleteMessage, LibcException::MessageWhat}; void APS5_VABI _ZNSt8ios_base7failureD1Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DestroyMessage(self); } @@ -281,6 +287,7 @@ const char* APS5_VABI _ZNKSt8ios_base7failure4whatEv_nid_postfix(const LibcExcep [[noreturn]] void APS5_VABI _ZSt14_Xout_of_rangePKc_nid_postfix(const char* message) { LibcException::ThrowMessage(_ZTVSt12out_of_range_nid_postfix, message); } [[noreturn]] void APS5_VABI _ZSt14_Xlength_errorPKc_nid_postfix(const char* message) { LibcException::ThrowMessage(_ZTVSt12length_error_nid_postfix, message); } [[noreturn]] void APS5_VABI _ZSt18_Xinvalid_argumentPKc_nid_postfix(const char* message) { LibcException::ThrowMessage(_ZTVSt16invalid_argument_nid_postfix, message); } +[[noreturn]] void APS5_VABI _ZSt13_Xrange_errorPKc_nid_postfix(const char* message) { LibcException::ThrowMessage(_ZTVSt11range_error_nid_postfix, message); } [[noreturn]] void APS5_VABI _ZSt19_Xbad_function_callv_nid_postfix() { LibcException::ThrowPlain(_ZTVSt17bad_function_call_nid_postfix); } [[noreturn]] void APS5_VABI _ZSt13_Xregex_errorNSt15regex_constants10error_typeE_nid_postfix(std::regex_constants::error_type code) { struct RegexObject { LibcException::ExceptionObject base; std::regex_constants::error_type code; }; diff --git a/core/libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp b/core/libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp index 89b53c3f2..aa36abe72 100644 --- a/core/libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp +++ b/core/libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp @@ -1,6 +1,7 @@ #include "prx/libkernel/AppMetadata/include/ParamJsonParser.hpp" #include +#include #include #include #include @@ -21,6 +22,7 @@ struct JsonValue { JsonType type = JsonType::Null; bool boolValue = false; double numberValue = 0.0; + std::string numberText; std::string stringValue; JsonArray arrayValue; JsonObject objectValue; @@ -193,7 +195,8 @@ JsonValue parseJsonNumber(const std::string& text, std::size_t& position) { } JsonValue value; value.type = JsonType::Number; - value.numberValue = std::stod(text.substr(start, position - start)); + value.numberText = text.substr(start, position - start); + value.numberValue = std::stod(value.numberText); return value; } @@ -261,7 +264,14 @@ ParsedParamJson parseParamJson(const std::filesystem::path& paramJsonPath) { result.titleId = titleId; if (const JsonValue* downloadData = findObjectMember(root, "downloadDataSize")) { if (downloadData->type != JsonType::Number || downloadData->numberValue < 0) throw std::runtime_error("param.json downloadDataSize is not a size"); - result.downloadDataSizeMiB = static_cast(downloadData->numberValue); + if (downloadData->numberText.find_first_of("-.eE") == std::string::npos) { + const auto& number = downloadData->numberText; + const auto parsed = std::from_chars(number.data(), number.data() + number.size(), result.downloadDataSizeMiB); + if (parsed.ec != std::errc{} || parsed.ptr != number.data() + number.size()) throw std::runtime_error("param.json downloadDataSize exceeds uint64 range"); + } else { + if (downloadData->numberValue >= 0x1p64) throw std::runtime_error("param.json downloadDataSize exceeds uint64 range"); + result.downloadDataSizeMiB = static_cast(downloadData->numberValue); + } } return result; } diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 2e5364b1b..43949deb4 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -70,6 +70,15 @@ if(MINGW) endif() if(BUILD_TESTING) + add_test_executable(param_json_parser_tests ${LIBS_INCLUDE_DIR}/tests/ParamJsonParser.cpp) + target_include_directories(param_json_parser_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(param_json_parser_tests PRIVATE libkernel libc) + configure_windows_unwind(param_json_parser_tests) + add_test(NAME param_json_parser COMMAND param_json_parser_tests) + if(WIN32) + set_tests_properties(param_json_parser PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_posix_sem_tests ${LIBS_INCLUDE_DIR}/tests/GuestPosixSem.cpp) target_include_directories(guest_posix_sem_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_posix_sem_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index 25147d08a..ef114beba 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -226,7 +226,12 @@ public: #ifdef _WIN32 const auto size = static_cast(bytes); section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE, static_cast(size >> 32), static_cast(size), nullptr); - if (!section) throw std::system_error(static_cast(GetLastError()), std::system_category(), "create direct memory backing"); + if (!section) { + const auto error = static_cast(GetLastError()); + char message[96]; + std::snprintf(message, sizeof(message), "create direct memory backing of 0x%llx bytes (%llu MiB)", static_cast(size), static_cast((size + 0xFFFFF) >> 20)); + throw std::system_error(error, std::system_category(), message); + } #else file = memfd_create("direct memory", MFD_CLOEXEC); if (file < 0) throw std::system_error(errno, std::generic_category(), "create direct memory backing"); diff --git a/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp b/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp index 87944ae04..733ae7c41 100644 --- a/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp +++ b/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp @@ -25,23 +25,16 @@ struct AddressWaiter { std::mutex g_waitersLock; std::unordered_map> g_waiters; -bool IsWordAddress(std::uintptr_t address) { - return address != 0 && address % alignof(std::uint32_t) == 0; +template +bool IsAlignedAddress(std::uintptr_t address) { + return address != 0 && address % alignof(TValue) == 0; } -} // namespace - -extern "C" { - -int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name) { - (void)name; +template +int WaitOnAddress(TValue* address, TValue expected, const KernelUseconds* timeout, const void* caller) { const auto key = reinterpret_cast(address); - if (!IsWordAddress(key)) { - APS5_INVALID_ARG_EX; - } - std::unique_lock lock(g_waitersLock); - if (std::atomic_ref(*address).load() != expected) { + if (std::atomic_ref(*address).load() != expected) { return SYNC_ON_ADDRESS_OK; } @@ -65,13 +58,41 @@ int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t e } } lock.unlock(); - KernelTraceWait_nid_postfix("addr", __builtin_return_address(0), static_cast(waited.count()), !woken); + KernelTraceWait_nid_postfix("addr", caller, static_cast(waited.count()), !woken); return woken ? SYNC_ON_ADDRESS_OK : SCE_KERNEL_ERROR_ETIMEDOUT; } +} // namespace + +extern "C" { + +int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name) { + (void)name; + if (!IsAlignedAddress(reinterpret_cast(address))) { + APS5_INVALID_ARG_EX; + } + return WaitOnAddress(address, expected, timeout, __builtin_return_address(0)); +} + +int APS5_VABI sceKernelSyncOnAddressWait32(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name) { + (void)name; + if (!IsAlignedAddress(reinterpret_cast(address))) { + APS5_INVALID_ARG_EX; + } + return WaitOnAddress(address, expected, timeout, __builtin_return_address(0)); +} + +int APS5_VABI sceKernelSyncOnAddressWait64(std::uint64_t* address, std::uint64_t expected, const KernelUseconds* timeout, const char* name) { + (void)name; + if (!IsAlignedAddress(reinterpret_cast(address))) { + APS5_INVALID_ARG_EX; + } + return WaitOnAddress(address, expected, timeout, __builtin_return_address(0)); +} + int APS5_VABI sceKernelSyncOnAddressWake(void* address, std::int32_t count) { const auto key = reinterpret_cast(address); - if (!IsWordAddress(key) || count < 0) { + if (!IsAlignedAddress(key) || count < 0) { APS5_INVALID_ARG_EX; } diff --git a/core/libs/prx/libkernel/System/src/Coredump.cpp b/core/libs/prx/libkernel/System/src/Coredump.cpp index 710d956cd..27efa3e65 100644 --- a/core/libs/prx/libkernel/System/src/Coredump.cpp +++ b/core/libs/prx/libkernel/System/src/Coredump.cpp @@ -31,8 +31,7 @@ int APS5_VABI sceKernelDebugWriteCppExceptionInfo(const void* exception, uint64_ } -APS5_EXPORT("5nc2gdLNsok", sceCoredumpUnknown00); -int APS5_VABI sceCoredumpUnknown00(void) { +int APS5_VABI sceCoredumpAttachUserFile(void) { NotImplemented_nid_no_patch("5nc2gdLNsok"); return 0; } @@ -43,26 +42,22 @@ int APS5_VABI sceCoredumpUnknown01(void) { return 0; } -APS5_EXPORT("MEJ7tc7ThwM", sceCoredumpUnknown02); -int APS5_VABI sceCoredumpUnknown02(void) { +int APS5_VABI sceCoredumpAttachMemoryRegionAsUserFile(void) { NotImplemented_nid_no_patch("MEJ7tc7ThwM"); return 0; } -APS5_EXPORT("Uxqkdta7wEg", sceCoredumpUnknown03); -int APS5_VABI sceCoredumpUnknown03(void) { +int APS5_VABI sceCoredumpSetUserDataType(void) { NotImplemented_nid_no_patch("Uxqkdta7wEg"); return 0; } -APS5_EXPORT("dei8oUx6DbU", sceCoredumpUnknown04); -int APS5_VABI sceCoredumpUnknown04(void) { +int APS5_VABI sceCoredumpDebugTextOut(void) { NotImplemented_nid_no_patch("dei8oUx6DbU"); return 0; } -APS5_EXPORT("kK0DUW1Ukgc", sceCoredumpUnknown05); -int APS5_VABI sceCoredumpUnknown05(void) { +int APS5_VABI sceCoredumpGetStopInfoCpu(void) { NotImplemented_nid_no_patch("kK0DUW1Ukgc"); return 0; } diff --git a/core/libs/prx/libkernel/Unimplemented.cpp b/core/libs/prx/libkernel/Unimplemented.cpp index c969fc55e..96466573a 100644 --- a/core/libs/prx/libkernel/Unimplemented.cpp +++ b/core/libs/prx/libkernel/Unimplemented.cpp @@ -10,14 +10,12 @@ int APS5_VABI sceCoredumpWriteUserData() { return 0; } -APS5_EXPORT("vNe1w4diLCs", sceKernelUnknown00); -int APS5_VABI sceKernelUnknown00(void) { +int APS5_VABI __tls_get_addr_nid_postfix(void) { NotImplemented_nid_no_patch("vNe1w4diLCs"); return 0; } -APS5_EXPORT("teiItL2boFw", sceKernelUnknown01); -int APS5_VABI sceKernelUnknown01(void) { +int APS5_VABI sceKernelReleaseFlexibleMemory(void) { NotImplemented_nid_no_patch("teiItL2boFw"); return 0; } @@ -25,8 +23,7 @@ int APS5_VABI sceKernelUnknown01(void) { // Canonical lib is libScePosix (dead import of Cyberpunk 2077): 35 of its // 36 sibling imports resolve to libkernel, and libScePosix is not in the // game's NEEDED list so only a NEEDED module can satisfy the loader here. -APS5_EXPORT("0D4-FVvEikw", sceKernelUnknown02); -int APS5_VABI sceKernelUnknown02(void) { +int APS5_VABI pthread_cancel_nid_postfix(void) { NotImplemented_nid_no_patch("0D4-FVvEikw"); return 0; } diff --git a/core/libs/prx/ulobjmgr/CMakeLists.txt b/core/libs/prx/ulobjmgr/CMakeLists.txt new file mode 100644 index 000000000..84d347d1a --- /dev/null +++ b/core/libs/prx/ulobjmgr/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(ulobjmgr SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(ulobjmgr PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(ulobjmgr) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_ulobjmgr_tests ${LIBS_INCLUDE_DIR}/tests/GuestUlobjmgr.cpp) + target_include_directories(guest_ulobjmgr_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_ulobjmgr_tests PRIVATE ulobjmgr) + add_test(NAME guest_ulobjmgr COMMAND guest_ulobjmgr_tests) + if(WIN32) + set_tests_properties(guest_ulobjmgr PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/ulobjmgr/Export.cpp b/core/libs/prx/ulobjmgr/Export.cpp index 528a0dfc2..25b7f547b 100644 --- a/core/libs/prx/ulobjmgr/Export.cpp +++ b/core/libs/prx/ulobjmgr/Export.cpp @@ -3,17 +3,23 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int ULOBJMGR_EINVAL = 22; +constexpr std::uint32_t ULOBJMGR_ID_LIMIT = 0x4000; + +} + extern "C" { -int APS5_VABI _sceUlobjmgrRegisterObject(void* object) { - (void)object; - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _sceUlobjmgrRegisterObject(std::uint64_t object, std::int32_t kind, std::uint32_t* id) { + if (object == 0 || kind == 0 || id == nullptr) return ULOBJMGR_EINVAL; + *id = 0; return 0; } -int APS5_VABI _sceUlobjmgrUnregisterObject(void* object) { - (void)object; - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _sceUlobjmgrUnregisterObject(std::uint32_t id) { + if (id >= ULOBJMGR_ID_LIMIT) return ULOBJMGR_EINVAL; return 0; } diff --git a/core/libs/tests/ApplicationHeap.cpp b/core/libs/tests/ApplicationHeap.cpp index 3fb557ed1..0f08924bb 100644 --- a/core/libs/tests/ApplicationHeap.cpp +++ b/core/libs/tests/ApplicationHeap.cpp @@ -14,6 +14,9 @@ void APS5_VABI _ZdlPv_nid_postfix(void*); void APS5_VABI _ZdaPv_nid_postfix(void*); void APS5_VABI _ZdlPvSt11align_val_t_nid_postfix(void*, std::size_t); void* ApplicationHeapRealign_nid_no_patch(void*, std::size_t, std::size_t); +char* APS5_VABI strdup_nid_postfix(const char*); +char* APS5_VABI strndup_nid_postfix(const char*, std::size_t); +int* APS5_VABI __error_nid_postfix(); } namespace { @@ -192,4 +195,23 @@ int main(int argc, char** argv) { reject([] { ApplicationHeapAllocate_nid_no_patch(64); }); recurse = false; require(ApplicationHeapAllocate_nid_no_patch(64) == storage.data()); + const char text[] = "guest string"; + char* copy = strdup_nid_postfix(text); + require(copy == reinterpret_cast(storage.data()) && lastSize == sizeof(text)); + require(std::strcmp(copy, text) == 0 && copy != text); + ApplicationHeapFree_nid_no_patch(copy); + copy = strdup_nid_postfix(""); + require(lastSize == 1 && copy[0] == '\0'); + ApplicationHeapFree_nid_no_patch(copy); + copy = strndup_nid_postfix(text, 5); + require(lastSize == 6 && std::strcmp(copy, "guest") == 0); + ApplicationHeapFree_nid_no_patch(copy); + fail = true; + *__error_nid_postfix() = 0; + require(strdup_nid_postfix(text) == nullptr && *__error_nid_postfix() == 12); + *__error_nid_postfix() = 0; + require(strdup_nid_postfix("") == nullptr && *__error_nid_postfix() == 12); + *__error_nid_postfix() = 0; + require(strndup_nid_postfix(text, 5) == nullptr && *__error_nid_postfix() == 12); + fail = false; } diff --git a/core/libs/tests/ExceptionRuntime.cpp b/core/libs/tests/ExceptionRuntime.cpp index 2e50cd0ba..bb8df2c16 100644 --- a/core/libs/tests/ExceptionRuntime.cpp +++ b/core/libs/tests/ExceptionRuntime.cpp @@ -17,6 +17,7 @@ extern "C" void __cxa_decrement_exception_refcount_nid_postfix(void*); extern "C" void __cxa_rethrow_primary_exception_nid_postfix(void*); extern "C" [[noreturn]] void _ZSt14_Xout_of_rangePKc_nid_postfix(const char*); +extern "C" [[noreturn]] void _ZSt13_Xrange_errorPKc_nid_postfix(const char*); extern "C" [[noreturn]] void __cxa_bad_cast_nid_postfix(); extern "C" [[noreturn]] void _ZNKSt9exception6_RaiseEv_nid_postfix(const void*); extern "C" void* __cxa_vec_new3_nid_postfix(std::size_t, std::size_t, std::size_t, void(*)(void*), void(*)(void*), void*(*)(std::size_t), void(*)(void*, std::size_t)); @@ -190,6 +191,13 @@ int main() { try { throw Virtual(); } catch (Base& value) { assert(value.value == 7); } try { _ZSt14_Xout_of_rangePKc_nid_postfix("test message"); } catch (const std::logic_error& value) { assert(std::strcmp(value.what(), "test message") == 0); } + try { _ZSt13_Xrange_errorPKc_nid_postfix("range message"); } + catch (const std::range_error& value) { if (std::strcmp(value.what(), "range message") != 0) std::abort(); } + try { _ZSt13_Xrange_errorPKc_nid_postfix(nullptr); } + catch (const std::runtime_error& value) { if (std::strcmp(value.what(), "") != 0) std::abort(); } + try { _ZSt13_Xrange_errorPKc_nid_postfix("not a logic error"); } + catch (const std::logic_error&) { std::abort(); } + catch (const std::range_error&) {} try { __cxa_bad_cast_nid_postfix(); } catch (const std::exception& value) { assert(value.what() != nullptr); } try { _ZNKSt9exception6_RaiseEv_nid_postfix(nullptr); assert(false); } diff --git a/core/libs/tests/GuestAjm.cpp b/core/libs/tests/GuestAjm.cpp index 8d8504257..71524e885 100644 --- a/core/libs/tests/GuestAjm.cpp +++ b/core/libs/tests/GuestAjm.cpp @@ -321,6 +321,24 @@ void TestCodecInfo(std::uint32_t context) { Require(sceAjmInstanceDestroy(context, instance) == 0); } +void TestBatchWaitRelease(std::uint32_t context) { + constexpr int invalidBatch = static_cast(0x80930004); + std::vector batch(64); + AjmBatchInfo info{}; + AjmBatchError error{}; + std::uint32_t first = 0; + std::uint32_t second = 0; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchStart(context, &info, 0, &error, &first) == 0); + Require(sceAjmBatchStart(context, &info, 0, &error, &second) == 0 && second != first); + Require(sceAjmBatchWait(context, second, 0, &error) == 0); + Require(sceAjmBatchWait(context, first, 0, &error) == 0); + Require(sceAjmBatchWait(context, first, 0, &error) == invalidBatch); + Require(sceAjmBatchWait(context, second, 0, &error) == invalidBatch); + Require(sceAjmBatchWait(context, 0, 0, &error) == invalidBatch); + Require(sceAjmBatchWait(context, second + 1, 0, &error) == invalidBatch); +} + } int main() { @@ -343,5 +361,6 @@ int main() { TestDecodeSingle(context); TestGaplessDecode(context); TestCodecInfo(context); + TestBatchWaitRelease(context); Require(sceAjmFinalize(context) == 0); } diff --git a/core/libs/tests/GuestFreebsdClocks.cpp b/core/libs/tests/GuestFreebsdClocks.cpp index 6c7f959f0..75a69daa6 100644 --- a/core/libs/tests/GuestFreebsdClocks.cpp +++ b/core/libs/tests/GuestFreebsdClocks.cpp @@ -8,6 +8,8 @@ int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res); int APS5_VABI sceKernelClockGettime(KernelClockid clockId, KernelTimespec* tp); int APS5_VABI sceKernelClockGetres(KernelClockid clockId, KernelTimespec* tp); int APS5_VABI sceKernelUsleep_nid_postfix(KernelUseconds microseconds); +int APS5_VABI gettimeofday_nid_postfix(KernelTimeval* tv, KernelTimezone* tz); +std::int64_t APS5_VABI _Xtime_get_ticks_nid_postfix(); } static constexpr int SCE_OK = 0; @@ -120,6 +122,21 @@ static void IdleProcessAccumulatesNone() { Require(Nanos(GUEST_CLOCK_PROF) - profStart <= IDLE_LIMIT_NANOS); } +static std::int64_t Microseconds() { + KernelTimeval time{-1, -1}; + Require(gettimeofday_nid_postfix(&time, nullptr) == SCE_OK); + return time.tv_sec * 1000000LL + time.tv_usec; +} + +static void XtimeTicksAreGettimeofdayMicroseconds() { + for (int i = 0; i < 100; ++i) { + const std::int64_t before = Microseconds(); + const std::int64_t ticks = _Xtime_get_ticks_nid_postfix(); + const std::int64_t after = Microseconds(); + Require(before <= ticks && ticks <= after); + } +} + int main() { SecondClockReportsWholeSeconds(); @@ -135,4 +152,5 @@ int main() { ProcessClocksAreOrdered(); BusyProcessAccumulatesUserTime(); IdleProcessAccumulatesNone(); + XtimeTicksAreGettimeofdayMicroseconds(); } diff --git a/core/libs/tests/GuestHttp.cpp b/core/libs/tests/GuestHttp.cpp index fa1ae4a63..6800c55c8 100644 --- a/core/libs/tests/GuestHttp.cpp +++ b/core/libs/tests/GuestHttp.cpp @@ -7,6 +7,7 @@ extern "C" { int APS5_VABI sceHttpUriParse(SceHttpUriElement*, const char*, void*, std::size_t*, std::size_t); +int APS5_VABI sceHttpSetInflateGZIPEnabled(int, int); int APS5_VABI sceHttpUriBuild(char*, std::size_t*, std::size_t, const SceHttpUriElement*, std::uint32_t); int APS5_VABI sceHttpUriEscape(char*, std::size_t*, std::size_t, const char*); int APS5_VABI sceHttpCreateEpoll(int, HttpEpollHandle*); @@ -16,6 +17,7 @@ int APS5_VABI sceHttpCreateRequest2(int, const char*, const char*, std::uint64_t int APS5_VABI sceHttpsEnableOption(int, std::uint32_t); int APS5_VABI sceHttpsLoadCert(int, int, void*, void*, void*); int APS5_VABI sceHttpGetLastErrno(int, int*); +int APS5_VABI sceHttpParseStatusLine(const char*, std::size_t, std::int32_t*, std::int32_t*, std::int32_t*, const char**, std::size_t*); } static void Require(bool value) { if (!value) std::abort(); } @@ -97,10 +99,61 @@ int main() { Require(sceHttpReadData(1, data, sizeof(data)) == network); Require(sceHttpCreateRequest2(1, "GET", "/", 0) > 0); + Require(sceHttpSetInflateGZIPEnabled(1, 0) == 0); + Require(sceHttpSetInflateGZIPEnabled(1, 1) == 0); + Require(sceHttpSetInflateGZIPEnabled(1, 2) == invalidValue); + Require(sceHttpSetInflateGZIPEnabled(1, -1) == invalidValue); Require(sceHttpsEnableOption(1, 0) == 0); Require(sceHttpsLoadCert(1, 0, nullptr, nullptr, nullptr) == 0); int httpErrno = -1; Require(sceHttpGetLastErrno(1, &httpErrno) == 0); Require(httpErrno == 0); Require(sceHttpGetLastErrno(1, nullptr) == invalidValue); + + constexpr int parseInvalidResponse = static_cast(0x80432060); + constexpr int parseInvalidValue = static_cast(0x804321FE); + std::int32_t major = -1; + std::int32_t minor = -1; + std::int32_t code = -1; + const char* phrase = nullptr; + std::size_t phraseLength = 0; + auto parse = [&](const char* line, std::size_t length) { + return sceHttpParseStatusLine(line, length, &major, &minor, &code, &phrase, &phraseLength); + }; + + const char* response = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"; + Require(parse(response, std::strlen(response)) == 17); + Require(major == 1 && minor == 1 && code == 200); + Require(phrase == response + 12 && phraseLength == 3 && std::strncmp(phrase, " OK", 3) == 0); + + const char* lineFeedOnly = "HTTP/10.25 404 Not Found\n"; + Require(parse(lineFeedOnly, std::strlen(lineFeedOnly)) == 25); + Require(major == 10 && minor == 25 && code == 404); + Require(phrase == lineFeedOnly + 14 && phraseLength == 10); + + const char* noPhrase = "HTTP/2.0 204\r\n"; + Require(parse(noPhrase, std::strlen(noPhrase)) == 14); + Require(major == 2 && minor == 0 && code == 204); + Require(phrase == noPhrase + 12 && phraseLength == 0); + + phrase = nullptr; + phraseLength = 0; + Require(parse(nullptr, 17) == parseInvalidResponse); + Require(sceHttpParseStatusLine(response, 17, nullptr, &minor, &code, &phrase, &phraseLength) == parseInvalidValue); + Require(sceHttpParseStatusLine(response, 17, &major, &minor, &code, &phrase, nullptr) == parseInvalidValue); + major = -1; + minor = -1; + Require(parse("HTTX/1.1 200 OK\n", 16) == parseInvalidResponse); + Require(major == 0 && minor == 0); + Require(parse("HTTP/1.1", 7) == parseInvalidResponse); + Require(parse("HTTP/123", 8) == parseInvalidResponse); + Require(parse("HTTP/x.1 200 OK\n", 16) == parseInvalidResponse); + Require(parse("HTTP/1 200 OK\n", 14) == parseInvalidResponse); + Require(parse("HTTP/1. 200 OK\n", 15) == parseInvalidResponse); + Require(parse("HTTP/1.1/200 OK\n", 16) == parseInvalidResponse); + Require(parse("HTTP/1.1 2x0 OK\n", 16) == parseInvalidResponse); + Require(parse("HTTP/1.1 20", 11) == parseInvalidResponse); + Require(parse(response, 15) == parseInvalidResponse); + Require(parse(response, 16) == parseInvalidResponse); + Require(phrase == nullptr && phraseLength == 0); } diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 7ef93b7ec..eaf3cc121 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include extern "C" { double APS5_VABI atof_nid_postfix(const char*); @@ -10,13 +11,19 @@ float APS5_VABI strtof_nid_postfix(const char*, char**); long double APS5_VABI strtold_nid_postfix(const char*, char**); std::int64_t APS5_VABI strtol_nid_postfix(const char*, char**, int); std::uint64_t APS5_VABI strtoul_nid_postfix(const char*, char**, int); +std::intmax_t APS5_VABI strtoimax_nid_postfix(const char*, char**, int); int* APS5_VABI __error_nid_postfix(); +struct LibcFloatConstant { std::uint32_t bits[4]; }; +extern LibcFloatConstant _FInf_nid_postfix; +extern LibcFloatConstant _FNan_nid_postfix; +short APS5_VABI _FDtest_nid_postfix(const float*); float APS5_VABI fmodf_nid_postfix(float, float); float APS5_VABI asinf_nid_postfix(float); float APS5_VABI acosf_nid_postfix(float); float APS5_VABI atan2f_nid_postfix(float, float); float APS5_VABI tanf_nid_postfix(float); float APS5_VABI log10f_nid_postfix(float); +float APS5_VABI logbf_nid_postfix(float); double APS5_VABI exp2_nid_postfix(double); double APS5_VABI ldexp_nid_postfix(double, int); double APS5_VABI scalbn_nid_postfix(double, int); @@ -24,6 +31,7 @@ float APS5_VABI scalbnf_nid_postfix(float, int); double APS5_VABI frexp_nid_postfix(double, int*); float APS5_VABI frexpf_nid_postfix(float, int*); std::int64_t APS5_VABI lround_nid_postfix(double); +std::div_t APS5_VABI div_nid_postfix(int, int); std::int64_t APS5_VABI lroundf_nid_postfix(float); std::int64_t APS5_VABI llround_nid_postfix(double); int APS5_VABI __isfinitef_nid_postfix(float); @@ -69,6 +77,12 @@ static void CheckIntegerConversions() { std::fprintf(stderr, "Guest strtol failed for '%s' in base %d\n", test.text, test.base); std::abort(); } + *__error_nid_postfix() = 0; + const auto maxValue = strtoimax_nid_postfix(test.text, &end, test.base); + if (maxValue != test.value || end != test.text + test.consumed || *__error_nid_postfix() != test.error) { + std::fprintf(stderr, "Guest strtoimax failed for '%s' in base %d\n", test.text, test.base); + std::abort(); + } } struct UnsignedCase { const char* text; @@ -113,7 +127,28 @@ static void CheckIntegerConversions() { *__error_nid_postfix() = 0; } +static void CheckFloatClassification() { + Require(_FInf_nid_postfix.bits[0] == 0x7f800000u && _FNan_nid_postfix.bits[0] == 0x7fc00000u); + for (int word = 1; word < 4; ++word) Require(_FInf_nid_postfix.bits[word] == 0 && _FNan_nid_postfix.bits[word] == 0); + const struct { std::uint32_t bits; short code; } cases[] = { + {0x00000000u, 0}, {0x80000000u, 0}, {0x00000001u, -2}, {0x807fffffu, -2}, {0x00800000u, -1}, + {0xbf800000u, -1}, {0x7f7fffffu, -1}, {0x7f800000u, 1}, {0xff800000u, 1}, {0x7f800001u, 2}, + {0x7fc00000u, 2}, {0xff810000u, 2}, {0x7f810000u, 2}, + }; + for (const auto& test : cases) { + float value; + std::memcpy(&value, &test.bits, sizeof(value)); + if (_FDtest_nid_postfix(&value) != test.code) { + std::fprintf(stderr, "Guest _FDtest failed for %08x\n", test.bits); + std::abort(); + } + } + Require(_FDtest_nid_postfix(reinterpret_cast(&_FInf_nid_postfix)) == 1); + Require(_FDtest_nid_postfix(reinterpret_cast(&_FNan_nid_postfix)) == 2); +} + int main() { + CheckFloatClassification(); CheckIntegerConversions(); Require(atof_nid_postfix(" -12.5tail") == -12.5); char* end = nullptr; @@ -134,6 +169,11 @@ int main() { Require(std::abs(atan2f_nid_postfix(1.f, -1.f) - 2.3561945f) < 0.000001f); Require(tanf_nid_postfix(0.f) == 0.f); Require(log10f_nid_postfix(100.f) == 2.f); + Require(logbf_nid_postfix(8.f) == 3.f && logbf_nid_postfix(-0.75f) == -1.f); + Require(logbf_nid_postfix(std::numeric_limits::denorm_min()) == -149.f); + Require(logbf_nid_postfix(0.f) == -std::numeric_limits::infinity()); + Require(logbf_nid_postfix(-std::numeric_limits::infinity()) == std::numeric_limits::infinity()); + Require(std::isnan(logbf_nid_postfix(std::numeric_limits::quiet_NaN()))); Require(exp2_nid_postfix(-3.) == 0.125); Require(ldexp_nid_postfix(0.75, 4) == 12.); Require(scalbn_nid_postfix(0.75, -2) == 0.1875); @@ -156,4 +196,12 @@ int main() { Require(__isnormalf_nid_postfix(std::numeric_limits::denorm_min()) == 0); Require(__isnormal_nid_postfix(1.) == 1 && __isnormal_nid_postfix(0.) == 0); Require(__isnormal_nid_postfix(std::numeric_limits::denorm_min()) == 0); + const auto quotient = div_nid_postfix(7, 2); + Require(quotient.quot == 3 && quotient.rem == 1); + const auto negativeNumerator = div_nid_postfix(-7, 2); + Require(negativeNumerator.quot == -3 && negativeNumerator.rem == -1); + const auto negativeDenominator = div_nid_postfix(7, -2); + Require(negativeDenominator.quot == -3 && negativeDenominator.rem == 1); + const auto minimum = div_nid_postfix(std::numeric_limits::min(), 10); + Require(minimum.quot == -214748364 && minimum.rem == -8); } diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp index 4b6c3cbcd..f7ba8fa38 100644 --- a/core/libs/tests/GuestMemory.cpp +++ b/core/libs/tests/GuestMemory.cpp @@ -236,6 +236,38 @@ static void CheckNoOverwriteRejectsHostOccupiedMapping() { GuestArena::GuestArenaReset_nid_postfix(target, page); Require(sceKernelReleaseDirectMemory(phys, page) == 0); } + +static void CheckFixedMappingsReachTheApplicationAreaEnd() { + constexpr std::size_t page = 0x4000; + constexpr std::uintptr_t applicationAreaEnd = 0xFC00000000ull; + std::uintptr_t base = 0; + std::size_t size = 0; + GuestArena::GuestArenaRange_nid_postfix(&base, &size); + Require(base == 0x200000000ull && base + size == applicationAreaEnd); + std::int64_t phys = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0); + void* const requested = reinterpret_cast(applicationAreaEnd - page * 2); + void* mapped = requested; + Require(sceKernelMapDirectMemory(&mapped, page * 2, 3, 0x90, phys, 0) == 0); + Require(mapped == requested); + static_cast(mapped)[page * 2 - 1] = 7; + void* alias = nullptr; + Require(sceKernelMapDirectMemory(&alias, page, 3, 0, phys + page, 0) == 0); + Require(static_cast(alias)[page - 1] == 7); + VirtualQueryInfo info{}; + Require(sceKernelVirtualQuery(mapped, 0, &info, sizeof(info)) == 0 && info.is_direct && info.end == applicationAreaEnd); + void* beyond = reinterpret_cast(applicationAreaEnd); + bool refused = false; + try { + refused = sceKernelMapDirectMemory(&beyond, page, 3, 0x10, phys, 0) != 0; + } catch (const std::exception&) { + refused = true; + } + Require(refused && beyond == reinterpret_cast(applicationAreaEnd)); + Require(sceKernelMunmap(alias, page) == 0); + Require(sceKernelMunmap(mapped, page * 2) == 0); + Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); +} #endif #if defined(__linux__) @@ -674,6 +706,7 @@ int main() { CheckHeapAfterMappingReuse(); #ifdef _WIN32 CheckNoOverwriteRejectsHostOccupiedMapping(); + CheckFixedMappingsReachTheApplicationAreaEnd(); #endif CheckSharedWriteTracking(); CheckReadsIntoSharedWriteTracking(); diff --git a/core/libs/tests/GuestMsgDialogNative.cpp b/core/libs/tests/GuestMsgDialogNative.cpp new file mode 100644 index 000000000..6e3b757e2 --- /dev/null +++ b/core/libs/tests/GuestMsgDialogNative.cpp @@ -0,0 +1,49 @@ +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +extern "C" { +int APS5_VABI sceMsgDialogInitialize(void); +int APS5_VABI sceMsgDialogOpen(const void* param); +int APS5_VABI sceMsgDialogUpdateStatus(void); +int APS5_VABI sceMsgDialogGetResult(MsgDialogResult* result); +int APS5_VABI sceMsgDialogClose(void); +int APS5_VABI sceMsgDialogTerminate(void); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr int kStatusInitialized = 1; +constexpr int kStatusFinished = 3; +constexpr int kErrNotRunning = static_cast(0x80B8000Bu); +constexpr int kButtonIdOk = 1; + +} + +int main() { + std::uint8_t param[0x60]{}; + + Require(sceMsgDialogClose() == kErrNotRunning); + Require(sceMsgDialogUpdateStatus() == 0); + + Require(sceMsgDialogInitialize() == 0); + Require(sceMsgDialogClose() == kErrNotRunning); + Require(sceMsgDialogUpdateStatus() == kStatusInitialized); + + Require(sceMsgDialogOpen(param) == 0); + Require(sceMsgDialogUpdateStatus() == kStatusFinished); + Require(sceMsgDialogClose() == kErrNotRunning); + Require(sceMsgDialogUpdateStatus() == kStatusFinished); + + MsgDialogResult result{}; + result.button_id = -1; + Require(sceMsgDialogGetResult(&result) == 0); + Require(result.result == 0); + Require(result.button_id == kButtonIdOk); + + Require(sceMsgDialogTerminate() == 0); + Require(sceMsgDialogClose() == kErrNotRunning); +} diff --git a/core/libs/tests/GuestNpSessionSignaling.cpp b/core/libs/tests/GuestNpSessionSignaling.cpp new file mode 100644 index 000000000..10ec8b222 --- /dev/null +++ b/core/libs/tests/GuestNpSessionSignaling.cpp @@ -0,0 +1,51 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceNpSessionSignalingInitialize(void* param); +int APS5_VABI sceNpSessionSignalingCreateContext2(const void* param, std::uint32_t* contextId); +std::int32_t APS5_VABI sceNpSessionSignalingGetLocalNetInfo(std::int32_t contextId, void* info); +int APS5_VABI sceNpSessionSignalingRequestPrepare(std::uint32_t contextId, std::uint32_t* requestId); +int APS5_VABI sceNpSessionSignalingTerminate(void); +} + +namespace { + +constexpr int InvalidArgument = static_cast(0x80552D02u); +constexpr int Unavailable = static_cast(0x80552D06u); + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "NpSessionSignaling: %s\n", message); + std::abort(); + } +} + +} + +int main() { + unsigned char initializeParam[32] = {}; + Require(sceNpSessionSignalingInitialize(initializeParam) == 0, "initialize failed"); + unsigned char contextParam[64] = {}; + std::uint32_t contextId = 0; + Require(sceNpSessionSignalingCreateContext2(contextParam, &contextId) == 0, "context creation failed"); + + std::uint32_t requestId = 0xa5a5a5a5u; + Require(sceNpSessionSignalingRequestPrepare(contextId, nullptr) == InvalidArgument, "null request id"); + Require(sceNpSessionSignalingRequestPrepare(contextId, &requestId) == Unavailable, "prepare without network"); + Require(requestId == 0xa5a5a5a5u, "failed prepare wrote a request id"); + + const auto netContextId = static_cast(contextId); + unsigned char info[16]; + std::memset(info, 0xa5, sizeof(info)); + unsigned char untouched[sizeof(info)]; + std::memcpy(untouched, info, sizeof(info)); + Require(sceNpSessionSignalingGetLocalNetInfo(netContextId, nullptr) == InvalidArgument, "null info"); + Require(sceNpSessionSignalingGetLocalNetInfo(netContextId, info) == Unavailable, "local net info without network"); + Require(std::memcmp(info, untouched, sizeof(info)) == 0, "failed query modified the info"); + + Require(sceNpSessionSignalingTerminate() == 0, "terminate failed"); +} diff --git a/core/libs/tests/GuestNpTrophy2.cpp b/core/libs/tests/GuestNpTrophy2.cpp new file mode 100644 index 000000000..5a286ab35 --- /dev/null +++ b/core/libs/tests/GuestNpTrophy2.cpp @@ -0,0 +1,20 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include + +extern "C" { +int APS5_VABI sceNpTrophy2RegisterUnlockCallback(void*, void*); +int APS5_VABI sceNpTrophy2UnregisterUnlockCallback(); +} + +static void Require(bool value) { if (!value) std::abort(); } + +static void APS5_VABI OnUnlock(int, int, void*) {} + +int main() { + int userdata = 0; + Require(sceNpTrophy2UnregisterUnlockCallback() == 0); + Require(sceNpTrophy2RegisterUnlockCallback(reinterpret_cast(&OnUnlock), &userdata) == 0); + Require(sceNpTrophy2UnregisterUnlockCallback() == 0); + Require(sceNpTrophy2RegisterUnlockCallback(reinterpret_cast(&OnUnlock), &userdata) == 0); + Require(sceNpTrophy2UnregisterUnlockCallback() == 0); +} diff --git a/core/libs/tests/GuestPlayGoDialog.cpp b/core/libs/tests/GuestPlayGoDialog.cpp index 9c313bda1..40a6a6710 100644 --- a/core/libs/tests/GuestPlayGoDialog.cpp +++ b/core/libs/tests/GuestPlayGoDialog.cpp @@ -10,6 +10,7 @@ int APS5_VABI scePlayGoDialogTerminate(void); int APS5_VABI scePlayGoDialogOpen(const void* param); int APS5_VABI scePlayGoDialogClose(void); int APS5_VABI scePlayGoDialogUpdateStatus(void); +int APS5_VABI scePlayGoDialogGetStatus(void); int APS5_VABI scePlayGoDialogGetResult(void* result); } @@ -21,6 +22,7 @@ int main() { constexpr std::int32_t paramInvalid = static_cast(0x80ED0003); constexpr std::int32_t invalidState = static_cast(0x80ED0005); + Require(scePlayGoDialogGetStatus() == 0); Require(scePlayGoDialogTerminate() == notInitialized); Require(scePlayGoDialogOpen(nullptr) == paramInvalid); Require(scePlayGoDialogGetResult(nullptr) == notInitialized); @@ -29,11 +31,13 @@ int main() { Require(scePlayGoDialogOpen(&openParam) == invalidState); Require(scePlayGoDialogInitialize() == 0); + Require(scePlayGoDialogGetStatus() == 1); Require(scePlayGoDialogInitialize() == alreadyInitialized); Require(scePlayGoDialogOpen(nullptr) == paramInvalid); Require(scePlayGoDialogUpdateStatus() == 1); Require(scePlayGoDialogOpen(&openParam) == 0); + Require(scePlayGoDialogGetStatus() == 3); Require(scePlayGoDialogUpdateStatus() == 3); PlayGoDialogResult result{}; @@ -41,6 +45,7 @@ int main() { Require(result.result == 0); Require(scePlayGoDialogClose() == 0); Require(scePlayGoDialogTerminate() == 0); + Require(scePlayGoDialogGetStatus() == 0); Require(scePlayGoDialogTerminate() == notInitialized); return 0; } diff --git a/core/libs/tests/GuestPsml.cpp b/core/libs/tests/GuestPsml.cpp new file mode 100644 index 000000000..d0aac1d17 --- /dev/null +++ b/core/libs/tests/GuestPsml.cpp @@ -0,0 +1,53 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +extern "C" { +std::int32_t APS5_VABI scePsmlMfsrGetContextBufferRequirement1100(void* requirement, const void* param); +std::int32_t APS5_VABI scePsmlMfsrCreateContext1100(void** context, const void* param); +std::int32_t APS5_VABI scePsmlMfsrGetDispatchMfsrPacket1100(void* context, void* commandBuffer, const void* param); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr std::int32_t kErrNotInitialized = static_cast(0x8A810001); + +struct CommandBuffer { + std::uint32_t* cursor; + std::uint32_t sizeInDwords; +}; + +} + +int main() { + std::array param{}; + + std::array requirement; + requirement.fill(0xa5); + const auto untouchedRequirement = requirement; + Require(scePsmlMfsrGetContextBufferRequirement1100(requirement.data(), param.data()) == kErrNotInitialized); + Require(requirement == untouchedRequirement); + Require(scePsmlMfsrGetContextBufferRequirement1100(nullptr, nullptr) == kErrNotInitialized); + + std::uint64_t contextStorage = 0; + void* const sentinel = &contextStorage; + void* context = sentinel; + Require(scePsmlMfsrCreateContext1100(&context, param.data()) == kErrNotInitialized); + Require(context == sentinel); + Require(scePsmlMfsrCreateContext1100(nullptr, nullptr) == kErrNotInitialized); + + std::array dwords; + dwords.fill(0xa5a5a5a5u); + const auto untouchedDwords = dwords; + CommandBuffer commandBuffer{dwords.data(), static_cast(dwords.size())}; + Require(scePsmlMfsrGetDispatchMfsrPacket1100(&contextStorage, &commandBuffer, param.data()) == kErrNotInitialized); + Require(commandBuffer.cursor == dwords.data()); + Require(commandBuffer.sizeInDwords == dwords.size()); + Require(dwords == untouchedDwords); + Require(scePsmlMfsrGetDispatchMfsrPacket1100(nullptr, &commandBuffer, param.data()) == kErrNotInitialized); + Require(commandBuffer.cursor == dwords.data()); + Require(dwords == untouchedDwords); +} diff --git a/core/libs/tests/GuestRazorCpu.cpp b/core/libs/tests/GuestRazorCpu.cpp index 2b966d8a6..0f1469a82 100644 --- a/core/libs/tests/GuestRazorCpu.cpp +++ b/core/libs/tests/GuestRazorCpu.cpp @@ -7,6 +7,8 @@ std::uint32_t APS5_VABI sceRazorCpuIsCapturing(void); int APS5_VABI sceRazorCpuJobManagerDispatch(const void* args); int APS5_VABI sceRazorCpuJobManagerJob(const void* args); int APS5_VABI sceRazorCpuJobManagerSequence(const void* args); +int APS5_VABI sceRazorCpuPushMarkerStatic(const char* name, std::uint32_t color, std::uint32_t flags); +int APS5_VABI sceRazorCpuPopMarker(void); } namespace { @@ -20,4 +22,10 @@ int main() { Require(sceRazorCpuJobManagerDispatch(nullptr) == 0); Require(sceRazorCpuJobManagerJob(nullptr) == 0); Require(sceRazorCpuJobManagerSequence(nullptr) == 0); + Require(sceRazorCpuPushMarkerStatic("outer", 0x80ffffffu, 2) == 0); + Require(sceRazorCpuPushMarkerStatic("inner", 0x80ffffffu, 2) == 0); + Require(sceRazorCpuPopMarker() == 0); + Require(sceRazorCpuPopMarker() == 0); + Require(sceRazorCpuPushMarkerStatic("unbalanced", 0x80ffffffu, 2) == 0); + Require(sceRazorCpuIsCapturing() == 0); } diff --git a/core/libs/tests/GuestRtc.cpp b/core/libs/tests/GuestRtc.cpp index fca1f2688..2d843ebfe 100644 --- a/core/libs/tests/GuestRtc.cpp +++ b/core/libs/tests/GuestRtc.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include extern "C" { @@ -97,6 +98,22 @@ int main() { Require(sceRtcFormatRFC3339(text, &tick, 0) == 0 && std::strcmp(text, "2024-02-29T12:34:56.78Z") == 0); Require(sceRtcFormatRFC3339(text, &tick, 90) == 0 && std::strcmp(text, "2024-02-29T14:04:56.78+01:30") == 0); Require(sceRtcFormatRFC3339(text, &tick, -300) == 0 && std::strcmp(text, "2024-02-29T07:34:56.78-05:00") == 0); + Require(sceRtcFormatRFC3339(text, &tick, 1439) == 0 && std::strcmp(text, "2024-03-01T12:33:56.78+23:59") == 0); + Require(sceRtcFormatRFC3339(text, &tick, -1439) == 0 && std::strcmp(text, "2024-02-28T12:35:56.78-23:59") == 0); + for (int offset : {0, 1, -1, 59, -59, 60, -60, 90, -300, 1439, -1439}) { + RtcTick parsed{}; + Require(sceRtcFormatRFC3339(text, &tick, offset) == 0); + Require(sceRtcParseRFC3339(&parsed, text) == 0 && parsed.tick == leapDayTick - 9000); + } + for (int offset : {1440, -1440, 6000, -6000, std::numeric_limits::min(), std::numeric_limits::max()}) { + std::memset(text, 'x', sizeof(text)); + char original[sizeof(text)]; + std::memcpy(original, text, sizeof(text)); + Require(sceRtcFormatRFC3339(text, &tick, offset) == invalidValue); + Require(std::memcmp(text, original, sizeof(text)) == 0); + } + Require(sceRtcFormatRFC3339(nullptr, &tick, 1440) == invalidPointer); + Require(sceRtcFormatRFC3339(text, nullptr, 1440) == invalidPointer); Require(sceRtcParseRFC3339(&tick, "2024-02-29T14:04:56.789+01:30") == 0 && tick.tick == leapDayTick); Require(sceRtcParseRFC3339(&tick, "2024-02-29t12:34:56.789z") == 0 && tick.tick == leapDayTick); Require(sceRtcParseRFC3339(&tick, "1970-01-01T00:00:00Z") == 0 && tick.tick == unixEpochTick); diff --git a/core/libs/tests/GuestSaveDataPs4.cpp b/core/libs/tests/GuestSaveDataPs4.cpp new file mode 100644 index 000000000..39e7b14d1 --- /dev/null +++ b/core/libs/tests/GuestSaveDataPs4.cpp @@ -0,0 +1,93 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include + +extern "C" int APS5_VABI sceSaveDataTransferringMountPs4(const SaveDataTransferringMount*, SaveDataMountResult*); +extern "C" int APS5_VABI sceSaveDataDirNameSearchPs4(const SaveDataDirNameSearchCond*, SaveDataDirNameSearchResult*); +extern "C" int APS5_VABI sceSaveDataDirNameSearch(const SaveDataDirNameSearchCond*, SaveDataDirNameSearchResult*); + +namespace { + +constexpr int SaveDataErrorNotFound = -2137063416; + +int failures = 0; + +void Check(bool condition, const std::string& what) { + if (condition) return; + std::fprintf(stderr, "savedata ps4 check failed: %s\n", what.c_str()); + ++failures; +} + +template +bool ThrowsRuntimeError(TFunction function) { + try { + function(); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +void TestTransferringMountPs4() { + SceSaveDataTitleId title{}; + std::strcpy(title.data, "CUSA00001"); + SceSaveDataDirName dir{}; + std::strcpy(dir.data, "kept"); + SaveDataTransferringMount mount{}; + mount.user_id = 1; + mount.title_id = &title; + mount.dir_name = &dir; + SaveDataMountResult result{}; + std::memset(&result, 0xAA, sizeof(result)); + Check(sceSaveDataTransferringMountPs4(&mount, &result) == SaveDataErrorNotFound, "TransferringMountPs4 returns NOT_FOUND"); + Check(result.mount_point.data[0] == '\0', "TransferringMountPs4 reports no mount point"); +} + +void TestDirNameSearchPs4() { + SceSaveDataDirName names[4]{}; + std::memset(names, 0x5A, sizeof(names)); + SaveDataDirNameSearchCond cond{}; + cond.user_id = 1; + SaveDataDirNameSearchResult result{}; + result.hit_num = 7; + result.set_num = 7; + result.dir_names = names; + result.dir_names_num = 4; + + Check(sceSaveDataDirNameSearch(&cond, &result) == 0 && result.hit_num == 1 && result.set_num == 1, "the PS5 search finds the PS5 save"); + + std::memset(names, 0x5A, sizeof(names)); + result.hit_num = 7; + result.set_num = 7; + Check(sceSaveDataDirNameSearchPs4(&cond, &result) == 0, "DirNameSearchPs4 succeeds"); + Check(result.hit_num == 0, "DirNameSearchPs4 writes zero hits"); + Check(result.set_num == 0, "DirNameSearchPs4 writes zero set entries"); + Check(static_cast(names[0].data[0]) == 0x5A, "DirNameSearchPs4 leaves the name buffer untouched"); + + Check(ThrowsRuntimeError([&] { sceSaveDataDirNameSearchPs4(nullptr, &result); }), "null cond throws"); + Check(ThrowsRuntimeError([&] { sceSaveDataDirNameSearchPs4(&cond, nullptr); }), "null result throws"); +} + +} + +int main() { + const auto root = std::filesystem::temp_directory_path() / ("anyps5-savedata-ps4-" + std::to_string(std::random_device{}())); + std::filesystem::create_directories(root / "_sd" / "kept"); + const auto previous = std::filesystem::current_path(); + std::filesystem::current_path(root); + + TestTransferringMountPs4(); + TestDirNameSearchPs4(); + + std::filesystem::current_path(previous); + std::error_code error; + std::filesystem::remove_all(root, error); + if (failures != 0) return 1; + std::printf("savedata ps4 tests passed\n"); + return 0; +} diff --git a/core/libs/tests/GuestShare.cpp b/core/libs/tests/GuestShare.cpp new file mode 100644 index 000000000..353b80bb4 --- /dev/null +++ b/core/libs/tests/GuestShare.cpp @@ -0,0 +1,30 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +extern "C" { +int APS5_VABI sceShareCaptureScreenshotExtended(const void* extended_param, std::int32_t* req_id); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +} + +int main() { + constexpr std::int32_t notSupported = static_cast(0x81960007); + std::uint8_t param[64]{}; + + std::int32_t reqId = 7; + Require(sceShareCaptureScreenshotExtended(param, &reqId) == notSupported); + Require(reqId == -1); + + reqId = 7; + Require(sceShareCaptureScreenshotExtended(nullptr, &reqId) == notSupported); + Require(reqId == -1); + + Require(sceShareCaptureScreenshotExtended(param, nullptr) == notSupported); + Require(sceShareCaptureScreenshotExtended(nullptr, nullptr) == notSupported); + return 0; +} diff --git a/core/libs/tests/GuestSyncOnAddress.cpp b/core/libs/tests/GuestSyncOnAddress.cpp index 363c35d9a..e5954051f 100644 --- a/core/libs/tests/GuestSyncOnAddress.cpp +++ b/core/libs/tests/GuestSyncOnAddress.cpp @@ -10,6 +10,8 @@ extern "C" { int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name); +int APS5_VABI sceKernelSyncOnAddressWait32(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name); +int APS5_VABI sceKernelSyncOnAddressWait64(std::uint64_t* address, std::uint64_t expected, const KernelUseconds* timeout, const char* name); int APS5_VABI sceKernelSyncOnAddressWake(void* address, std::int32_t count); } @@ -130,14 +132,87 @@ static void WaitsOnTheLowHalfOfAnOnceState() { waiter.join(); } +static void SizedWaitsTimeOutWhileTheValueMatches() { + std::uint32_t word = 5; + std::uint64_t value = 0x500000005ULL; + const KernelUseconds none = 0; + Require(sceKernelSyncOnAddressWait32(&word, 4, &none, "differs") == SCE_OK); + Require(sceKernelSyncOnAddressWait32(&word, 5, &none, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(sceKernelSyncOnAddressWait64(&value, 0x500000005ULL, &none, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT); + + const KernelUseconds timeout = 20000; + auto start = std::chrono::steady_clock::now(); + Require(sceKernelSyncOnAddressWait32(&word, 5, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15)); + start = std::chrono::steady_clock::now(); + Require(sceKernelSyncOnAddressWait64(&value, 0x500000005ULL, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15)); +} + +static void Wait64ComparesTheHighHalf() { + std::uint64_t value = 0x100000001ULL; + const KernelUseconds none = 0; + Require(sceKernelSyncOnAddressWait64(&value, 0x1ULL, &none, "high") == SCE_OK); + Require(sceKernelSyncOnAddressWait64(&value, 0x200000001ULL, &none, "high") == SCE_OK); + Require(sceKernelSyncOnAddressWait64(&value, 0x100000001ULL, &none, "high") == SCE_KERNEL_ERROR_ETIMEDOUT); +} + +static void WakeReleasesWaitersOfEverySize() { + std::uint64_t value = 1; + auto* word = reinterpret_cast(&value); + std::atomic started{0}; + std::atomic finished{0}; + std::vector results(3, -1); + + std::vector waiters; + waiters.emplace_back([&] { + ++started; + results[0] = sceKernelSyncOnAddressWait(word, 1, &FAILSAFE_TIMEOUT, "sized"); + ++finished; + }); + waiters.emplace_back([&] { + ++started; + results[1] = sceKernelSyncOnAddressWait32(word, 1, &FAILSAFE_TIMEOUT, "sized"); + ++finished; + }); + waiters.emplace_back([&] { + ++started; + results[2] = sceKernelSyncOnAddressWait64(&value, 1, &FAILSAFE_TIMEOUT, "sized"); + ++finished; + }); + AwaitCount(started, 3); + std::this_thread::sleep_for(SETTLE); + + Require(sceKernelSyncOnAddressWake(&value, 1) == SCE_OK); + AwaitCount(finished, 1); + std::this_thread::sleep_for(SETTLE); + Require(finished.load() == 1); + + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (finished.load() != 3) { + Require(std::chrono::steady_clock::now() < deadline); + Require(sceKernelSyncOnAddressWake(&value, INT_MAX) == SCE_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + for (auto& waiter : waiters) waiter.join(); + for (const int result : results) Require(result == SCE_OK); +} + static void RejectsInvalidArguments() { std::uint32_t words[2] = {1, 1}; auto* misaligned = reinterpret_cast(reinterpret_cast(words) + 1); Require(Rejects([&] { sceKernelSyncOnAddressWait(nullptr, 1, nullptr, nullptr); })); Require(Rejects([&] { sceKernelSyncOnAddressWait(misaligned, 1, nullptr, nullptr); })); + Require(Rejects([&] { sceKernelSyncOnAddressWait32(nullptr, 1, nullptr, nullptr); })); + Require(Rejects([&] { sceKernelSyncOnAddressWait32(misaligned, 1, nullptr, nullptr); })); Require(Rejects([&] { sceKernelSyncOnAddressWake(nullptr, 1); })); Require(Rejects([&] { sceKernelSyncOnAddressWake(misaligned, 1); })); Require(Rejects([&] { sceKernelSyncOnAddressWake(words, -1); })); + + std::uint64_t values[2] = {1, 1}; + auto* wordAligned = reinterpret_cast(reinterpret_cast(values) + sizeof(std::uint32_t)); + Require(Rejects([&] { sceKernelSyncOnAddressWait64(nullptr, 1, nullptr, nullptr); })); + Require(Rejects([&] { sceKernelSyncOnAddressWait64(wordAligned, 1, nullptr, nullptr); })); } int main() { @@ -146,5 +221,8 @@ int main() { WakeReleasesAWaiterWithoutAValueChange(); WakeHonoursTheCountAndTheAddress(); WaitsOnTheLowHalfOfAnOnceState(); + SizedWaitsTimeOutWhileTheValueMatches(); + Wait64ComparesTheHighHalf(); + WakeReleasesWaitersOfEverySize(); RejectsInvalidArguments(); } diff --git a/core/libs/tests/GuestSystemService.cpp b/core/libs/tests/GuestSystemService.cpp index 88193689e..fe937fc57 100644 --- a/core/libs/tests/GuestSystemService.cpp +++ b/core/libs/tests/GuestSystemService.cpp @@ -1,13 +1,26 @@ #include "SceTypes.hpp" #include "prx/libSceSystemService/SystemService.hpp" +#include #include +#include +#include extern "C" int APS5_VABI sceSystemServiceGetHdrToneMapLuminance(SystemServiceHdrToneMapLuminance* luminance); +extern "C" int APS5_VABI sceSystemServiceParamGetString(int paramId, char* buf, std::size_t bufSize); namespace { void Require(bool value) { if (!value) std::abort(); } +bool ParamGetStringThrows(int paramId, char* buf, std::size_t bufSize) { + try { + static_cast(sceSystemServiceParamGetString(paramId, buf, bufSize)); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + } int main() { @@ -17,4 +30,14 @@ int main() { Require(luminance.max_full_frame_tone_map_luminance == 100.0f); Require(luminance.max_tone_map_luminance == 100.0f); Require(luminance.min_tone_map_luminance == 0.0f); + + char name[SYSTEM_SERVICE_MAX_SYSTEM_NAME_LENGTH]; + std::memset(name, 'x', sizeof(name)); + Require(sceSystemServiceParamGetString(SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME, nullptr, sizeof(name)) == SYSTEM_SERVICE_ERROR_PARAMETER); + Require(sceSystemServiceParamGetString(SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME, name, 0) == SYSTEM_SERVICE_ERROR_PARAMETER); + Require(ParamGetStringThrows(SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME, name, sizeof(name) - 1)); + Require(ParamGetStringThrows(SYSTEM_SERVICE_PARAM_ID_LANG, name, sizeof(name))); + Require(name[0] == 'x'); + Require(sceSystemServiceParamGetString(SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME, name, sizeof(name)) == SYSTEM_SERVICE_OK); + Require(std::strcmp(name, "PS5") == 0); } diff --git a/core/libs/tests/GuestThreadAtexit.cpp b/core/libs/tests/GuestThreadAtexit.cpp new file mode 100644 index 000000000..f37fd5d52 --- /dev/null +++ b/core/libs/tests/GuestThreadAtexit.cpp @@ -0,0 +1,87 @@ +#include "SceTypes.hpp" +#include +#include +#include +#include + +extern "C" { +int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); +int APS5_VABI scePthreadJoin(Pthread thread, void** retval); +Pthread APS5_VABI scePthreadSelf(); +int APS5_VABI _sceLibcInternalThreadAtexit_nid_postfix(void (APS5_VABI* destructor)(void*), void* object, void* dsoSymbol); +void APS5_VABI _sceLibcInternalThreadDtors_nid_postfix(); +} + +namespace { + +using Destructor = void (APS5_VABI*)(void*); + +void Require(bool value) { if (!value) std::abort(); } + +int dsoHandle = 0; +std::vector calls; +std::vector callers; + +void* Object(std::intptr_t value) { return reinterpret_cast(value); } + +int Register(Destructor destructor, std::intptr_t object) { + return _sceLibcInternalThreadAtexit_nid_postfix(destructor, Object(object), &dsoHandle); +} + +void APS5_VABI Record(void* object) { + calls.push_back(reinterpret_cast(object)); + callers.push_back(scePthreadSelf()); +} + +void APS5_VABI RecordAndRegister(void* object) { + Record(object); + Require(Register(Record, 9) == 0); +} + +void* APS5_VABI Worker(void*) { + Require(Register(Record, 4) == 0); + Require(Register(Record, 5) == 0); + return nullptr; +} + +bool DestructorsThrow() { + try { + _sceLibcInternalThreadDtors_nid_postfix(); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +} + +int main() { + const Pthread mainThread = scePthreadSelf(); + Require(Register(Record, 1) == 0); + Require(Register(RecordAndRegister, 2) == 0); + Require(Register(Record, 3) == 0); + _sceLibcInternalThreadDtors_nid_postfix(); + Require(calls == std::vector{3, 2, 9, 1}); + Require(callers == std::vector(4, mainThread)); + _sceLibcInternalThreadDtors_nid_postfix(); + Require(calls.size() == 4); + + calls.clear(); + callers.clear(); + Pthread worker = nullptr; + Require(scePthreadCreate(&worker, nullptr, Worker, nullptr, "ThreadAtexit") == 0); + Require(scePthreadJoin(worker, nullptr) == 0); + Require(calls == std::vector{5, 4}); + Require(callers == std::vector(2, worker)); + + calls.clear(); + std::vector heap(64); + Require(Register(Record, 6) == 0); + Require(Register(nullptr, 7) == 0); + Require(Register(reinterpret_cast(heap.data()), 8) == 0); + Require(DestructorsThrow()); + Require(DestructorsThrow()); + Require(calls.empty()); + _sceLibcInternalThreadDtors_nid_postfix(); + Require(calls == std::vector{6}); +} diff --git a/core/libs/tests/GuestUlobjmgr.cpp b/core/libs/tests/GuestUlobjmgr.cpp new file mode 100644 index 000000000..0fd79500f --- /dev/null +++ b/core/libs/tests/GuestUlobjmgr.cpp @@ -0,0 +1,32 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +extern "C" { +int APS5_VABI _sceUlobjmgrRegisterObject(std::uint64_t object, std::int32_t kind, std::uint32_t* id); +int APS5_VABI _sceUlobjmgrUnregisterObject(std::uint32_t id); +} + +namespace { + +constexpr int einval = 22; + +void Require(bool value) { if (!value) std::abort(); } + +} + +int main() { + std::uint32_t id = 0xffffffffu; + Require(_sceUlobjmgrRegisterObject(0, 1, &id) == einval); + Require(id == 0xffffffffu); + Require(_sceUlobjmgrRegisterObject(0x1000, 0, &id) == einval); + Require(id == 0xffffffffu); + Require(_sceUlobjmgrRegisterObject(0x1000, 1, nullptr) == einval); + Require(_sceUlobjmgrRegisterObject(0x1000, -1, &id) == 0); + Require(id == 0); + + Require(_sceUlobjmgrUnregisterObject(id) == 0); + Require(_sceUlobjmgrUnregisterObject(0x3fff) == 0); + Require(_sceUlobjmgrUnregisterObject(0x4000) == einval); + Require(_sceUlobjmgrUnregisterObject(0xffffffffu) == einval); +} diff --git a/core/libs/tests/GuestWcsrtombsS.cpp b/core/libs/tests/GuestWcsrtombsS.cpp new file mode 100644 index 000000000..758ff5d33 --- /dev/null +++ b/core/libs/tests/GuestWcsrtombsS.cpp @@ -0,0 +1,78 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +extern "C" int APS5_VABI wcsrtombs_s_nid_postfix( + std::size_t*, char*, std::size_t, const std::uint16_t**, std::size_t, void*); +static void Require(bool value) { if (!value) std::abort(); } + +constexpr auto Failed = static_cast(-1); +constexpr auto Huge = std::size_t{1} << 63; +static const std::uint16_t text[] = {'a', 'b', 'c', 0}; +static const std::uint16_t wide[] = {'a', 0x100, 'b', 0}; + +struct Run { + int status; + std::size_t result = 7; + const std::uint16_t* source; + char out[8]; +}; + +static Run Convert(const std::uint16_t* input, std::size_t capacity, std::size_t limit, bool toBuffer = true) { + Run run{}; + std::memset(run.out, 'x', sizeof(run.out)); + run.source = input; + std::uint64_t state[2] = {}; + run.status = wcsrtombs_s_nid_postfix(&run.result, toBuffer ? run.out : nullptr, capacity, &run.source, limit, state); + return run; +} + +int main() { + auto run = Convert(text, 0, 0, false); + Require(run.status == 0 && run.result == 3 && run.source == text); + run = Convert(wide, 0, Huge, false); + Require(run.status == 86 && run.result == Failed && run.source == wide); + run = Convert(text, 5, 1, false); + Require(run.status == 22 && run.result == Failed && run.source == text); + + run = Convert(text, 8, 8); + Require(run.status == 0 && run.result == 3 && run.source == nullptr && std::memcmp(run.out, "abc\0x", 5) == 0); + run = Convert(text, 4, 4); + Require(run.status == 0 && run.result == 3 && run.source == nullptr && std::memcmp(run.out, "abc\0x", 5) == 0); + run = Convert(text, 8, 2); + Require(run.status == 0 && run.result == 2 && run.source == text + 2 && std::memcmp(run.out, "ab\0x", 4) == 0); + run = Convert(text, 8, 0); + Require(run.status == 0 && run.result == 0 && run.source == text && std::memcmp(run.out, "\0x", 2) == 0); + + run = Convert(text, 3, 8); + Require(run.status == 34 && run.result == Failed && run.source == text + 3 && std::memcmp(run.out, "\0bcx", 4) == 0); + run = Convert(text, 3, 3); + Require(run.status == 34 && run.result == Failed && run.source == text + 3 && std::memcmp(run.out, "\0bcx", 4) == 0); + run = Convert(text, 1, 8); + Require(run.status == 34 && run.result == Failed && run.source == text + 1 && std::memcmp(run.out, "\0x", 2) == 0); + + run = Convert(wide, 8, 8); + Require(run.status == 86 && run.result == Failed && run.source == wide && std::memcmp(run.out, "a\0xx", 4) == 0); + + run = Convert(text, 0, 8); + Require(run.status == 22 && run.result == Failed && run.source == text && run.out[0] == 'x'); + run = Convert(text, Huge, 8); + Require(run.status == 22 && run.result == Failed && run.out[0] == 'x'); + run = Convert(text, 8, Huge); + Require(run.status == 22 && run.result == Failed && run.source == text && run.out[0] == '\0'); + run = Convert(nullptr, 8, 8); + Require(run.status == 22 && run.result == Failed && run.out[0] == '\0'); + + char out[4] = {'x', 'x', 'x', 'x'}; + std::size_t result = 7; + const std::uint16_t* source = text; + std::uint64_t state[2] = {}; + Require(wcsrtombs_s_nid_postfix(nullptr, out, 4, &source, 4, state) == 22 && out[0] == '\0' && source == text); + out[0] = 'x'; + Require(wcsrtombs_s_nid_postfix(&result, out, 4, nullptr, 4, state) == 22 && result == Failed && out[0] == '\0'); + out[0] = 'x'; + result = 7; + Require(wcsrtombs_s_nid_postfix(&result, out, 4, &source, 4, nullptr) == 22 && result == Failed && out[0] == '\0'); + Require(source == text && std::memcmp(out, "\0xxx", 4) == 0); +} diff --git a/core/libs/tests/ParamJsonParser.cpp b/core/libs/tests/ParamJsonParser.cpp new file mode 100644 index 000000000..fc3c06cb0 --- /dev/null +++ b/core/libs/tests/ParamJsonParser.cpp @@ -0,0 +1,56 @@ +#include "prx/libkernel/AppMetadata/include/ParamJsonParser.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +static ParsedParamJson ParseSize(const std::filesystem::path& path, const std::string& size) { + { + std::ofstream file(path); + file << R"({"titleId":"PPSA00000","localizedParameters":{"en-US":{"titleName":"Example"}})"; + if (!size.empty()) file << ",\"downloadDataSize\":" << size; + file << '}'; + Require(static_cast(file), "Cannot write param.json fixture"); + } + return parseParamJson(path); +} + +int main() { + const auto path = std::filesystem::temp_directory_path() / ("anyps5-param-json-" + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".json"); + int result = 0; + try { + const std::pair sizes[] = { + {"", 0}, {"0", 0}, {"-0", 0}, {"4096", 4096}, + {"9007199254740993", 9007199254740993ull}, + {"18446744073709551614", std::numeric_limits::max() - 1}, + {"18446744073709551615", std::numeric_limits::max()}, + {"1e3", 1000}, {"1.25", 1} + }; + for (const auto& [text, expected] : sizes) { + const auto parsed = ParseSize(path, text); + Require(parsed.downloadDataSizeMiB == expected, std::string("Incorrect download size: ") + text); + Require(parsed.titleId == "PPSA00000" && parsed.title == "Example", "Title metadata changed"); + } + for (const auto* text : {"18446744073709551616", "18446744073709551617", "1.8446744073709552e19", + "1e40", "-1", "-0.5", "true", "null", "\"4096\""}) { + bool rejected = false; + try { ParseSize(path, text); } catch (const std::exception&) { rejected = true; } + Require(rejected, std::string("Accepted invalid download size: ") + text); + } + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + result = 1; + } + std::filesystem::remove(path); + return result; +} diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index a388ec7a3..0dca9d1f2 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -125,6 +125,12 @@ endif() if(BUILD_TESTING) + add_test_executable(autorun_exit_tests + ${CMAKE_CURRENT_SOURCE_DIR}/cli/tests/AutorunExitTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cli/src/Autorun.cpp) + target_include_directories(autorun_exit_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cli/include) + add_test(NAME autorun_exit_status COMMAND autorun_exit_tests) + set_tests_properties(autorun_exit_status PROPERTIES TIMEOUT 20) add_test_executable(buffer_bounds_tests ${CMAKE_CURRENT_SOURCE_DIR}/io/tests/BufferBoundsTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io/src/ByteReader.cpp @@ -202,6 +208,8 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_entry_argv.py $) add_test(NAME windows_gui COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_gui.py $) + add_test(NAME windows_address_space COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_address_space.py $) add_test(NAME windows_icon COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_icon.py $) add_test(NAME guest_intel_trampolines COMMAND ${Python3_EXECUTABLE} diff --git a/core/relinker/cli/src/Autorun.cpp b/core/relinker/cli/src/Autorun.cpp index 30979a985..3bb7016be 100644 --- a/core/relinker/cli/src/Autorun.cpp +++ b/core/relinker/cli/src/Autorun.cpp @@ -20,12 +20,15 @@ int Autorun(const std::string& absPath, bool toWindows) { const int rawCode = std::system(cmd.c_str()); int exitCode = rawCode; - if (toWindows) { - std::cout << "\nExit code: " << rawCode << '\n'; - } else { - if (rawCode != -1) exitCode = (rawCode >> 8) & 0xFF; - std::cout << "\nRaw exit code: " << rawCode << "; Unpacked: " << exitCode << '\n'; +#ifdef _WIN32 + std::cout << "\nExit code: " << rawCode << '\n'; +#else + if (rawCode != -1) { + const int signal = rawCode & 0x7F; + exitCode = signal ? 128 + signal : (rawCode >> 8) & 0xFF; } + std::cout << "\nRaw exit code: " << rawCode << "; Unpacked: " << exitCode << '\n'; +#endif std::cout << "\nPress Enter to exit...\n"; std::cin.ignore(std::numeric_limits::max(), '\n'); diff --git a/core/relinker/cli/tests/AutorunExitTests.cpp b/core/relinker/cli/tests/AutorunExitTests.cpp new file mode 100644 index 000000000..f6fc70829 --- /dev/null +++ b/core/relinker/cli/tests/AutorunExitTests.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int, char* argv[]) { + const auto executable = std::filesystem::absolute(argv[0]); + const auto name = executable.stem().string(); + if (name.starts_with("anyps5-autorun-child-")) { + if (name.find("signal") != std::string::npos) std::raise(SIGILL); + return 42; + } + const auto child = std::filesystem::temp_directory_path() / ("anyps5-autorun-child-" + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + " with spaces" + executable.extension().string()); +#ifndef _WIN32 + const auto signaled = child.parent_path() / ("anyps5-autorun-child-signal-" + child.filename().string()); +#endif + std::istringstream input("\n\n\n"); + auto* savedInput = std::cin.rdbuf(input.rdbuf()); + int result = 0; + try { + std::filesystem::copy_file(executable, child); + for (const bool windows : {false, true}) { + const int code = Cli::Autorun(child.string(), windows); + if (code != 42) throw std::runtime_error("Autorun changed exit code 42 to " + std::to_string(code)); + } +#ifndef _WIN32 + std::filesystem::rename(child, signaled); + const int code = Cli::Autorun(signaled.string(), false); + if (code != 128 + SIGILL) throw std::runtime_error("Autorun lost the child signal"); +#endif + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + result = 1; + } + std::cin.rdbuf(savedInput); + std::filesystem::remove(child); +#ifndef _WIN32 + std::filesystem::remove(signaled); +#endif + return result; +} diff --git a/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp b/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp index 61c0a567d..3c4e9ac34 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp @@ -30,7 +30,7 @@ std::vector WindowsPeWriter::Write(const std::vector& s Io::WriteU16(result, optionalOffset + 50, 2); Io::WriteU32(result, optionalOffset + 60, LoadRva); Io::WriteU16(result, optionalOffset + 68, windowsGui ? 2 : 3); - Io::WriteU16(result, optionalOffset + 70, directories[5].Size == 0 ? 0x100 : 0x160); + Io::WriteU16(result, optionalOffset + 70, directories[5].Size == 0 ? 0x100 : 0x140); Io::WriteU64(result, optionalOffset + 72, 0x210000); Io::WriteU64(result, optionalOffset + 80, 0x200000); Io::WriteU64(result, optionalOffset + 88, 0x100000); diff --git a/core/relinker/io/include/io/BufferUtils.hpp b/core/relinker/io/include/io/BufferUtils.hpp index 365a50847..82cc8d0ec 100644 --- a/core/relinker/io/include/io/BufferUtils.hpp +++ b/core/relinker/io/include/io/BufferUtils.hpp @@ -2,6 +2,8 @@ #define IO_BUFFERUTILS_HPP #include +#include +#include #include #include @@ -27,7 +29,14 @@ std::uint64_t ReadU64(const std::vector& buf, std::size_t offset); template constexpr T AlignUp(T value, T alignment) { - return (value + alignment - 1) / alignment * alignment; + if (value < 0 || alignment <= 0) + throw std::invalid_argument("AlignUp requires a nonnegative value and a positive alignment"); + const T remainder = value % alignment; + if (remainder == 0) return value; + const T padding = alignment - remainder; + if (value > std::numeric_limits::max() - padding) + throw std::overflow_error("Aligned value exceeds integer range"); + return value + padding; } std::uint64_t AlignUp64(std::uint64_t value, std::uint64_t alignment); diff --git a/core/relinker/io/src/BufferUtils.cpp b/core/relinker/io/src/BufferUtils.cpp index c0d389588..d4ce96336 100644 --- a/core/relinker/io/src/BufferUtils.cpp +++ b/core/relinker/io/src/BufferUtils.cpp @@ -60,11 +60,7 @@ void AppendString(std::vector& buf, const std::string& str) { } void AlignBuffer(std::vector& buf, std::size_t alignment) { - const std::size_t remainder = buf.size() % alignment; - if (remainder != 0) { - const std::size_t padding = alignment - remainder; - buf.resize(buf.size() + padding, 0); - } + buf.resize(AlignUp(buf.size(), alignment), 0); } std::uint64_t AlignUp64(std::uint64_t value, std::uint64_t alignment) { diff --git a/core/relinker/io/tests/BufferBoundsTests.cpp b/core/relinker/io/tests/BufferBoundsTests.cpp index 2de9beb8b..6767291ff 100644 --- a/core/relinker/io/tests/BufferBoundsTests.cpp +++ b/core/relinker/io/tests/BufferBoundsTests.cpp @@ -11,6 +11,8 @@ namespace { using Bytes = std::vector; +static_assert(Io::AlignUp(std::uint64_t{17}, std::uint64_t{16}) == 32); + void require(bool condition, const std::string& message) { if (!condition) throw std::runtime_error(message); } @@ -71,12 +73,51 @@ void writeBounds(const TWrite& write, const std::string& name) { invalidRanges(bytes, [&](Bytes& buffer, std::size_t offset) { write(buffer, offset, value); }, name); } +template +void requireThrows(const TOperation& operation, const std::string& message) { + try { + operation(); + } catch (const TError&) { + return; + } + throw std::runtime_error(message); +} + +template +void alignmentBounds() { + constexpr auto maximum = std::numeric_limits::max(); + require(Io::AlignUp(TValue{0}, TValue{16}) == 0, "AlignUp changed zero"); + require(Io::AlignUp(TValue{17}, TValue{16}) == 32, "AlignUp failed to round up"); + require(Io::AlignUp(TValue{7}, TValue{3}) == 9, "AlignUp requires a power-of-two alignment"); + require(Io::AlignUp(maximum, TValue{1}) == maximum, "AlignUp changed an aligned maximum"); + require(Io::AlignUp(static_cast(maximum - 15), TValue{16}) == maximum - 15, "AlignUp changed an aligned value near the maximum"); + require(Io::AlignUp(static_cast(maximum - 1), maximum) == maximum, "AlignUp overflowed for a representable result"); + requireThrows([&] { Io::AlignUp(maximum, TValue{16}); }, "AlignUp accepted an overflowing result"); + requireThrows([] { Io::AlignUp(TValue{1}, TValue{0}); }, "AlignUp accepted zero alignment"); +} + } int main() { try { const Io::ByteReader reader; const Io::ByteWriter writer; + alignmentBounds(); + alignmentBounds(); + alignmentBounds(); + alignmentBounds(); + requireThrows([] { Io::AlignUp(-1, 16); }, "AlignUp accepted a negative value"); + requireThrows([] { Io::AlignUp(1, -1); }, "AlignUp accepted negative alignment"); + require(Io::AlignUp64(std::numeric_limits::max() - 1, std::numeric_limits::max()) == std::numeric_limits::max(), "AlignUp64 overflowed for a representable result"); + requireThrows([] { Io::AlignUp64(std::numeric_limits::max(), 16); }, "AlignUp64 accepted an overflowing result"); + Bytes aligned{1, 2, 3}; + const auto original = aligned; + requireThrows([&] { Io::AlignBuffer(aligned, 0); }, "AlignBuffer accepted zero alignment"); + require(aligned == original, "AlignBuffer modified the buffer on failure"); + Io::AlignBuffer(aligned, 4); + require(aligned == Bytes({1, 2, 3, 0}), "AlignBuffer failed to preserve bytes and zero-fill padding"); + Io::AlignBuffer(aligned, 4); + require(aligned == Bytes({1, 2, 3, 0}), "AlignBuffer changed an aligned buffer"); readBounds([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU16(bytes, offset); }, "ByteReader::ReadU16"); readBounds([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU32(bytes, offset); }, "ByteReader::ReadU32"); readBounds([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU64(bytes, offset); }, "ByteReader::ReadU64"); diff --git a/core/relinker/relinker/tests/test_windows_address_space.py b/core/relinker/relinker/tests/test_windows_address_space.py new file mode 100644 index 000000000..d2720daf6 --- /dev/null +++ b/core/relinker/relinker/tests/test_windows_address_space.py @@ -0,0 +1,45 @@ +"""Keep the main thread stack that Windows creates before libc reserves the guest arena below it.""" + +import os +from pathlib import Path +import struct +import subprocess +import sys +import tempfile + +from test_optional_plt import fixture + +GUEST_ARENA_START = 0x200000000 +DYNAMIC_BASE = 0x40 +NX_COMPAT = 0x100 + + +def dll_characteristics(pe): + pe_offset = struct.unpack_from("> 32, \ + f"stack not below {GUEST_ARENA_START:#x}: exit code {executed.returncode:#x}" + print("Windows address space integration tests passed") + + +if __name__ == "__main__": + main() diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp index b1fac1488..805a366f9 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp @@ -303,6 +303,7 @@ enum class IrOpcode : std::uint16_t { AddressAtomicXor32, AddressAtomicInc32, AddressAtomicDec32, + AddressAtomicUSubSat32, AddressAtomicSwap64, AddressAtomicCmpSwap64, AddressAtomicIAdd64, diff --git a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp index eb7d22e56..7057ea930 100644 --- a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp +++ b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp @@ -344,6 +344,7 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("AddressAtomicXor32", U32, AddressResource, U32, U32, U32, U1), makeMeta("AddressAtomicInc32", U32, AddressResource, U32, U32, U32, U1), makeMeta("AddressAtomicDec32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicUSubSat32", U32, AddressResource, U32, U32, U32, U1), makeMeta("AddressAtomicSwap64", U64, AddressResource, U32, U32, U64, U1), makeMeta("AddressAtomicCmpSwap64", U64, AddressResource, U32, U32, U64, U64, U1), makeMeta("AddressAtomicIAdd64", U64, AddressResource, U32, U32, U64, U1), @@ -744,6 +745,7 @@ AddressOpcodeInfo AddressOpcodeInfoOf(IrOpcode opcode) { case IrOpcode::AddressAtomicXor32: case IrOpcode::AddressAtomicInc32: case IrOpcode::AddressAtomicDec32: + case IrOpcode::AddressAtomicUSubSat32: case IrOpcode::AddressAtomicFCmpSwap32: case IrOpcode::AddressAtomicFMin32: case IrOpcode::AddressAtomicFMax32: diff --git a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp index 7fef993b7..2b06d89d7 100644 --- a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp +++ b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp @@ -241,6 +241,7 @@ void DescriptorBindingBuilder::Populate(BindingAllocationResult& allocation, con const auto& image = info.images.at(resource); physical.imageWritten.push_back(image.written || image.atomic); physical.imageDepthCompare.push_back(image.depthCompare); + physical.imageAtomic.push_back(image.atomic); } break; case DescriptorRole::GuestSamplers: diff --git a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp index 4308347e1..943b3e14e 100644 --- a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp @@ -151,8 +151,8 @@ DecodedImage decodeImageDescriptor(const DescriptorValue& descriptor, const Imag decoded.dimension = descriptorDimension(descriptor, base.dimension); decoded.cube = descriptorIsCube(descriptor); const auto format = rawImageFormat(descriptor); - if (base.atomic && format != IrBufferFormat::Format32UInt) { - throw std::runtime_error("atomic image descriptor uses an unsupported format"); + if (base.atomic && format != IrBufferFormat::Format32UInt && format != IrBufferFormat::Format32SInt && format != IrBufferFormat::Format32Float) { + throw std::runtime_error("atomic image descriptor uses an unsupported format " + std::to_string(static_cast(format))); } const bool storage = base.resourceClass == ImageResourceClass::Storage; decoded.fmask = IsFmaskTextureFormat(format); @@ -170,7 +170,7 @@ DecodedImage decodeImageDescriptor(const DescriptorValue& descriptor, const Imag decoded.shaderSwizzle = descriptorImageSwizzle(descriptor); } const bool rawSintStorage = storage && format == IrBufferFormat::Format32SInt && base.written && !base.read && !base.atomic; - decoded.numericClass = SampledTextureNumericClass(format); + decoded.numericClass = base.atomic ? IrTextureNumericClass::Uint : SampledTextureNumericClass(format); if (!storage && !base.depthCompare && IsDepthBitsTexture(descriptor.dwords[1], descriptor.dwords[3])) { decoded.depthBits = true; decoded.depthUnorm16 = DepthBitsTextureWidth(descriptor.dwords[1], descriptor.dwords[3]) == 16u; diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp index 14cc7e3a1..23d5277fa 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -832,6 +832,7 @@ enum class RdnaOpcode : std::uint16_t { FlatAtomicFmaxX2, FlatAtomicIncX2, FlatAtomicDecX2, + GlobalAtomicCsub, GlobalStoreDwordAddtid, DsAddU32, DsAddRtnU32, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp index 53e6a4605..2544e10eb 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp @@ -190,6 +190,7 @@ constexpr MemoryOpcodeInfo flatOpcodes[] = { {0x31u, RdnaOpcode::FlatAtomicCmpswap, 1, 32, false, false, false}, {0x32u, RdnaOpcode::FlatAtomicAdd, 1, 32, false, false, false}, {0x33u, RdnaOpcode::FlatAtomicSub, 1, 32, false, false, false}, + {0x34u, RdnaOpcode::GlobalAtomicCsub, 1, 32, false, false, false}, {0x35u, RdnaOpcode::FlatAtomicSmin, 1, 32, false, false, false}, {0x36u, RdnaOpcode::FlatAtomicUmin, 1, 32, false, false, false}, {0x37u, RdnaOpcode::FlatAtomicSmax, 1, 32, false, false, false}, @@ -602,6 +603,7 @@ bool isFlatAtomicOpcode(RdnaOpcode opcode) { case RdnaOpcode::FlatAtomicFmaxX2: case RdnaOpcode::FlatAtomicIncX2: case RdnaOpcode::FlatAtomicDecX2: + case RdnaOpcode::GlobalAtomicCsub: return true; default: return false; } @@ -820,6 +822,9 @@ RdnaInstruction DecodeRdnaFlat(std::uint32_t programCounter, std::spansourceModifiers && hasSourceModifiers && !cndmaskFloatModifiers) { throw std::invalid_argument("VOP2 SDWA source modifiers are not supported"); } + const bool byteSelector = fields.src0Sel < 4u || fields.src1Sel < 4u || fields.dstSel < 4u; + if (!rule->byteSelectorClamp && fields.clamp != 0u && byteSelector) { + throw std::invalid_argument("VOP2 SDWA clamp with byte selectors is not implemented"); + } } void finalizeVop2Instruction(std::span code, std::uint32_t wordIndex, RdnaInstruction& instruction) { @@ -1938,7 +1959,7 @@ bool isF32DivisionStepOpcode(RdnaOpcode opcode) { } bool supportsNativeVop3Clamp(RdnaOpcode opcode) { - return supportsNativeVop3ResultModifiers(opcode) || usesInexactClampControl(opcode) || usesSignedSaturateClamp(opcode) || + return supportsNativeVop3ResultModifiers(opcode) || usesInexactClampControl(opcode) || usesIntegerSaturateClamp(opcode) || isF32DivisionStepOpcode(opcode) || isCubeOpcode(opcode); } @@ -1956,13 +1977,13 @@ void checkNativeVop3Modifiers(RdnaOpcode opcode, bool permlane, bool carryInOut, return; } if (isNativeVop3I16TernaryOpcode(opcode)) { - if (abs != 0u || clamp != 0u || omod != 0u || neg != 0u) { + if (abs != 0u || omod != 0u || neg != 0u) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); } return; } if (isNativeVop3AlignOpcode(opcode)) { - if (abs != 0u || clamp != 0u || omod != 0u || neg != 0u) { + if (abs != 0u || omod != 0u || neg != 0u) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); } return; @@ -1980,7 +2001,7 @@ void checkNativeVop3Modifiers(RdnaOpcode opcode, bool permlane, bool carryInOut, } return; } - if (usesSignedSaturateClamp(opcode)) { + if (usesIntegerSaturateClamp(opcode)) { if (abs != 0u || opSel != 0u || omod != 0u || neg != 0u) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); } diff --git a/core/shader/recompiler/Recompiler.hpp b/core/shader/recompiler/Recompiler.hpp index 9cf9234e1..054374fb5 100644 --- a/core/shader/recompiler/Recompiler.hpp +++ b/core/shader/recompiler/Recompiler.hpp @@ -310,6 +310,7 @@ struct DescriptorBinding { // Guest image elements the shader stores to (or updates atomically); the others are only read. std::vector imageWritten; std::vector imageDepthCompare; + std::vector imageAtomic; // Guest buffer elements the shader updates atomically (one entry per element of a GuestBuffers // binding, empty otherwise). An atomic on a host-imported range is a serialized PCIe round trip // (~0.4-0.5 us each on NVIDIA), so a driver may keep these elements in device-local memory. diff --git a/core/shader/recompiler/ShaderDiskCache.cpp b/core/shader/recompiler/ShaderDiskCache.cpp index fd57be587..c93a751b1 100644 --- a/core/shader/recompiler/ShaderDiskCache.cpp +++ b/core/shader/recompiler/ShaderDiskCache.cpp @@ -64,7 +64,7 @@ namespace ShaderRecompiler::ShaderDiskCache { #if defined(__linux__) && defined(__x86_64__) && defined(__GLIBCXX__) static_assert(sizeof(RecompileResult) == 176, "RecompileResult changed: update EncodeResult and DecodeResult"); -static_assert(sizeof(DescriptorBinding) == 264, "DescriptorBinding changed: update the binding encoder"); +static_assert(sizeof(DescriptorBinding) == 304, "DescriptorBinding changed: update the binding encoder"); static_assert(sizeof(VertexAttribute) == 28, "VertexAttribute changed: update the attribute encoder"); static_assert(sizeof(FragmentParameter) == 12, "FragmentParameter changed: update the parameter encoder"); static_assert(sizeof(CompiledShaderInfo) == 304, "CompiledShaderInfo changed: update the info encoder"); @@ -248,6 +248,7 @@ void encodeBinding(Writer& writer, const DescriptorBinding& binding) { writer.Flags(binding.samplerDepthCompare); writer.Flags(binding.imageWritten); writer.Flags(binding.imageDepthCompare); + writer.Flags(binding.imageAtomic); writer.Flags(binding.bufferAtomic); writer.Flags(binding.bufferWritten); } @@ -266,6 +267,7 @@ void decodeBinding(Reader& reader, DescriptorBinding& binding) { reader.Flags(binding.samplerDepthCompare); reader.Flags(binding.imageWritten); reader.Flags(binding.imageDepthCompare); + reader.Flags(binding.imageAtomic); reader.Flags(binding.bufferAtomic); reader.Flags(binding.bufferWritten); } diff --git a/core/shader/recompiler/ShaderDiskCache.hpp b/core/shader/recompiler/ShaderDiskCache.hpp index 216931eee..84c7a081b 100644 --- a/core/shader/recompiler/ShaderDiskCache.hpp +++ b/core/shader/recompiler/ShaderDiskCache.hpp @@ -12,7 +12,7 @@ namespace ShaderRecompiler::ShaderDiskCache { -inline constexpr std::uint32_t FormatVersion = 3; +inline constexpr std::uint32_t FormatVersion = 4; enum class LoadStatus { Loaded, diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp index d2ea10dd0..201f4f9ae 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp @@ -135,6 +135,7 @@ std::uint32_t Binary(SpirvEmitterState& state, std::uint32_t opcode, std::uint32 std::uint32_t Select(SpirvEmitterState& state, std::uint32_t type, std::uint32_t condition, std::uint32_t trueValue, std::uint32_t falseValue); std::uint32_t AtomicIncrement(SpirvEmitterState& state, std::uint32_t old, std::uint32_t limit); std::uint32_t AtomicDecrement(SpirvEmitterState& state, std::uint32_t old, std::uint32_t limit); +std::uint32_t AtomicUSubSat(SpirvEmitterState& state, std::uint32_t old, std::uint32_t value); std::uint32_t AtomicFloatMinMax(SpirvEmitterState& state, std::uint32_t old, std::uint32_t source, bool maxValue); std::uint32_t AtomicFloatCompareSwap(SpirvEmitterState& state, std::uint32_t old, std::uint32_t desired, std::uint32_t comparator); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp index ca3785c3d..c05dcf3df 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp @@ -508,6 +508,7 @@ void EmitDirectInstruction(SpirvValueEmitContext& ctx, const IrValue& inst) { case IrOpcode::AddressAtomicXor32: return Invoke(EmitAddressAtomic, ctx, inst); case IrOpcode::AddressAtomicInc32: return Invoke(EmitAddressAtomic, ctx, inst); case IrOpcode::AddressAtomicDec32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicUSubSat32: return Invoke(EmitAddressAtomic, ctx, inst); case IrOpcode::AddressAtomicSwap64: return Invoke(EmitAddressAtomic, ctx, inst); case IrOpcode::AddressAtomicCmpSwap64: return Invoke(EmitAddressAtomic, ctx, inst); case IrOpcode::AddressAtomicIAdd64: return Invoke(EmitAddressAtomic, ctx, inst); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp index 5edd79713..9fe529a7c 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp @@ -82,6 +82,10 @@ bool HasFlag(const MemoryInfo& mem, std::uint32_t flag) { return (mem.imageSampleFlags & flag) != 0u; } +const RdnaImageDimensionInfo& AddressDimension(const ImageEmitAccess& access) { + return RdnaImageDimensionInfoFor(access.mem.imageDimension); +} + std::uint32_t ZeroF32(SpirvEmitterState& state) { return ConstantF32(state, 0); } @@ -111,10 +115,10 @@ std::uint32_t AddressF32(SpirvValueEmitContext& ctx, const ImageEmitAccess& acce return GetRdnaImageAddressComponentLayout(access.mem.imageSampleFlags, component).bitWidth == 16u ? EmitF16BitsToF32(ctx.state, value) : Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), value); } -ImageSampleLayout Layout(const MemoryInfo& mem, RdnaImageDimension dimension) { +ImageSampleLayout Layout(const MemoryInfo& mem) { ImageSampleLayout layout; std::uint32_t cursor = 0; - const auto& info = RdnaImageDimensionInfoFor(dimension); + const auto& info = RdnaImageDimensionInfoFor(mem.imageDimension); if (HasFlag(mem, RdnaImageSampleFlagOffset)) { layout.offset = cursor++; } @@ -155,8 +159,8 @@ std::uint32_t CubeLayer(SpirvEmitterState& state, std::uint32_t value) { return result; } -std::uint32_t CoordF32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, std::uint32_t first, std::uint32_t components) { - if (first == NoImageComponent || access.mem.imageAddressComponents < first + components) { +std::uint32_t CoordF32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, std::uint32_t first, std::uint32_t components, std::uint32_t encoded) { + if (first == NoImageComponent || encoded < components || access.mem.imageAddressComponents < first + encoded) { ctx.Fail(access.inst, "has an image address with too few coordinate components"); } const bool cube = access.image.cube; @@ -184,7 +188,7 @@ std::uint32_t CoordF32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access std::uint32_t CoordU32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { const auto components = RdnaImageDimensionInfoFor(access.image.dimension).coordinateComponents; - if (access.mem.imageAddressComponents < components) { + if (AddressDimension(access).coordinateComponents < components || access.mem.imageAddressComponents < components) { ctx.Fail(access.inst, "has an image address with too few coordinate components"); } const auto x = AddressU32(ctx, access, 0); @@ -206,7 +210,7 @@ std::uint32_t LodU32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) if (!access.mem.imageHasMip) { return ConstantU32(ctx.state, 0); } - const auto component = RdnaImageDimensionInfoFor(access.image.dimension).coordinateComponents; + const auto component = AddressDimension(access).coordinateComponents; if (access.mem.imageAddressComponents <= component) { ctx.Fail(access.inst, "has an image address without the mip level component"); } @@ -691,7 +695,7 @@ void EmitQueryLodOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { auto& state = ctx.state; state.module.EmitCapability(spv::CapabilityImageQuery); const auto sampled = MakeSampledImage(state, access.mem.resource, access.mem.sampler, access.slot); - const auto coord = CoordF32(ctx, access, 0, RdnaImageDimensionInfoFor(access.image.dimension).spatialComponents); + const auto coord = CoordF32(ctx, access, 0, RdnaImageDimensionInfoFor(access.image.dimension).spatialComponents, AddressDimension(access).spatialComponents); const auto lod = state.module.AllocateId(); state.module.AddFunction(spv::OpImageQueryLod, TypeF32Vector(state, 2), lod, sampled, coord); std::uint32_t values[4] = {ConstantU32(state, 0), ConstantU32(state, 0), ConstantU32(state, 0), ConstantU32(state, 0)}; @@ -715,10 +719,11 @@ void EmitReadOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { const auto color = state.module.AllocateId(); const auto coord = CoordU32(ctx, access); if (dimensionInfo.multisampled != 0u) { - if (access.mem.imageAddressComponents <= dimensionInfo.coordinateComponents) { + const auto& addressInfo = AddressDimension(access); + if (addressInfo.multisampled == 0u || access.mem.imageAddressComponents <= addressInfo.coordinateComponents) { ctx.Fail(access.inst, "has no sample index in the image address"); } - state.module.AddFunction(spv::OpImageFetch, ImageVectorType(state, numericClass, 4), color, descriptor, coord, spv::ImageOperandsSampleMask, AddressU32(ctx, access, dimensionInfo.coordinateComponents)); + state.module.AddFunction(spv::OpImageFetch, ImageVectorType(state, numericClass, 4), color, descriptor, coord, spv::ImageOperandsSampleMask, AddressU32(ctx, access, addressInfo.coordinateComponents)); } else { state.module.AddFunction(spv::OpImageFetch, ImageVectorType(state, numericClass, 4), color, descriptor, coord, spv::ImageOperandsLodMask, LodU32(ctx, access)); } @@ -783,12 +788,12 @@ void EmitAtomicOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { SampleSetup MakeSampleSetup(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { const auto& dimensionInfo = RdnaImageDimensionInfoFor(access.image.dimension); - const auto layout = Layout(access.mem, access.image.dimension); + const auto layout = Layout(access.mem); const bool dref = HasFlag(access.mem, RdnaImageSampleFlagCompare); if (dref && access.image.conversionFormat != IrBufferFormat::Invalid) { ctx.Fail(access.inst, "uses depth comparison with a packed integer image"); } - const auto coord = CoordF32(ctx, access, layout.coord, dimensionInfo.coordinateComponents); + const auto coord = CoordF32(ctx, access, layout.coord, dimensionInfo.coordinateComponents, AddressDimension(access).coordinateComponents); return {dimensionInfo, layout, access.image.numericClass, dref, coord}; } @@ -908,8 +913,8 @@ void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, con std::vector operands; if (HasFlag(mem, RdnaImageSampleFlagDerivative)) { operandMask |= spv::ImageOperandsGradMask; - operands.push_back(CoordF32(ctx, access, setup.layout.gradX, setup.dimensionInfo.spatialComponents)); - operands.push_back(CoordF32(ctx, access, setup.layout.gradY, setup.dimensionInfo.spatialComponents)); + operands.push_back(CoordF32(ctx, access, setup.layout.gradX, setup.dimensionInfo.spatialComponents, AddressDimension(access).spatialComponents)); + operands.push_back(CoordF32(ctx, access, setup.layout.gradY, setup.dimensionInfo.spatialComponents, AddressDimension(access).spatialComponents)); } else if (explicitLod) { operandMask |= spv::ImageOperandsLodMask; operands.push_back(HasFlag(mem, RdnaImageSampleFlagLod) ? AddressF32(ctx, access, setup.layout.lod) : ZeroF32(state)); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp index 60d891f99..3657db5d6 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp @@ -1130,6 +1130,9 @@ std::uint32_t AddressAtomic(SpirvValueEmitContext& ctx, const IrValue& inst) { return increment ? AtomicIncrement(state, current, value) : AtomicDecrement(state, current, value); }); } + if (inst.Opcode() == IrOpcode::AddressAtomicUSubSat32) { + return AtomicUpdate(state, pointer, mem.kind, [&](std::uint32_t current) { return AtomicUSubSat(state, current, value); }); + } const AtomicFloatBits bits{state, wide}; const auto update = [&](auto&& replacement) { return AtomicUpdateTyped(state, pointer, mem.kind, scalarType, replacement); }; switch (inst.Opcode()) { @@ -1324,6 +1327,12 @@ std::uint32_t AtomicDecrement(SpirvEmitterState& state, std::uint32_t old, std:: return Select(state, TypeU32(state), wrap, limit, next); } +std::uint32_t AtomicUSubSat(SpirvEmitterState& state, std::uint32_t old, std::uint32_t value) { + const auto fits = Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), old, value); + const auto difference = Binary(state, spv::OpISub, TypeU32(state), old, value); + return Select(state, TypeU32(state), fits, difference, ConstantU32(state, 0u)); +} + std::uint32_t AtomicFloatMinMax(SpirvEmitterState& state, std::uint32_t old, std::uint32_t source, bool maxValue) { return AtomicFloatBits{state, false}.minMax(old, source, maxValue); } @@ -1828,11 +1837,7 @@ std::uint32_t EmitBufferAtomicDec32(SpirvValueEmitContext& ctx, const IrValue& i } std::uint32_t EmitBufferAtomicUSubSat32(SpirvValueEmitContext& ctx, const IrValue& inst) { - return EmitAtomicUpdate(ctx, inst, BufferMemory(ctx, inst), [](SpirvEmitterState& state, std::uint32_t old, std::uint32_t value) { - const auto fits = Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), old, value); - const auto difference = Binary(state, spv::OpISub, TypeU32(state), old, value); - return Select(state, TypeU32(state), fits, difference, ConstantU32(state, 0u)); - }); + return EmitAtomicUpdate(ctx, inst, BufferMemory(ctx, inst), AtomicUSubSat); } std::uint32_t EmitBufferAtomicIAdd64(SpirvValueEmitContext& ctx, const IrValue& inst) { diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp index 390ac734b..6532d500d 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp @@ -141,6 +141,13 @@ namespace ShaderRecompiler if (control == 0x141u) { return EmitDppMirrorTargetLane(state, subid, true); } + if (control >= 0x150u && control <= 0x15fu) { + const auto row = state.module.AllocateId(); + const auto target = state.module.AllocateId(); + state.module.AddFunction(spv::OpBitwiseAnd, TypeU32(state), row, subid, ConstantU32(state, 0xfffffff0u)); + state.module.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, row, ConstantU32(state, control & 0xfu)); + return {target, ConstantBool(state, true)}; + } if (control >= 0x160u && control <= 0x16fu) { const auto target = state.module.AllocateId(); state.module.AddFunction(spv::OpBitwiseXor, TypeU32(state), target, subid, ConstantU32(state, control & 0xfu)); diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index b2e93e342..3bf343733 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -193,6 +193,8 @@ private: bool integer16Shift(const RdnaInstruction& inst, IrOpcode opcode, bool arithmetic); bool integer16Binary(const RdnaInstruction& inst, IrOpcode opcode, bool sign); bool integer16Mad(const RdnaInstruction& inst, bool sign, bool wide); + IrU32 saturateU16Result(const RdnaInstruction& inst, const IrU32& value, bool sign); + IrU32 wideAdd(const RdnaInstruction& inst, const IrU32& low, const IrU32& high, const IrU32& addend, bool sign); bool vAddSubNcI32(const RdnaInstruction& inst, bool subtract); bool integer16Ternary(const RdnaInstruction& inst, IrOpcode opcode, bool sign); bool vMed3I16(const RdnaInstruction& inst); diff --git a/core/shader/recompiler/Translation/src/IntegerInstructions.cpp b/core/shader/recompiler/Translation/src/IntegerInstructions.cpp index 9e9949ba3..ba6b25f67 100644 --- a/core/shader/recompiler/Translation/src/IntegerInstructions.cpp +++ b/core/shader/recompiler/Translation/src/IntegerInstructions.cpp @@ -25,6 +25,33 @@ bool TranslationContext::integer16Binary(const RdnaInstruction& inst, IrOpcode o return true; } +IrU32 TranslationContext::saturateU16Result(const RdnaInstruction& inst, const IrU32& value, bool sign) { + if (!inst.destination.clamp) { + return value; + } + if (!sign) { + return IrU32(ir.Emit(IrOpcode::UMin32, IrType::U32, {&value.Value(), &ir.Constant(0xffffu)})); + } + const IrU32 upper(ir.Emit(IrOpcode::SMin32, IrType::U32, {&value.Value(), &ir.Constant(0x7fffu)})); + return IrU32(ir.Emit(IrOpcode::SMax32, IrType::U32, {&upper.Value(), &ir.Constant(0xffff8000u)})); +} + +IrU32 TranslationContext::wideAdd(const RdnaInstruction& inst, const IrU32& low, const IrU32& high, const IrU32& addend, bool sign) { + const IrU32 sum(ir.IAdd(low.Value(), addend.Value())); + if (!inst.destination.clamp) { + return sum; + } + const IrU32 carry(ir.Select(ir.ULessThan(sum.Value(), low.Value()), ir.Constant(1u), ir.Constant(0u))); + IrU32 top(ir.IAdd(high.Value(), carry.Value())); + if (!sign) { + return IrU32(ir.Select(ir.INotEqual(top.Value(), ir.Constant(0u)), ir.Constant(0xffffffffu), sum.Value())); + } + top = IrU32(ir.IAdd(top.Value(), ir.ShiftRightArithmetic(addend.Value(), ir.Constant(31u)))); + const IrU1 overflow(ir.INotEqual(top.Value(), ir.ShiftRightArithmetic(sum.Value(), ir.Constant(31u)))); + IrValue& saturated = ir.BitwiseXor(ir.ShiftRightArithmetic(top.Value(), ir.Constant(31u)), ir.Constant(0x7fffffffu)); + return IrU32(ir.Select(overflow.Value(), saturated, sum.Value())); +} + bool TranslationContext::vMed3I16(const RdnaInstruction& inst) { const IrU32 first = readU16AsU32(sourceAt(inst, 0u), true); const IrU32 second = readU16AsU32(sourceAt(inst, 1u), true); @@ -46,11 +73,14 @@ bool TranslationContext::integer16Ternary(const RdnaInstruction& inst, IrOpcode bool TranslationContext::integer16Mad(const RdnaInstruction& inst, bool sign, bool wide) { const IrU32 lhs = readU16AsU32(sourceAt(inst, 0u), sign); const IrU32 rhs = readU16AsU32(sourceAt(inst, 1u), sign); - const IrU32 addend = wide ? readU32(sourceAt(inst, 2u)) : readU16AsU32(sourceAt(inst, 2u), sign); - const IrU32 result(ir.IAdd(ir.IMul(lhs.Value(), rhs.Value()), addend.Value())); + const IrU32 product(ir.IMul(lhs.Value(), rhs.Value())); if (wide) { + const IrU32 high(sign ? ir.ShiftRightArithmetic(product.Value(), ir.Constant(31u)) : ir.Constant(0u)); + const IrU32 result = wideAdd(inst, product, high, readU32(sourceAt(inst, 2u)), sign); writeOperand(inst.destination, &result.Value()); } else { + const IrU32 addend = readU16AsU32(sourceAt(inst, 2u), sign); + const IrU32 result = saturateU16Result(inst, IrU32(ir.IAdd(product.Value(), addend.Value())), sign); write16Bits(inst.destination, IrU32(ir.BitwiseAnd(result.Value(), ir.Constant(0xffffu)))); } return true; @@ -323,7 +353,8 @@ bool TranslationContext::integer24(const RdnaInstruction& inst, bool sign, bool result = IrU32(ir.Select(ir.INotEqual(top.Value(), expected), saturated, result.Value())); } if (addend) { - result = IrU32(ir.IAdd(result.Value(), readU32(sourceAt(inst, 2u)).Value())); + const IrU32 productHigh(inst.destination.clamp ? ir.Emit(sign ? IrOpcode::SMulHi : IrOpcode::UMulHi, IrType::U32, {&lhs.Value(), &rhs.Value()}) : ir.Constant(0u)); + result = wideAdd(inst, result, productHigh, readU32(sourceAt(inst, 2u)), sign); } writeOperand(inst.destination, &result.Value()); return true; @@ -377,7 +408,7 @@ bool TranslationContext::vSadU32(const RdnaInstruction& inst) { const IrU32 lo(ir.Emit(IrOpcode::UMin32, IrType::U32, {&lhs.Value(), &rhs.Value()})); const IrU32 hi(ir.Emit(IrOpcode::UMax32, IrType::U32, {&lhs.Value(), &rhs.Value()})); const IrU32 addend = readU32(sourceAt(inst, 2u)); - const IrU32 result(ir.IAdd(ir.ISub(hi.Value(), lo.Value()), addend.Value())); + const IrU32 result = wideAdd(inst, IrU32(ir.ISub(hi.Value(), lo.Value())), IrU32(ir.Constant(0u)), addend, false); writeOperand(inst.destination, &result.Value()); return true; } @@ -405,7 +436,7 @@ bool TranslationContext::subwordSad(const RdnaInstruction& inst, std::uint32_t f sum = IrU32(ir.ShiftLeftLogical(sum.Value(), ir.Constant(sumShift))); } const IrU32 addend = readU32(sourceAt(inst, 2u)); - const IrU32 result(ir.IAdd(sum.Value(), addend.Value())); + const IrU32 result = wideAdd(inst, sum, IrU32(ir.Constant(0u)), addend, false); writeOperand(inst.destination, &result.Value()); return true; } @@ -423,14 +454,15 @@ bool TranslationContext::vQsadU8(const RdnaInstruction& inst, bool masked, bool const std::array accumulator = readU32Pair(sourceAt(inst, 2u)); if (wide) { const std::array accumulatorHigh = readU32Pair(offsetOperand(sourceAt(inst, 2u), 2u)); - writeU32Pair(inst.destination, {IrU32(ir.IAdd(accumulator[0].Value(), sad(0u).Value())), IrU32(ir.IAdd(accumulator[1].Value(), sad(8u).Value()))}); + const IrU32 zero(ir.Constant(0u)); + writeU32Pair(inst.destination, {wideAdd(inst, sad(0u), zero, accumulator[0], false), wideAdd(inst, sad(8u), zero, accumulator[1], false)}); writeU32Pair(offsetOperand(inst.destination, 2u), - {IrU32(ir.IAdd(accumulatorHigh[0].Value(), sad(16u).Value())), IrU32(ir.IAdd(accumulatorHigh[1].Value(), sad(24u).Value()))}); + {wideAdd(inst, sad(16u), zero, accumulatorHigh[0], false), wideAdd(inst, sad(24u), zero, accumulatorHigh[1], false)}); return true; } const auto packed = [&](const IrU32& addend, std::uint32_t shift) { - const IrU32 low(ir.IAdd(addend.Value(), sad(shift).Value())); - const IrU32 high(ir.IAdd(ir.ShiftRightLogical(addend.Value(), ir.Constant(16u)), sad(shift + 8u).Value())); + const IrU32 low = saturateU16Result(inst, IrU32(ir.IAdd(ir.BitwiseAnd(addend.Value(), ir.Constant(0xffffu)), sad(shift).Value())), false); + const IrU32 high = saturateU16Result(inst, IrU32(ir.IAdd(ir.ShiftRightLogical(addend.Value(), ir.Constant(16u)), sad(shift + 8u).Value())), false); return IrU32(ir.BitwiseOr(ir.BitwiseAnd(low.Value(), ir.Constant(0xffffu)), ir.ShiftLeftLogical(high.Value(), ir.Constant(16u)))); }; writeU32Pair(inst.destination, {packed(accumulator[0], 0u), packed(accumulator[1], 16u)}); diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp index b5bade463..66bc02885 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp @@ -217,6 +217,11 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) { return flatAtomic(inst, IrOpcode::AddressAtomicIAdd32); case RdnaOpcode::FlatAtomicSub: return flatAtomic(inst, IrOpcode::AddressAtomicISub32); + case RdnaOpcode::GlobalAtomicCsub: + if (!inst.glc) { + throw std::runtime_error("global_atomic_csub without glc is not supported, at pc " + std::to_string(inst.programCounter)); + } + return flatAtomic(inst, IrOpcode::AddressAtomicUSubSat32); case RdnaOpcode::FlatAtomicSmin: return flatAtomic(inst, IrOpcode::AddressAtomicSMin32); case RdnaOpcode::FlatAtomicUmin: diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index d8a1f4b86..9788be41a 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -12,48 +12,61 @@ Throughout the project, every function at every stage either **does exactly what it's supposed to or throws an exception**. Everywhere... except: - [libSceSaveDataDialog.native](../../core/libs/prx/libSceSaveDataDialog.native/Export.cpp) +- [libSceMsgDialog.native](../../core/libs/prx/libSceMsgDialog.native/Export.cpp) - dialogs finish at `sceMsgDialogOpen` without UI and answer with button 1, so `sceMsgDialogClose` returns `NOT_RUNNING`; its result before `sceMsgDialogInitialize` follows libSceMsgDialog - [libSceCommonDialog](../../core/libs/prx/libSceCommonDialog/Export.cpp) - [libSceErrorDialog](../../core/libs/prx/libSceErrorDialog/Export.cpp) - the error dialog runs the state machine but shows nothing; the error code passed to `sceErrorDialogOpen` is only logged - [libSceHmd](../../core/libs/prx/libSceHmd/Export.cpp) implements only the disconnected-headset path: initialization succeeds, device queries report `NotDetected`, and opening a device returns `DeviceDisconnected`. Headset support, tracking, rendering and additional HMD exports are not implemented; SDK-level ABI compatibility and in-game behaviour remain unverified. - [libSceAudioPropagation](../../core/libs/prx/libSceAudioPropagation/Export.cpp) implements only the no-acoustic-propagation path: the system keeps a 16-byte header in the title's CPU memory (no GPU memory), rooms, portals, sources and materials are tracked handles, attributes are validated and have no effect, no rays are requested and sources report no audio paths. Ray results, path calculation and audio paths are accepted only when empty and throw otherwise; `sceAudioPropagationSourceRender` writes silence (zeroes) to each source's output buffer for the given size, without a dry signal. Objects still alive when their system is destroyed become unusable, and their later destroy or unregister is a no-op: PPSA21567 releases its reference-counted room, portal and source owners just before `sceAudioPropagationSystemDestroy`, so the order for every object is not proven. Occlusion, reflections and reverb from level geometry are absent; in-game behaviour remains unverified. -- [libSceNpCommerce](../../core/libs/prx/libSceNpCommerce/Export.cpp) - the PS Store icon show/hide calls do nothing +- [libSceNpCommerce](../../core/libs/prx/libSceNpCommerce/Export.cpp) - the PS Store icon show/hide calls and `sceNpCommerceSetPsStoreIconLayout` do nothing - [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) validates the priority and CPU affinity that the open param requests for the VideoOut service thread but does not apply them: the port's present and vblank threads are host threads, and guest priorities and affinities do not reach host scheduling - The shader recompiler [skips baryctric coordinates](../../core/shader/recompiler/Recompiler.cpp) (is not even passed to SpirvTargetOptions at row 212). - [libSceAvPlayer](../../core/libs/prx/libSceAvPlayer/Export.cpp): `sceAvPlayerSetLogCallback` accepts a callback that is never called, as the player produces no log messages, and `sceAvPlayerSetAvailableBandwidth` has no effect, as it governs HLS sources, which `sceAvPlayerAddSource` does not implement. +- [libSceNpTrophy2](../../core/libs/prx/libSceNpTrophy2/src/GameInfo.cpp): `sceNpTrophy2RegisterUnlockCallback` accepts a callback that is never called, as no trophy is ever unlocked, and `sceNpTrophy2UnregisterUnlockCallback` only returns success. - [sceAvPlayerSetTrickSpeed](../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) with a negative speed runs the clock backwards but delivers no frames; when a forward speed is set again, playback resumes from the rewound time. +- [ulobjmgr](../../core/libs/prx/ulobjmgr/Export.cpp) registers no object: `_sceUlobjmgrRegisterObject` always hands out id 0 and `_sceUlobjmgrUnregisterObject` releases nothing, as shadPS4 does ### Unknown function info - [sceAvPlayerAddSource](../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) - requests up to 4 additional decode-ahead video framebuffers from the memory replacement beyond `num_output_video_framebuffers` when memory is available; the console's internal buffering behaviour is unknown +- [sceVideoOutRegisterBuffers2](../../core/libs/prx/libSceVideoOut/src/Buffer.cpp) (libSceVideoOut) - buffer option 8 (STRICT_COLORIMETRY) is presented as option 0, with the stored values unchanged; what the console changes for it is unknown. Only options 0 and 8 are accepted - [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 - [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`); a string without a zone is taken as UTC, and anything outside this shape throws instead of returning a parse error, unverified against hardware - [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 markers](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - signatures taken from the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation, not confirmed on a title: `sceAmprCommandBufferSetMarkerWithColor` takes the color by pointer, the push and measure variants by value. Colors are not recorded - [sceFontGetRenderScaledKerning](../../core/libs/prx/libSceFont/src/Render.cpp) (libSceFont) - arguments, the cleared output and the bound renderer check taken from `cellFontGetRenderScaledKerning` in [RPCS3](https://github.com/RPCS3/rpcs3/blob/master/rpcs3/Emu/Cell/Modules/cellFont.cpp) (PS3 predecessor of the library), not confirmed on a title. The kerning is the `sceFontGetKerning` one at the render scale - [sceFontGetPixelResolution](../../core/libs/prx/libSceFont/src/Library.cpp) (libSceFont) - signature taken from the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json), not confirmed on a title. The value is the one the library's driver reports (64 for FreeType); the argument checks and the cleared output follow `sceFontGetLibrary` +- [sceSaveDataTransferringMountPs4, sceSaveDataDirNameSearchPs4](../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - arguments assumed to be those of `sceSaveDataTransferringMount` and `sceSaveDataDirNameSearch`: KytyPS5 binds both NIDs to the PS5 functions, and [prosper](https://github.com/mattias800/prosper) captured the same result layout on a title for the search. There is no PS4 save area, so the mount returns `NOT_FOUND` and the search reports zero hits - [sceAgcSetSubmitMode](../../core/libs/prx/libSceAgc/Misc/src/Suspend.cpp) (libSceAgc) - mode values unknown; only 0 is accepted +- [sceAgcGetIsTrinityMode](../../core/libs/prx/libSceAgc/Misc/src/Platform.cpp) (libSceAgc) - signature from the only PPSA26344 call (a pointer to a one-byte flag, result ignored); a null pointer throws because its error code is unknown - [zARR5aCmkoY](../../core/libs/prx/libSceAgc/DcbFlow/src/Control.cpp) (libSceAgc) - unknown name, signature - [qj7QZpgr9Uw](../../core/libs/prx/libSceAgc/DcbState/src/ContextState.cpp) (libSceAgc) - unknown name - [fd5Bp5tGTgo](../../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name - [dolOmWH+huQ](../../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name +- [dbOlWdppb4o](../../core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp) (libSceAgc) - unknown name; same arguments as `sceAgcCreateInterpolantMapping`; the `SPI_PS_INPUT_CNTL` bits for `is_f16` 2 inputs follow KytyPS5, not confirmed on hardware - [V++UgBtQhn0](../../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc) - unknown name - [gQkqkLttcpw](../../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - unknown name, signature - [sceAgcDcbPrimeUtcl2](../../core/libs/prx/libSceAgc/DcbState/src/Display.cpp) (libSceAgc) - assumed to write the packet `sceAgcAcbPrimeUtcl2` writes: PRIME_UTCL2 has the same 5 dwords on the graphics and compute rings. Not confirmed on a title. Both write a NOP of that size, as there is no TLB to prime - [sceKernelInternalMemoryGetModuleSegmentInfo](../../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) - unknown signature - [sceKernelSyncOnAddressWait](../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - the only known caller passes a null timeout and a name string as the fourth argument; the timeout is assumed to point to microseconds like the other kernel waits, and the name is ignored +- [sceKernelSyncOnAddressWait32 / sceKernelSyncOnAddressWait64](../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - libc's `__std_atomic_wait_direct_32/64` pass a microsecond timeout or null and ignore the result; the by-value comparand, return values and Wait64's 8-byte alignment are assumed - [sceKernelMapNamedFlexibleMemoryInternal](../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - flag 0x8000 unknown; only the sceKernelMapNamedFlexibleMemory flags are accepted +- [Guest arena](../../core/libs/prx/libc/src/GuestArena.cpp) (libc) - the application map area is assumed to end at 0xFC_0000_0000, as `SCE_KERNEL_APP_MAP_AREA_END_ADDR` does on the PS4. On Windows, host allocations made in the arena before libc loads stay, and a fixed guest mapping over one throws - [sceLibcInternalBacktraceForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature - [sceLibcInternalHeapErrorReportForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature +- [_sceLibcInternalThreadAtexit, _sceLibcInternalThreadDtors](../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - FreeBSD `__cxa_thread_atexit` semantics assumed; a null or non-image destructor and an allocation failure throw +- [_sceLibcInternalForceTlsDestructor](../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - unknown return type and behaviour; a title's `libc.prx` calls it from `__cxa_finalize` with the module handle of a non-null dso - [__progname](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - unknown data export - [pthread_barrierattr_setpshared](../../core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp) (libkernel) - PTHREAD_PROCESS_SHARED throws: FreeBSD 9.0 libthr rejects it with EINVAL and 11.0 accepts it, and which one the console follows is unknown - [sceSslClose](../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - unknown signature - [sceSslGetSerialNumber](../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - unknown signature - [X+4jdIS75P0](../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown name, signature +- [sceAudioOut2Set3DLatency](../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) (libSceAudioOut) - assumed to take `(user_id, latency)`: KytyPS5 reads `(user_id, output, latency_us)`, but the only known call sets just edi and esi; only `(0xFF, 2)` is accepted +- [sceAudioOut2MasteringInit](../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) - flag values unknown; only 0 is accepted - [sceAudioOutSetMixLevelPadSpk](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - effect of a negative mix level unknown; only 0 to 32768 (0 dB) is accepted - [sceAudioOutSetMixLevelPadSpk](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - no source applies the level (shadPS4 only stores it); it is assumed to be a linear gain on the port's own output, and the -9 dB default (11626) is applied to every pad speaker port, including those of titles that never call the function - [sceAudioOutGetLastOutputTime](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - signature and behaviour taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [fpPS4](https://github.com/red-prig/fpPS4) reimplementations of the PS4 library, not confirmed on a PS5 title: the time is the process time at which `sceAudioOutOutput` or `sceAudioOutOutputs` accepted the port's last block, not the time the block reached the device - [AOWqIYsgVHs](../../core/libs/prx/libSceContentExport/Export.cpp) (libSceContentExport) - unknown name, signature -- [GQTObcITIXI](../../core/libs/prx/libSceShare/Export.cpp) (libSceShare) - unknown name, signature +- [scePsmlMfsrGetContextBufferRequirement1100, scePsmlMfsrCreateContext1100, scePsmlMfsrGetDispatchMfsrPacket1100](../../core/libs/prx/libScePsml_debug/Export.cpp) (libScePsml_debug) - argument layouts unknown; only 0x8A810001 (not initialized) is returned - [BnMAMrsfVWo](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libc) - unknown name, signature - [scePngEncEncode](../../core/libs/prx/libScePngEnc/Export.cpp) (libScePngEnc) - whether rows may stay unfiltered when `filter_type` names a subset of the filters is unknown; as in shadPS4, they may only for 0 and for the all-filters mask - AudioIn, NpSessionSignaling and PlayerInvitationDialog exports added without an implementation have assumed signatures @@ -61,34 +74,44 @@ Throughout the project, every function at every stage either **does exactly what - [sceAvPlayerStartEx](../../core/libs/prx/libSceAvPlayer/Export.cpp) (libSceAvPlayer) - start info layout unknown; it is ignored and playback starts as with `sceAvPlayerStart` - [sceAvPlayerInit / sceAvPlayerInitEx](../../core/libs/prx/libSceAvPlayer/src/Player.cpp) (libSceAvPlayer) - behaviour without a memory replacement unknown; frame and sample buffers then come from the guest heap - [SceAvPlayerVideoEx](../../core/libs/SceTypes.hpp) (libSceAvPlayer) - frame rate field and encoding unknown; it is left zero in frame and stream info +- [sceRazorCpuPushMarkerStatic, sceRazorCpuPopMarker](../../core/libs/prx/libSceRazorCpu/Export.cpp) (libSceRazorCpu) - signatures from PPSA26344's calls; (name, color, flags) assumed for the push; both do nothing without a capture - [sceAjmDecMp3ParseFrame](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - signature, result layout and rate tables taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation of the PS4 library, not confirmed on a PS5 title: the layer field is not checked and MPEG-2.5 stops at 64 kbps - [sceAjmBatchJobGetCodecInfo, sceAjmBatchJobGetGaplessDecode](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the `RUN_GET_CODEC_INFO` flag (0x800), the ATRAC9 codec info layout and its next frame size are taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [RPCSX](https://github.com/RPCSX/rpcsx) reimplementations, not confirmed on a PS5 title; a run job without buffers returns result 0, as in shadPS4. The gapless sideband reports the total and skip counts the title set and does not count them down: RPCSX does the same, shadPS4 counts them down and BryKytyPS5 returns its current counts - [AJM Opus decoder](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native, codec 24) - the initialize parameter layout (`u32 channels, u32 sample rate, u32`, seen as `2, 48000, 0`) and the little-endian `u16` byte count before each packet come from one title (The Smurfs Dreams); only 48000 Hz and a zero third word are accepted - [sceHttpParseResponseHeader](../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature +- [sceHttpParseStatusLine](../../core/libs/prx/libSceHttp/src/Parse.cpp) (libSceHttp) - behaviour and error codes follow the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation of the PS4 library, not confirmed on a PS5 title; unlike it, no byte past `lineLen` is read - [sceHttp2GetMemoryPoolStats](../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - unknown signature - [sceNetGetMemoryPoolStats](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceHttpSetRequestStatusCallback](../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - no reference implements it (shadPS4 only logs and returns 0); when the callback runs and what status it gets is unknown - [sce::Np::CppWebApi::Common::IntrusivePtr](../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x18-byte layout (object, deleter, `LibContext*`) and the offset of the 32-bit reference count in the pointee (0x00, 0x30 for `Vector`) come from the module bundled with one title (PPSA21564); other SDK versions are not checked. The destructor throws on the last reference of a pointer without a deleter: destroying the object needs its destructor and the `LibContext` allocator, which are not implemented - [sceNpSessionSignalingGetMemoryInfo](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature - [sceNpSessionSignalingGetConnectionStatistics](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature - [sce::Np::CppWebApi::Common::String](../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x20-byte layout (32-bit reference count, buffer at 0x08, buffer size at 0x10, `LibContext*` at 0x18) comes from the module bundled with one title (PPSA21564); other SDK versions are not checked. The destructor throws for a non-null buffer: releasing it needs the `LibContext` allocator, which is not implemented -- [sceNpTrophy2UnregisterUnlockCallback](../../core/libs/prx/libSceNpTrophy2/src/Context.cpp) (libSceNpTrophy2) - unknown signature - [sceUserServiceGetAgeLevel](../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - the meaning of the value is unknown; 0 is reported, as for the unset game presets. PPSA12544 (Unity) only stores it for its user profile - [sceSystemServiceDisableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature - [sceSystemServiceReenableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature +- [sceSystemServiceParamGetString](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - the system name (parameter 6) is the fixed `PS5`, not the console's name; other parameters and buffers of 1 to 64 bytes throw - [sceVoiceSetMuteFlag](../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature - [snwprintf_s](../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follows C11 K.3.9.1.3 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`. A runtime-constraint violation only returns a negative value: `set_constraint_handler_s` is not exported and the console's default handler is unknown. An invalid multibyte `%s` argument is copied byte by byte instead of being reported as an encoding error, as in `vswprintf` - [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) - the added `sceUltQueuePop`, `sceUltQueueTryPush`, `sceUltQueueDestroy` and `sceUltQueueDataResourcePoolDestroy` signatures are inferred from the existing push/pop and object APIs. Queue destruction is assumed to reject blocked callers with `ULT_ERROR_BUSY` and otherwise discard queued data; pool exhaustion returns `ULT_ERROR_AGAIN`. These rules, alignment checks and shared slot limits have not been confirmed on PS5 hardware or a title. Non-null queue and data-pool option parameters throw because their layouts are unknown. +- [_sceUlobjmgrRegisterObject](../../core/libs/prx/ulobjmgr/Export.cpp) (ulobjmgr) - parameters taken from shadPS4 (PS4): a nonzero 64-bit object, a nonzero 32-bit kind and a 32-bit id output; the meaning of the first two and the PS5 signature are unverified. The id bound of `_sceUlobjmgrUnregisterObject` (below 0x4000) and the raw `EINVAL` (22) return also come from shadPS4 ### Functional - [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) use preallocated host storage instead of the supplied guest work area; its existing size formula and the 512-byte object layouts remain unverified. Waiting-queue pool thread limits and waiter priority ordering are not implemented. Finalization wakes blocked queue callers with `ULT_ERROR_STATE`. - [MIMG `tfe` and `lwe`](../../core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp) never write the status VGPR after the data. On hardware it is written only when a texel fetch hits an unmapped page of a partially resident texture, or, for `lwe` on sample/gather, when the LOD is below the T# `MIN_LOD_WARN`; texture memory is always resident here, and `lwe` on sample, gather and `image_get_lod` throws. +- [Image atomics](../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) on 32_SINT and 32_FLOAT surfaces act on the raw dword through an R32_UINT storage view; their float results are assumed to match those measured on 32_UINT surfaces. Other formats throw. - The shader recompiler [ignores `s_setreg_b32`](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp): writes to MODE (float rounding and denormal controls) and the other hardware registers have no effect, and `s_getreg_b32` reads of the MODE round fields report round to nearest even even after `s_setreg_b32` wrote another mode. The initial round mode from the shader's program registers is not read either. - [Shader recompilation](../../core/shader/recompiler/Recompiler.cpp) currently occurs right before it was transferred to Vulkan with caching, but should be moved to the [relinker](../../core/relinker/main.cpp) stage. For this purpose, [shader/recompiler](../../core/shader/recompiler) was written completely independently from [libs/prx](../../core/libs/prx). - [v_fma_f64](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp) is fused exactly only for normal inputs: with a subnormal input it is a separate multiply and add, which rounds twice. +- [COND_EXEC](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp) reads its condition after the earlier packets of its queue complete. Without PFP_SYNC_ME the console's graphics PFP reads it ahead of the ME, so a title that races the read sees a later value here. +- A [flip inside a COND_EXEC range](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp) throws: the flip is reserved with the video output when the buffer is submitted, and how the console withdraws a skipped one is unknown. +- A [COND_EXEC range](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) must hold whole packets and end inside its command buffer. A chained INDIRECT_BUFFER or a REWIND inside a range throws, as what the CP does with the skipped dwords then is unknown. - [v_mullit_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follows the hardware measured with the MODE register at IEEE=1 and denormals kept (a signaling NaN src0 is quieted, a positive denormal src2 counts as positive): the recompiler does not read MODE, so other IEEE/denormal settings are not modelled. +- [v_fma_legacy_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (VOP3 0x140) is assumed to be fused, which has not been measured on hardware. LLVM decodes this encoding as `v_mad_legacy_f32` for gfx1013, and `v_mac_legacy_f32` (VOP2 0x06) was measured to round its product before the add, so 0x140 may do the same. - The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability. - [libSceJpegEnc](../../core/libs/prx/libSceJpegEnc/Export.cpp) encodes 4:2:2 sampling requests with 4:2:0 chroma subsampling, and grayscale requests as a 3-component JPEG with neutral chroma instead of a single-component one: the [JPEG encoder](../../core/Decoder/Jpeg/src/Jpeg.cpp) (stb) only produces 3-component 4:2:0 and 4:4:4 images. +- [PA_CL_CLIP_CNTL](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - `DX_LINEAR_ATTR_CLIP_ENA` (bit 24) clear is drawn like bit 24 set, with Vulkan's clipping of `NoPerspective` outputs; what a clear bit changes on the console is undocumented. - [v_rcp, v_rsq, v_sqrt, v_log, v_exp, v_sin and v_cos](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (f32 and f16) use the driver's approximation for finite results, not the console's: the low bits can differ. Only NaN, invalid, zero and infinite results and the exact sin/cos zeros are chosen on the bits. v_sin_f32/v_cos_f32 with inputs beyond ±256 cycles were not compared with the console. - `--to-intel` does not lower RDPRU/MCOMMIT; the [matcher](../../core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp) fails the relink instead. SHA-1 and SHA-256 instructions with a RIP-relative operand fail the relink. - The length-changing path of the [instruction rewriter](../../core/relinker/codegen/src/x86/X64InstructionRewriter.cpp) is not used by the [converter](../../core/relinker/codegen/src/Amd64OnlyConverter.cpp): it does not adjust VEX/0F38/0F3A RIP-relative operands, data-to-code references (relocations, FDEs, jump tables) or segment sizes, so every substitution keeps the instruction length. @@ -97,8 +120,11 @@ Throughout the project, every function at every stage either **does exactly what - `--to-intel` guest module trampolines are covered only by a synthetic relinker test; no game title has been verified with them on Linux or Windows. - `--to-intel` replaces the approximate VEX.128 register forms of VRSQRTPS and VRCPPS with [correctly rounded](../../core/relinker/codegen/src/x86/ReciprocalLowering.cpp) 1/sqrt(x) and 1/x, not with the AMD approximation: its tables are not public. Intel's approximation of 1/sqrt(1.0) is 0x3F7FF000, and two Newton-Raphson steps on it settle on 0.99999994 instead of 1.0; a renormalised identity quaternion then turns into a NaN axis in PPSA21564, which the console does not show. Refined results now reach the exact value; unrefined ones can still differ from the console in the low bits. Each site costs an out-of-line stub (two jumps, a spill below the red zone, SQRTPS/DIVPS). The 256-bit, scalar, legacy and memory forms keep the native instruction, as does a 4-byte site with no movable instruction after it for the jump (5 sites in PPSA21564). - [sceKeyboardGetKey2Char](../../core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp) (libSceKeyboard) translates only the 101-key (US) arrangement and throws for the 106-key (Japanese) one; Ctrl and Alt do not change the character. +- [sceAudioOut2Set3DLatency](../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) and [sceAudioOut2MasteringInit](../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) have no effect: there is no 3D rendering or mastering stage. - [libSceAudiodec](../../core/libs/prx/libSceAudiodec/Export.cpp) throws for the 24-bit PCM word size (`iBwPcm` 0): its sample layout is unknown. ATRAC9 decoding is covered only by configuration and error tests, as no ATRAC9 encoder is available for a fixture. - [sceAjmDecMp3ParseFrame](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) throws when the original file length is requested (`parse_ofl` non-zero): reading it from the frame's Xing/Info, VBRI or Fraunhofer data is not implemented. - The [output modifier](../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) (`mul:2`, `mul:4`, `div:2`) follows the hardware with the IEEE mode off and f32 denormals flushed, the LLVM graphics default; the shader's MODE register is not read, and the hardware ignores the modifier in the other modes. On f16 results it is ignored, as on the hardware while f16 denormals are enabled; with them flushed the hardware applies it. - [libScePlayerInvitationDialog](../../core/libs/prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.cpp) simulates dialog completion without displaying UI or sending invitations; its parameter ABI remains unverified. - [64-bit LDS atomics](../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) (`ds_*_u64`, `ds_*_b64`, `ds_*_f64`) read and write the two dwords under a workgroup lock stored after the guest LDS, as LDS is a 32-bit array without 64-bit atomics. They are atomic with respect to each other only: 32-bit accesses to the same dwords issued concurrently are not ordered by the lock. 64-bit GDS atomics throw. `ds_min/max(_rtn)_f64` and `ds_cmpst(_rtn)_f64` follow the NaN and signed-zero rules measured for the f32 forms; the f64 forms were not measured on hardware. +- [wcsrtombs_s](../../core/libs/prx/libc/src/Multibyte.cpp) (libc) follows the console's `libc.prx`, but a runtime-constraint violation only returns its error: the default handler there writes the message and a newline to `stderr` before returning, which is not done here. Conversion uses the C locale of `wcrtomb` (`MB_CUR_MAX` 1), so the path where a multibyte character does not fit the remaining space is not reached +- [Thread exit destructors](../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) run when a libkernel thread finishes; on the main or a host thread only `_sceLibcInternalThreadDtors` runs them, as `exit` does, else they are dropped diff --git a/docs/user/USAGE.md b/docs/user/USAGE.md index 3b8a408be..7cb429298 100644 --- a/docs/user/USAGE.md +++ b/docs/user/USAGE.md @@ -70,6 +70,8 @@ app0/ Use `sce_modules/` or `prx/` instead of `sce_module/` if that is the input directory name. Relinker preserves each module's directory under `app0/` and prints its exact path. Place app resources in `app0/` separately. Copy the built system libraries from `build/core/libs/libs/*.prx` into `libs/`; use libraries built for the target OS. A custom `--rpath` changes the system library location. +On Windows, direct memory (`sceKernelAllocateDirectMemory`, up to 13824 MiB per title) is committed in full when the title allocates it, not when its pages are first used. The system commit limit (installed memory plus page file size, the second value of Committed in Task Manager) must cover it together with all other committed memory. Otherwise the allocation throws `create direct memory backing of 0x bytes ( MiB)` with the Windows error; enlarge the page file or close other applications. + Linux: ```sh diff --git a/tools/progress.py b/tools/progress.py index b78a73e89..81ceeea05 100644 --- a/tools/progress.py +++ b/tools/progress.py @@ -10,7 +10,7 @@ PRX = ROOT / "core" / "libs" / "prx" OPCODES = ROOT / "core" / "shader" / "recompiler" / "RdnaDecoder" / "include" / "RdnaDecoder" / "RdnaOpcode.hpp" ISA = Path(__file__).resolve().parent / "rdna_isa.txt" SOURCE = f'https://github.com/{os.environ.get("GITHUB_REPOSITORY", "boykopovar/AnyPS5")}/blob/main' -DEFINITION = re.compile(r"\bAPS5_VABI\s+(\w+)\s*\([^;{]*\)\s*(?:noexcept\s*)?\{") +DEFINITION = re.compile(r"\bAPS5_VABI\s+(\w+)\s*\([^;{]*\)\s*(?:noexcept\s*)?(?:try\s*)?\{") STUB = "NotImplemented_nid_no_patch" STUB_WRAPPER = re.compile(r"\bstatic\s+(?:\[\[noreturn\]\]\s+)?void\s+(\w+)\s*\([^;{]*\)\s*\{") FLAT_SEGMENTS = ("GLOBAL_", "SCRATCH_")