Merge pull request #569 from adriadam10/feat/global-store-addtid

feat(shader): implement global_store_dword_addtid
This commit is contained in:
Dean 2026-10-05 20:27:36 +13:00 committed by GitHub
commit 56acdef2f5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 198 additions and 7 deletions

View File

@ -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 <windows.h>
#endif
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <span>
#include <string>
#include <vector>
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<std::uint32_t, 41> 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<Store, 6> 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<std::uint8_t*>(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
#else
block = static_cast<std::uint8_t*>(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<std::uint32_t> Expected(std::uint32_t lanes) {
std::vector<std::uint32_t> 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<std::uint32_t> memory(BlockBytes / 4u, Fill);
std::memcpy(guest.Data(), memory.data(), BlockBytes);
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(guest.Data()));
std::vector<std::uint32_t> userData(10, 0u);
userData[8] = static_cast<std::uint32_t>(address);
userData[9] = static_cast<std::uint32_t>(address >> 32u);
const std::span<const std::uint32_t> code(AddtidCode);
const std::array<ShaderRecompiler::MemoryRegion, 1> regions{{{reinterpret_cast<std::uintptr_t>(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<std::uintptr_t>(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<std::uintptr_t>(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<const std::uint32_t> code, const std::string& reason) {
const std::array<ShaderRecompiler::MemoryRegion, 1> regions{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
const ShaderRecompiler::ShaderComputeStageInfo compute{{32, 1, 1}, 0, {false, false, false}, false, 1};
const std::vector<std::uint32_t> userData(10, 0u);
ShaderRecompiler::RecompileRequest request{
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(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<void>(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<std::uint32_t, 3> flatSegment{0xdc5c0000, 0x000a2900, 0xbf810000};
alignas(256) static constexpr std::array<std::uint32_t, 3> scratchSegment{0xdc5c4000, 0x000a2900, 0xbf810000};
alignas(256) static constexpr std::array<std::uint32_t, 3> 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;
}
}

View File

@ -832,6 +832,7 @@ enum class RdnaOpcode : std::uint16_t {
FlatAtomicFmaxX2,
FlatAtomicIncX2,
FlatAtomicDecX2,
GlobalStoreDwordAddtid,
DsAddU32,
DsAddRtnU32,
DsSubU32,

View File

@ -3,6 +3,7 @@
#include <bit>
#include <limits>
#include <stdexcept>
#include <string>
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<const std
setRawWords(instruction, code, wordIndex, 2u);
instruction.destination = d16Half(vectorRegister(isFlatStoreOpcode(instruction.op) ? data : vdst), instruction.op);
if (instruction.op == RdnaOpcode::GlobalLoadDwordAddtid) {
if (instruction.op == RdnaOpcode::GlobalLoadDwordAddtid || instruction.op == RdnaOpcode::GlobalStoreDwordAddtid) {
const std::string name = instruction.op == RdnaOpcode::GlobalLoadDwordAddtid ? "global_load_dword_addtid" : "global_store_dword_addtid";
if (seg != 2u) {
throw std::runtime_error("global_load_dword_addtid is available only in the global segment");
throw std::runtime_error(name + " is available only in the global segment");
}
instruction.source0 = scalarDescriptorBase(saddr, 2u, "global_load_dword_addtid supports only an SGPR pair as base address");
instruction.source0 = scalarDescriptorBase(saddr, 2u, (name + " supports only an SGPR pair as base address").c_str());
instruction.sourceCount = 1;
return instruction;
}

View File

@ -114,7 +114,7 @@ private:
bool dsAtomic64(const RdnaInstruction& inst, IrOpcode opcode, bool returnsValue);
bool dsAppendConsume(const RdnaInstruction& inst, IrOpcode opcode);
bool dsAddtid(const RdnaInstruction& inst, bool write);
bool globalLoadAddtid(const RdnaInstruction& inst);
bool globalAddtid(const RdnaInstruction& inst, bool write);
bool dsSwizzleB32(const RdnaInstruction& inst);
bool dsBpermuteB32(const RdnaInstruction& inst);
bool dsPermuteB32(const RdnaInstruction& inst);

View File

@ -97,7 +97,7 @@ bool TranslationContext::flatLoad(const RdnaInstruction& inst) {
return true;
}
bool TranslationContext::globalLoadAddtid(const RdnaInstruction& inst) {
bool TranslationContext::globalAddtid(const RdnaInstruction& inst, bool write) {
MemoryInfo memory = flatMemoryInfoFromInstruction(inst);
memory.addressIsFull = false;
const IrU32 baseLow = readU32(inst.source0);
@ -106,6 +106,11 @@ bool TranslationContext::globalLoadAddtid(const RdnaInstruction& inst) {
IrValue& lane = ir.Emit(IrOpcode::LaneId, IrOpcodeType(IrOpcode::LaneId), {});
IrValue& laneOffset = ir.ShiftLeftLogical(lane, ir.Constant(2u));
IrValue& active = ir.GetExec();
if (write) {
const IrU32 data = readU32(inst.destination);
(void)ir.Emit(IrOpcode::StoreAddressU32, IrType::Void, {resource, &laneOffset, &ir.Constant(0u), &data.Value(), &active}, addMemoryInfo(memory, inst.programCounter));
return true;
}
IrValue& loaded = ir.Emit(IrOpcode::LoadAddressU32, IrOpcodeType(IrOpcode::LoadAddressU32), {resource, &laneOffset, &ir.Constant(0u), &active}, addMemoryInfo(memory, inst.programCounter));
writeOperand(inst.destination, &loaded);
return true;

View File

@ -197,7 +197,9 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) {
case RdnaOpcode::FlatLoadDwordx4:
return flatLoad(inst);
case RdnaOpcode::GlobalLoadDwordAddtid:
return globalLoadAddtid(inst);
return globalAddtid(inst, false);
case RdnaOpcode::GlobalStoreDwordAddtid:
return globalAddtid(inst, true);
case RdnaOpcode::FlatStoreByte:
case RdnaOpcode::FlatStoreShort:
case RdnaOpcode::FlatStoreByteD16Hi: