AnyPS5/core/libs/prx/libSceAgcDriver/tests/SpirvLdsAtomicLock.cpp

145 lines
6.5 KiB
C++

#include "Recompiler.hpp"
#include <spirv/unified1/spirv.hpp>
#include <array>
#include <cstdint>
#include <cstdio>
#include <exception>
#include <span>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
namespace {
using namespace ShaderRecompiler;
constexpr std::uint32_t DsAddU64 = 0xd9000000u;
constexpr std::uint32_t DsAddU32Hi = 0xd8000004u;
constexpr std::uint32_t DsIncU32Hi = 0xd80c0004u;
constexpr std::uint32_t DsOperands = 0x00000201u;
constexpr std::uint32_t DsOperand32 = 0x00000401u;
constexpr std::uint32_t FlatAtomicAddU32 = 0xdcc90000u;
constexpr std::uint32_t FlatAtomicOperands = 0x0a7d0402u;
struct Usage {
std::size_t elects = 0;
std::size_t exchanges = 0;
std::size_t releases = 0;
std::size_t adds = 0;
bool addBetweenLock = false;
};
void Require(bool condition, const std::string& message) {
if (!condition) throw std::runtime_error(message);
}
std::vector<std::uint32_t> RecompileProgram(std::span<const std::uint32_t> code) {
const std::array<std::uint32_t, 7> capabilities{spv::CapabilityShader, spv::CapabilityGroupNonUniform, spv::CapabilityGroupNonUniformBallot, spv::CapabilityGroupNonUniformShuffle, spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess};
const std::array<std::uint32_t, 4> userData{};
const std::array<std::string_view, 2> extensions{"SPV_KHR_physical_storage_buffer", "SPV_KHR_8bit_storage"};
RecompileRequest request{};
request.shader = {ShaderStage::Compute, 0x20000u, code, 0, {}};
request.context.waveSize = 32;
request.context.userDataBaseRegister = 0;
request.context.userData = userData;
request.context.compute = ShaderComputeStageInfo{{64u, 1u, 1u}, 4u, {false, false, false}, false, 1u};
request.target.vulkanVersion = 0x00403000u;
request.target.spirvVersion = 0x00010600u;
request.target.subgroupSize = 32;
request.target.bdaAbiVersion = 1;
request.target.supportedCapabilities = capabilities;
request.target.supportedExtensions = extensions;
request.target.maxWorkgroupSize = {1024u, 1024u, 64u};
request.target.maxWorkgroupInvocations = 1024;
request.target.maxWorkgroupSharedMemoryBytes = 49152;
request.layout = {0, 0, 0, 128};
request.useCache = false;
auto result = Recompile(request);
Require(!result.spirv.empty(), "the LDS atomic program did not recompile");
return std::vector<std::uint32_t>(result.spirv.Words().begin(), result.spirv.Words().end());
}
Usage Scan(std::span<const std::uint32_t> words) {
Usage usage;
bool locked = false;
for (std::size_t cursor = 5; cursor < words.size();) {
const auto count = words[cursor] >> spv::WordCountShift;
const auto op = static_cast<spv::Op>(words[cursor] & spv::OpCodeMask);
Require(count != 0 && cursor + count <= words.size(), "truncated SPIR-V instruction");
if (op == spv::OpGroupNonUniformElect) ++usage.elects;
if (op == spv::OpAtomicCompareExchange) {
++usage.exchanges;
locked = true;
}
if (op == spv::OpAtomicStore) {
++usage.releases;
locked = false;
}
if (op == spv::OpAtomicIAdd) {
++usage.adds;
if (locked) usage.addBetweenLock = true;
}
cursor += count;
}
return usage;
}
Usage Compile(std::initializer_list<std::uint32_t> body) {
std::vector<std::uint32_t> code{0x7e020280u, 0x7e0402c1u, 0x7e060280u, 0x7e080281u};
code.insert(code.end(), body);
code.insert(code.end(), {0xbf8cc07fu, 0xbf810000u});
return Scan(RecompileProgram(code));
}
void Check64BitOnlyLocks() {
const auto usage = Compile({DsAddU64, DsOperands});
Require(usage.elects == 1 && usage.exchanges == 1 && usage.releases == 1, "a ds_add_u64 is not serialised by one elected lane taking the workgroup lock");
}
void Check32BitTakesLockWith64Bit() {
const auto usage = Compile({DsAddU64, DsOperands, DsAddU32Hi, DsOperand32});
Require(usage.elects == 2 && usage.exchanges == 2 && usage.releases == 2, "ds_add_u32 in a program with ds_add_u64 does not take the workgroup lock: elect " + std::to_string(usage.elects) + ", lock acquire " + std::to_string(usage.exchanges) + ", lock release " + std::to_string(usage.releases) + ", expected 2 each");
Require(usage.adds == 1 && usage.addBetweenLock, "the 32-bit OpAtomicIAdd is not between the lock acquire and the lock release");
}
void CheckOtherUpdatePathTakesLock() {
const auto usage = Compile({DsAddU64, DsOperands, DsIncU32Hi, DsOperand32});
Require(usage.elects == 2 && usage.exchanges >= 2 && usage.releases == 2, "ds_inc_u32 in a program with ds_add_u64 does not take the workgroup lock: elect " + std::to_string(usage.elects) + ", lock release " + std::to_string(usage.releases) + ", expected 2 each");
}
void CheckFlatAtomicTakesLockWith64Bit() {
const auto usage = Compile({DsAddU64, DsOperands, FlatAtomicAddU32, FlatAtomicOperands});
Require(usage.elects == 2, "a 32-bit flat atomic into the LDS aperture in a program with ds_add_u64 does not take the workgroup lock: elect " + std::to_string(usage.elects) + ", expected 2");
}
void CheckFlatAtomicOnlyIsUnchanged() {
const auto usage = Compile({FlatAtomicAddU32, FlatAtomicOperands});
Require(usage.elects == 0, "a program with only a 32-bit flat atomic got a workgroup lock: elect " + std::to_string(usage.elects));
}
void Check32BitOnlyIsUnchanged() {
const auto add = Compile({DsAddU32Hi, DsOperand32});
Require(add.elects == 0 && add.exchanges == 0 && add.releases == 0 && add.adds == 1, "a program with only ds_add_u32 got a workgroup lock: elect " + std::to_string(add.elects) + ", lock acquire " + std::to_string(add.exchanges) + ", lock release " + std::to_string(add.releases) + ", native add " + std::to_string(add.adds));
const auto inc = Compile({DsIncU32Hi, DsOperand32});
Require(inc.elects == 0 && inc.releases == 0, "a program with only ds_inc_u32 got a workgroup lock: elect " + std::to_string(inc.elects) + ", lock release " + std::to_string(inc.releases));
}
}
int main() {
try {
Check64BitOnlyLocks();
Check32BitTakesLockWith64Bit();
CheckOtherUpdatePathTakesLock();
CheckFlatAtomicTakesLockWith64Bit();
CheckFlatAtomicOnlyIsUnchanged();
Check32BitOnlyIsUnchanged();
} catch (const std::exception& error) {
std::fprintf(stderr, "SpirvLdsAtomicLock: %s\n", error.what());
return 1;
}
std::puts("SpirvLdsAtomicLock: 32-bit LDS atomics take the 64-bit workgroup lock only in programs with a 64-bit LDS atomic");
return 0;
}