From d044384a89fe7433207db1b02a4e4b2a30489ffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 00:09:14 +0200 Subject: [PATCH] feat(shader): implement global_store_dword_addtid Decode FLAT opcode 0x17 in the global segment and store the data VGPR of each active lane at the SGPR pair base plus the signed offset plus four times the lane id, sharing the address path of global_load_dword_addtid. Co-Authored-By: Claude --- .../tests/execution/GlobalStoreAddtid.cpp | 179 ++++++++++++++++++ .../include/RdnaDecoder/RdnaOpcode.hpp | 1 + .../RdnaDecoder/src/RdnaMemoryOpDecoder.cpp | 12 +- .../Translation/TranslationContext.hpp | 2 +- .../MemoryInstructions/FlatInstructions.cpp | 7 +- .../MemoryInstructionDispatch.cpp | 4 +- 6 files changed, 198 insertions(+), 7 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/GlobalStoreAddtid.cpp 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