diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/LdsSrc2.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LdsSrc2.cpp new file mode 100644 index 000000000..e7dd4fea7 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LdsSrc2.cpp @@ -0,0 +1,152 @@ +#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 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, 0xe0381000, 0x80000201, 0x340c0082, 0xbf8c3f70, 0x4a0e0cff, 0x00001000, 0x4a100c80, + 0xd8340000, 0x00000207, 0xd8340000, 0x00000308, 0xda007c00, 0x00000007, 0xd8d80000, 0x0a000007, + 0x4a0e0cff, 0x00001080, 0x4a100cff, 0x00002080, 0xd8340000, 0x00000207, 0xd8340000, 0x00000308, + 0xda040400, 0x00000007, 0xd8d80000, 0x0b000007, 0x4a0e0cff, 0x00001100, 0x4a100cff, 0x00000100, + 0xd8340000, 0x00000207, 0xd8340000, 0x00000308, 0xda087c00, 0x00000007, 0xd8d80000, 0x0c000007, + 0x4a0e0cff, 0x00001180, 0x4a100cff, 0x00002180, 0xd8340000, 0x00000207, 0xd8340000, 0x00000308, + 0xda0c0400, 0x00000007, 0xd8d80000, 0x0d000007, 0x4a0e0cff, 0x00001200, 0x4a100cff, 0x00000200, + 0xd8340000, 0x00000207, 0xd8340000, 0x00000308, 0xda107c00, 0x00000007, 0xd8d80000, 0x0e000007, + 0x4a0e0cff, 0x00001280, 0x4a100cff, 0x00002280, 0xd8340000, 0x00000207, 0xd8340000, 0x00000308, + 0xda140400, 0x00000007, 0xd8d80000, 0x0f000007, 0x4a0e0cff, 0x00001300, 0x4a100cff, 0x00000300, + 0xd8340000, 0x00000207, 0xd8340000, 0x00000308, 0xda187c00, 0x00000007, 0xd8d80000, 0x10000007, + 0x4a0e0cff, 0x00001380, 0x4a100cff, 0x00002380, 0xd8340000, 0x00000207, 0xd8340000, 0x00000308, + 0xda1c0400, 0x00000007, 0xd8d80000, 0x11000007, 0x4a0e0cff, 0x00001400, 0x4a100cff, 0x00000400, + 0xd8340000, 0x00000207, 0xd8340000, 0x00000308, 0xda207c00, 0x00000007, 0xd8d80000, 0x12000007, + 0x4a0e0cff, 0x00001480, 0x4a100cff, 0x00002480, 0xd8340000, 0x00000207, 0xd8340000, 0x00000308, + 0xda240400, 0x00000007, 0xd8d80000, 0x13000007, 0x4a0e0cff, 0x00001500, 0x4a100cff, 0x00000500, + 0xd8340000, 0x00000207, 0xd8340000, 0x00000308, 0xda287c00, 0x00000007, 0xd8d80000, 0x14000007, + 0x4a0e0cff, 0x00001580, 0x4a100cff, 0x00002580, 0xd8340000, 0x00000207, 0xd8340000, 0x00000308, + 0xda2c0400, 0x00000007, 0xd8d80000, 0x15000007, 0x4a0e0cff, 0x00001600, 0x4a100cff, 0x00000600, + 0xd8340000, 0x00000207, 0xd8340000, 0x00000308, 0xda347c00, 0x00000007, 0xd8d80000, 0x16000007, + 0x4a0e0cff, 0x00001680, 0x4a100cff, 0x00002680, 0xd8340000, 0x00000407, 0xd8340000, 0x00000508, + 0xda480400, 0x00000007, 0xd8d80000, 0x17000007, 0x4a0e0cff, 0x00001700, 0x4a100cff, 0x00000700, + 0xd8340000, 0x00000407, 0xd8340000, 0x00000508, 0xda4c7c00, 0x00000007, 0xd8d80000, 0x18000007, + 0x4a0e0cff, 0x00001780, 0x4a100cff, 0x00002780, 0xd8340000, 0x00000407, 0xd8340000, 0x00000508, + 0xda540400, 0x00000007, 0xd8d80000, 0x19000007, 0x34020086, 0xbf8cc07f, 0xe0781000, 0x80010a01, + 0xe0781010, 0x80010e01, 0xe0781020, 0x80011201, 0xe0781030, 0x80011601, 0xbf810000, +}; + +alignas(256) constexpr std::array AddressOffsetCode{0xda008000, 0x00000000, 0xbf810000}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + const std::uint64_t seed = (tid + 1u) * 0x9e3779b97f4a7c15ull; + const auto mixed = seed * 0xbf58476d1ce4e5b9ull; + words[0] = tid % 4u == 0u ? tid % 7u : static_cast(seed); + words[1] = tid % 4u == 0u ? (tid * 3u) % 7u : tid % 4u == 1u ? words[0] : static_cast(mixed >> 32u); + words[2] = std::bit_cast(static_cast(static_cast(tid % 9u) - 4) * 1.75f + 0.5f); + words[3] = std::bit_cast(static_cast(static_cast((tid * 5u) % 11u) - 5) * 0.625f + 0.25f); +} + +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; +} + +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}, 10240u, {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 auto result = Compile(device, Code); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(Code.data())); + device.WaitIdle(); +} + +void CheckLanes() { + constexpr std::array names{ + "ds_add_src2_u32", "ds_sub_src2_u32", "ds_rsub_src2_u32", "ds_inc_src2_u32", "ds_dec_src2_u32", "ds_min_src2_i32", "ds_max_src2_i32", "ds_min_src2_u32", + "ds_max_src2_u32", "ds_and_src2_b32", "ds_or_src2_b32", "ds_xor_src2_b32", "ds_write_src2_b32", "ds_min_src2_f32", "ds_max_src2_f32", "ds_add_src2_f32", + }; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t a = in[0]; + const std::uint32_t b = in[1]; + const auto sa = static_cast(a); + const auto sb = static_cast(b); + const float fa = std::bit_cast(in[2]); + const float fb = std::bit_cast(in[3]); + const std::array expected{ + a + b, a - b, b - a, a >= b ? 0u : a + 1u, (a == 0u || a > b) ? b : a - 1u, + static_cast(std::min(sa, sb)), static_cast(std::max(sa, sb)), std::min(a, b), std::max(a, b), + a & b, a | b, a ^ b, b, + std::bit_cast(std::min(fa, fb)), std::bit_cast(std::max(fa, fb)), std::bit_cast(fa + fb), + }; + for (std::uint32_t op = 0; op < Results; ++op) { + const std::uint32_t actual = Output[tid * Results + op]; + Require(actual == expected[op], "lds src2: lane " + std::to_string(tid) + " " + names[op] + " is " + Hex(actual) + ", expected " + Hex(expected[op])); + } + } +} + +void CheckAddressOffsetRejected(AgcDriver::VulkanDevice& device) { + try { + (void)Compile(device, AddressOffsetCode); + } catch (const std::exception&) { + return; + } + Require(false, "lds src2: an offset taken from the address was accepted"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + CheckLanes(); + CheckAddressOffsetRejected(*device); + std::puts("lds src2 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 3be783c0c..447cf0593 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -938,6 +938,22 @@ enum class RdnaOpcode : std::uint16_t { DsCmpstRtnF64, DsMinRtnF64, DsMaxRtnF64, + DsAddSrc2U32, + DsSubSrc2U32, + DsRsubSrc2U32, + DsIncSrc2U32, + DsDecSrc2U32, + DsMinSrc2I32, + DsMaxSrc2I32, + DsMinSrc2U32, + DsMaxSrc2U32, + DsAndSrc2B32, + DsOrSrc2B32, + DsXorSrc2B32, + DsWriteSrc2B32, + DsMinSrc2F32, + DsMaxSrc2F32, + DsAddSrc2F32, DsNop, DsReadU8D16, DsReadU8D16Hi, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp index 5bc29e580..8f8c5c142 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp @@ -283,6 +283,22 @@ constexpr MemoryOpcodeInfo dsOpcodes[] = { {0x71u, RdnaOpcode::DsCmpstRtnF64, 2, 32, false, false, false}, {0x72u, RdnaOpcode::DsMinRtnF64, 2, 32, false, false, false}, {0x73u, RdnaOpcode::DsMaxRtnF64, 2, 32, false, false, false}, + {0x80u, RdnaOpcode::DsAddSrc2U32, 1, 32, false, false, false}, + {0x81u, RdnaOpcode::DsSubSrc2U32, 1, 32, false, false, false}, + {0x82u, RdnaOpcode::DsRsubSrc2U32, 1, 32, false, false, false}, + {0x83u, RdnaOpcode::DsIncSrc2U32, 1, 32, false, false, false}, + {0x84u, RdnaOpcode::DsDecSrc2U32, 1, 32, false, false, false}, + {0x85u, RdnaOpcode::DsMinSrc2I32, 1, 32, false, false, false}, + {0x86u, RdnaOpcode::DsMaxSrc2I32, 1, 32, false, false, false}, + {0x87u, RdnaOpcode::DsMinSrc2U32, 1, 32, false, false, false}, + {0x88u, RdnaOpcode::DsMaxSrc2U32, 1, 32, false, false, false}, + {0x89u, RdnaOpcode::DsAndSrc2B32, 1, 32, false, false, false}, + {0x8au, RdnaOpcode::DsOrSrc2B32, 1, 32, false, false, false}, + {0x8bu, RdnaOpcode::DsXorSrc2B32, 1, 32, false, false, false}, + {0x8du, RdnaOpcode::DsWriteSrc2B32, 1, 32, false, false, false}, + {0x92u, RdnaOpcode::DsMinSrc2F32, 1, 32, false, false, false}, + {0x93u, RdnaOpcode::DsMaxSrc2F32, 1, 32, false, false, false}, + {0x95u, RdnaOpcode::DsAddSrc2F32, 1, 32, false, false, false}, {0x01u, RdnaOpcode::DsSubU32, 1, 32, false, false, false}, {0x05u, RdnaOpcode::DsMinI32, 1, 32, false, false, false}, {0x06u, RdnaOpcode::DsMaxI32, 1, 32, false, false, false}, @@ -554,6 +570,29 @@ bool isDsAtomicOpcode(RdnaOpcode opcode) { } } +bool isDsSrc2Opcode(RdnaOpcode opcode) { + switch (opcode) { + case RdnaOpcode::DsAddSrc2U32: + case RdnaOpcode::DsSubSrc2U32: + case RdnaOpcode::DsRsubSrc2U32: + case RdnaOpcode::DsIncSrc2U32: + case RdnaOpcode::DsDecSrc2U32: + case RdnaOpcode::DsMinSrc2I32: + case RdnaOpcode::DsMaxSrc2I32: + case RdnaOpcode::DsMinSrc2U32: + case RdnaOpcode::DsMaxSrc2U32: + case RdnaOpcode::DsAndSrc2B32: + case RdnaOpcode::DsOrSrc2B32: + case RdnaOpcode::DsXorSrc2B32: + case RdnaOpcode::DsWriteSrc2B32: + case RdnaOpcode::DsMinSrc2F32: + case RdnaOpcode::DsMaxSrc2F32: + case RdnaOpcode::DsAddSrc2F32: + return true; + default: return false; + } +} + std::uint32_t dsSourceCount(RdnaOpcode opcode) { switch (opcode) { case RdnaOpcode::DsWrxchg2RtnB32: @@ -975,6 +1014,12 @@ RdnaInstruction DecodeRdnaDs(std::uint32_t programCounter, std::span::_Getcat](../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) builds the facet for the C locale only and throws for any other locale name. The console's `libc.prx` stores pointers to its collation state tables at offsets 0x10 and 0x18 of the facet; ours leaves them null, as only the facet's own functions read them. `do_transform` throws for input with embedded null characters, which the console drops depending on the output buffer size, and for results of 4095 characters or more, whose string storage uses the guest's over-aligned large allocation.