diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalStoreAddtid.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalStoreAddtid.cpp new file mode 100644 index 000000000..fcf2f94d3 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalStoreAddtid.cpp @@ -0,0 +1,179 @@ +#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::size_t BlockBytes = 65536; +constexpr std::uint32_t Base = 2048; +constexpr std::uint32_t Fill = 0x5a5a5a5au; + +alignas(256) constexpr std::array AddtidCode{ + 0x800aff08, 0x00000800, 0x820b8009, 0x160800ff, 0x00000101, 0x4a0808ff, 0x00005100, 0x36500083, + 0x3a520880, 0xbefc0380, 0xdc5c8000, 0x000a2900, 0x3a5208ff, 0x11110000, 0xbefc03c0, 0xdc5c8f00, + 0x000a2900, 0x3a5208ff, 0x22220000, 0xbefc03ff, 0x0000ffff, 0xdc5c8c00, 0x000a2900, 0x3a5208ff, + 0x33330000, 0xbefc0380, 0x7daa5082, 0xdc5c8200, 0x000a2900, 0xbefe04c1, 0x3a5208ff, 0x44440000, + 0xbefc0380, 0xdc5c8800, 0x000a2900, 0x3a5208ff, 0x55550000, 0xbefc0380, 0xdc5c87fc, 0x000a2900, + 0xbf810000, +}; + +struct Store { + std::int32_t offset; + std::uint32_t pattern; + bool masked; +}; + +constexpr std::array Stores{{ + {0, 0x00000000u, false}, + {-256, 0x11110000u, false}, + {-1024, 0x22220000u, false}, + {512, 0x33330000u, true}, + {-2048, 0x44440000u, false}, + {2044, 0x55550000u, false}, +}}; + +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 store addtid: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, true); + } + + ~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::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +std::vector Expected(std::uint32_t lanes) { + std::vector memory(BlockBytes / 4u, Fill); + for (const auto& store : Stores) { + for (std::uint32_t lane = 0; lane < lanes; ++lane) { + if (store.masked && (lane & 3u) == 2u) continue; + memory[(Base + store.offset + lane * 4u) / 4u] = (0x5100u + lane * 0x101u) ^ store.pattern; + } + } + return memory; +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target, const std::string& name) { + std::vector memory(BlockBytes / 4u, Fill); + std::memcpy(guest.Data(), memory.data(), BlockBytes); + const auto address = static_cast(reinterpret_cast(guest.Data())); + std::vector userData(10, 0u); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::span code(AddtidCode); + const std::array regions{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{waveSize, 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, regions}, + 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(); + std::memcpy(memory.data(), guest.Data(), BlockBytes); + const auto expected = Expected(waveSize); + for (std::size_t dword = 0; dword < memory.size(); ++dword) { + Require(memory[dword] == expected[dword], "global store addtid: " + name + " byte " + std::to_string(dword * 4u) + " is " + Hex(memory[dword]) + ", expected " + Hex(expected[dword])); + } +} + +void CheckRejected(const AgcDriver::VulkanDevice& device, std::span code, const std::string& reason) { + const std::array regions{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{32, 1, 1}, 0, {false, false, false}, false, 1}; + const std::vector userData(10, 0u); + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, regions}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + std::string failure; + try { + static_cast(ShaderRecompiler::Recompile(request)); + } catch (const std::exception& error) { + failure = error.what(); + } + Require(failure.find(reason) != std::string::npos, "global store addtid: expected '" + reason + "', got '" + failure + "'"); +} + +void CheckRejections(const AgcDriver::VulkanDevice& device) { + alignas(256) static constexpr std::array flatSegment{0xdc5c0000, 0x000a2900, 0xbf810000}; + alignas(256) static constexpr std::array scratchSegment{0xdc5c4000, 0x000a2900, 0xbf810000}; + alignas(256) static constexpr std::array noScalarBase{0xdc5c8000, 0x007d2900, 0xbf810000}; + CheckRejected(device, flatSegment, "global_store_dword_addtid is available only in the global segment"); + CheckRejected(device, scratchSegment, "global_store_dword_addtid is available only in the global segment"); + CheckRejected(device, noScalarBase, "global_store_dword_addtid supports only an SGPR pair as base address"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + CheckRejections(*device); + if (device->Target().subgroupSize < 32u) { + std::printf("skipped, the device's subgroups are narrower than a wave (%u lanes)\n", device->Target().subgroupSize); + return VulkanTestSkipped; + } + GuestBlock guest; + Run(*device, guest, 32, device->Target(), "wave32"); + Run(*device, guest, 64, device->Target(), "wave64"); + Run(*device, guest, 64, device->ComputeTarget(32), "wave64 split"); + std::puts("global store addtid tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp index f132cbeed..14cc7e3a1 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, + GlobalStoreDwordAddtid, DsAddU32, DsAddRtnU32, DsSubU32, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp index e240a85e8..53e6a4605 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace ShaderRecompiler { @@ -170,6 +171,7 @@ constexpr MemoryOpcodeInfo flatOpcodes[] = { {0x0eu, RdnaOpcode::FlatLoadDwordx4, 4, 32, false, false, false}, {0x0fu, RdnaOpcode::FlatLoadDwordx3, 3, 32, false, false, false}, {0x16u, RdnaOpcode::GlobalLoadDwordAddtid, 1, 32, false, false, false}, + {0x17u, RdnaOpcode::GlobalStoreDwordAddtid, 1, 32, false, false, false}, {0x18u, RdnaOpcode::FlatStoreByte, 1, 8, false, false, false}, {0x1au, RdnaOpcode::FlatStoreShort, 1, 16, false, false, false}, {0x1cu, RdnaOpcode::FlatStoreDword, 1, 32, false, false, false}, @@ -560,7 +562,8 @@ bool isFlatStoreOpcode(RdnaOpcode opcode) { case RdnaOpcode::FlatStoreDword: case RdnaOpcode::FlatStoreDwordx2: case RdnaOpcode::FlatStoreDwordx3: - case RdnaOpcode::FlatStoreDwordx4: return true; + case RdnaOpcode::FlatStoreDwordx4: + case RdnaOpcode::GlobalStoreDwordAddtid: return true; default: return false; } } @@ -831,11 +834,12 @@ RdnaInstruction DecodeRdnaFlat(std::uint32_t programCounter, std::span