From 3d3e68797173b809a8bbaad359d2746a05cc6e2f Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Sun, 4 Oct 2026 19:50:47 +1300 Subject: [PATCH 001/164] fix(agc): address texture views that start past the surface's last mip PPSA21564 ends each of its two 1920x1080 RGBA16F bloom downsample chains (MAX_MIP 5) with a compute pass that stores level 6 through a view of levels 6..6, every frame from the PS logo on. The descriptor decode threw for such a view. GFX10 uses MAX_MIP only to place the levels: a level's address comes from the levels below it, and a level in the mip tail is a fixed slot of the tail block counted from the first tail level. A level past MAX_MIP is addressed the same way, so when the chain extended to the view's last level keeps every allocated level, the tail and the surface size unchanged, the view reaches that slot inside the surface's allocation. addrlib (GFX10, Navi10 with 16 pipes) computes the same 0x1640000-byte layout for 6, 7, 9 and 11 levels of this surface, with level 6 in the tail at (0, 32). The view then decodes with the extended chain over the same guest bytes. Views whose extra levels would move the allocated ones, such as linear chains or chains without a tail, still throw. --- .../Graphics/include/TextureTiling.hpp | 1 + .../Graphics/src/GuestTextureResource.cpp | 8 +- .../Graphics/src/TextureTiling.cpp | 19 +++ .../tests/GuestTextureResource.cpp | 52 ++++++++ .../tests/execution/ViewPastLastMip.cpp | 114 ++++++++++++++++++ 5 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/ViewPastLastMip.cpp diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp index aad8c0ddc..dbeeb0a91 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp @@ -65,6 +65,7 @@ struct SurfaceGeometry { std::int32_t CopyDepth(std::uint32_t layer) const { return imageDepth > 1 ? static_cast(layer) : 0; } }; SurfaceGeometry DescribeSurface(const GuestTextureResource& descriptor); +bool LevelsFitAllocation(const GuestTextureResource& surface, std::uint32_t levels); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp index fc8dfba9a..443517bba 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp @@ -1,6 +1,7 @@ #include #include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" #include "prx/libSceAgcDriver/Graphics/include/Context.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" #include #include #include @@ -129,10 +130,9 @@ GuestTextureResource DecodeTextureResource(std::span words) } catch (const std::exception& error) { throw std::runtime_error(std::string(error.what()) + describe()); } - Require(baseLevel <= maxMip, "guest texture descriptor starts past the surface's last mip level" + describe()); // Views may name levels past MAX_MIP (a 512x512 view through 1x1 over a 9-level surface); the // hardware never addresses them, so the view ends at the surface's last level. - lastLevel = std::min(lastLevel, maxMip); + if (baseLevel <= maxMip) lastLevel = std::min(lastLevel, maxMip); // XOR swizzles fold a pipe/bank XOR into the low address bits; only unmodified 64 KiB bases are modeled. Require(XorSwizzleMode(tileMode) == 0 || (baseAddress & 0xffffu) == 0, "guest texture descriptor combines an XOR swizzle with a pipe/bank XOR base which is not implemented"); @@ -176,6 +176,10 @@ GuestTextureResource DecodeTextureResource(std::span words) result.dccAddress = metaCompress ? metaAddr << 8u : 0u; result.dccAlphaOnMsb = dccAlphaPos; result.minLod = minLod; + if (baseLevel > maxMip) { + Require(LevelsFitAllocation(result, lastLevel + 1u), "guest texture descriptor starts past the surface's last mip level at levels that would move the surface's own" + describe()); + result.mipCount = lastLevel + 1u; + } return result; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp index 656239486..c5daed64e 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp @@ -325,6 +325,25 @@ SurfaceGeometry DescribeSurface(const GuestTextureResource& descriptor) { return geometry; } +bool LevelsFitAllocation(const GuestTextureResource& surface, std::uint32_t levels) { + Require(levels >= surface.mipCount && levels <= 16u, "a view's mip chain must cover the surface's levels and at most 16"); + auto view = surface; + view.mipCount = levels; + const auto allocated = DescribeSurface(surface); + const auto extended = DescribeSurface(view); + if (allocated.guestBytes != extended.guestBytes || allocated.layerBytes != extended.layerBytes || allocated.layers != extended.layers) return false; + for (std::uint32_t level = 0; level < surface.mipCount; ++level) { + const auto& before = allocated.mips[level]; + const auto& after = extended.mips[level]; + if (before.tiledOffset != after.tiledOffset || before.tiledSize != after.tiledSize || before.blocksPerRow != after.blocksPerRow || before.tail != after.tail || before.tailX != after.tailX || before.tailY != after.tailY) return false; + } + for (auto level = surface.mipCount; level < levels; ++level) { + const auto& mip = extended.mips[level]; + if (mip.tiledOffset + mip.tiledSize > extended.layerBytes) return false; + } + return true; +} + std::vector ComputeElementMipLayout(TextureTileMode tileMode, std::uint32_t bytesPerElement, std::uint32_t width, std::uint32_t height, std::uint32_t mipCount) { Require(width != 0 && height != 0 && mipCount != 0 && mipCount <= 16u, "invalid surface mip chain"); if (tileMode == TextureTileMode::kLinear) return ComputeLinearMipLayout(bytesPerElement, 1u, 1u, width, height, mipCount); diff --git a/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp b/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp index 092a548c0..87e25e28a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp @@ -1,5 +1,6 @@ #include "GraphicsTests.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" #include #include #include @@ -269,6 +270,57 @@ void RunGuestTextureResourceTests() { const auto partial = DecodeTextureResource(pack(partialMips)); Require(partial.lastLevel == 1 && partial.mipCount == 3, "a view over part of the mip chain decoded wrongly"); + Fields pastLast = base; + pastLast.base40 = 0x55ea000ull; + pastLast.format = 71; + pastLast.width = 1920; + pastLast.height = 1080; + pastLast.tileModeRaw = 0x1b; + pastLast.maxMip = 5; + pastLast.baseLevel = 6; + pastLast.lastLevel = 6; + const auto tailView = DecodeTextureResource(pack(pastLast)); + Require(tailView.baseLevel == 6 && tailView.lastLevel == 6 && tailView.mipCount == 7, "a view one level past the last mip must address that level of the chain"); + auto allocated = tailView; + allocated.mipCount = 6; + const auto allocatedSurface = DescribeSurface(allocated); + const auto viewSurface = DescribeSurface(tailView); + Require(allocatedSurface.guestBytes == 0x1640000u && viewSurface.guestBytes == 0x1640000u, "a 1920x1080 64 bpp SW_64KB_R_X chain must take addrlib's 0x1640000 bytes with 6 and with 7 levels"); + constexpr std::array addrlibOffsets{0x650000u, 0x1d0000u, 0x90000u, 0x30000u, 0x10000u, 0u, 0u}; + for (std::uint32_t level = 0; level < 7; ++level) { + Require(viewSurface.mips[level].tiledOffset == addrlibOffsets[level] && viewSurface.mips[level].tail == (level >= 5), "the 7-level chain must place each level at its addrlib offset"); + if (level < 6) Require(allocatedSurface.mips[level].tiledOffset == addrlibOffsets[level] && allocatedSurface.mips[level].tail == viewSurface.mips[level].tail && allocatedSurface.mips[level].tailX == viewSurface.mips[level].tailX && allocatedSurface.mips[level].tailY == viewSurface.mips[level].tailY, "the level past the last mip must not move the allocated levels"); + } + Require(viewSurface.mips[5].tailX == 64 && viewSurface.mips[5].tailY == 0, "the last allocated level must sit in its addrlib tail slot"); + Require(viewSurface.mips[6].tailX == 0 && viewSurface.mips[6].tailY == 32, "the level past the last mip must sit in its addrlib tail slot"); + pastLast.lastLevel = 8; + const auto deeperView = DecodeTextureResource(pack(pastLast)); + Require(deeperView.mipCount == 9, "a view past the last mip must cover every level it names"); + const auto deeperSurface = DescribeSurface(deeperView); + Require(deeperSurface.guestBytes == 0x1640000u && deeperSurface.mips[7].tail && deeperSurface.mips[7].tailX == 32 && deeperSurface.mips[7].tailY == 0 && deeperSurface.mips[8].tail && deeperSurface.mips[8].tailX == 0 && deeperSurface.mips[8].tailY == 16, "levels 7 and 8 must sit in their addrlib tail slots"); + + Fields pastLinear = base; + pastLinear.maxMip = 1; + pastLinear.baseLevel = 2; + pastLinear.lastLevel = 2; + rejectFields(pastLinear, "would move the surface's own"); + auto linearChain = partial; + linearChain.mipCount = 2; + const auto linearAllocated = DescribeSurface(linearChain).guestBytes; + linearChain.mipCount = 3; + Require(linearAllocated == 0x1800u && DescribeSurface(linearChain).guestBytes == 0x1c00u, "a third level must grow a 16x16 32 bpp linear chain from addrlib's 0x1800 to 0x1c00 bytes"); + + Fields pastUntailed = pastLast; + pastUntailed.maxMip = 0; + pastUntailed.baseLevel = 1; + pastUntailed.lastLevel = 1; + rejectFields(pastUntailed, "would move the surface's own"); + auto untailedChain = tailView; + untailedChain.mipCount = 1; + const auto untailedAllocated = DescribeSurface(untailedChain).guestBytes; + untailedChain.mipCount = 2; + Require(untailedAllocated == 0xff0000u && DescribeSurface(untailedChain).guestBytes == 0x1470000u, "a second level must grow a 1920x1080 64 bpp SW_64KB_R_X surface from addrlib's 0xff0000 to 0x1470000 bytes"); + std::array shortWords{}; reject([&] { DecodeTextureResource(shortWords); }, "8 dwords"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ViewPastLastMip.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ViewPastLastMip.cpp new file mode 100644 index 000000000..c2b535c5d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ViewPastLastMip.cpp @@ -0,0 +1,114 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 8; +constexpr std::uint32_t Side = 256; +constexpr std::uint32_t MaxMip = 2; +constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t TileR64KBX = 0x1b; +constexpr std::uint32_t Type2D = 9; +constexpr std::size_t SurfaceBytes = 0x60000; +constexpr std::size_t TailBlockBytes = 0x10000; +constexpr std::uint32_t StoredPast = 0x12345678u; +constexpr std::uint32_t StoredLast = 0x9abcdef0u; +constexpr std::uint32_t Untouched = 0xdeadbeefu; +constexpr std::array PastOffsets{0x4800, 0x4804, 0x4808, 0x480c, 0x4880, 0x4884, 0x4888, 0x488c}; +constexpr std::array LastOffsets{0x8400, 0x8404, 0x8408, 0x840c, 0x8480, 0x8484, 0x8488, 0x848c}; + +alignas(256) constexpr std::array StorePastCode{ + 0x7e040300, 0x7e060280, 0x7e0202ff, StoredPast, 0xf0200108, 0x00020102, 0xbf810000, +}; + +alignas(256) constexpr std::array StoreLastCode{ + 0x7e040300, 0x7e060280, 0x7e0202ff, StoredLast, 0xf0200108, 0x00020102, 0xbf810000, +}; + +std::array TextureDescriptor(const std::uint32_t* texels, std::uint32_t level) { + const auto address = static_cast(reinterpret_cast(texels)); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((Side - 1u) & 3u) << 30u), + ((Side - 1u) >> 2u) | ((Side - 1u) << 14u), + 0xfacu | (level << 12u) | (level << 16u) | (TileR64KBX << 20u) | (Type2D << 28u), + 0u, + MaxMip << 4u, + 0u, + 0u, + }; +} + +void Store(AgcDriver::VulkanDevice& device, const std::uint32_t* texels, std::uint32_t level, std::span code) { + std::vector userData(16, 0u); + const auto texture = TextureDescriptor(texels, level); + std::copy(texture.begin(), texture.end(), userData.begin() + 8); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + { + std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + AgcDriver::Graphics::FlushCachedTextures(device.Device()); + } + device.WaitIdle(); +} + +void Check(const std::uint32_t* texels, bool last, const std::string& step) { + for (const auto offset : PastOffsets) Require(texels[offset / 4u] == StoredPast, step + ": byte offset " + std::to_string(offset) + " does not hold the store through level 3, past MAX_MIP 2, at its addrlib tail slot"); + for (const auto offset : LastOffsets) Require(texels[offset / 4u] == (last ? StoredLast : Untouched), step + ": byte offset " + std::to_string(offset) + " of level 2 holds an unexpected value"); + for (auto index = TailBlockBytes / 4u; index < SurfaceBytes / 4u; ++index) Require(texels[index] == Untouched, step + ": byte offset " + std::to_string(index * 4u) + " of levels 0 and 1 changed"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + std::vector storage((SurfaceBytes + 0x10000u) / 4u); + auto* texels = reinterpret_cast((reinterpret_cast(storage.data()) + 0xffffu) & ~std::uintptr_t{0xffffu}); + std::fill(texels, texels + SurfaceBytes / 4u, Untouched); + { + GuestAllocations::Mutation mutation; + mutation.Add(texels, SurfaceBytes, true, true); + } + Store(*device, texels, 3u, StorePastCode); + Check(texels, false, "store through level 3"); + Store(*device, texels, 2u, StoreLastCode); + Check(texels, true, "store through level 2 after level 3"); + { + GuestAllocations::Mutation mutation; + mutation.Remove(texels); + } + std::puts("view past the last mip tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} From b6298bf738028817e28a18e19e6d4227d1b3d8fa Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Sun, 4 Oct 2026 19:50:47 +1300 Subject: [PATCH 002/164] fix(agc): keep one storage image for a surface viewed past its last mip The descriptors of one surface share its storage image through the surface key, which includes the level count. A view past MAX_MIP counts the extra levels, so its storage image was a second image of the same memory, and the two flushed and re-uploaded each other every frame (PPSA21564 writes level 6 of each 1920x1080 bloom chain after building levels 1 to 5 through MAX_MIP 5 views). A surface once viewed past its allocated levels now keeps a single image holding the extended chain, and its other descriptors look that image up; the allocated-level image it replaces is evicted with its results stored. The extra levels lie in the allocation (the decode proved that), so levels 0 to MAX_MIP keep their layout and the views of them read the same texels. --- .../Graphics/include/GuestTextureResource.hpp | 1 + .../Graphics/src/GuestTextureResource.cpp | 1 + .../Graphics/src/ShaderResources.cpp | 50 ++++++++++- .../prx/libSceAgcDriver/tests/Recorder.cpp | 86 +++++++++++++++++++ 4 files changed, 134 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp index b09d3168c..84583699b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp @@ -61,6 +61,7 @@ struct GuestTextureResource { std::uint64_t dccAddress = 0; bool dccAlphaOnMsb = false; std::uint32_t minLod = 0; + std::uint32_t allocatedMipCount = 0; }; float EffectiveMinLod(const GuestTextureResource& resource); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp index 443517bba..a9954b1da 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp @@ -164,6 +164,7 @@ GuestTextureResource DecodeTextureResource(std::span words) result.depthOrLastArray = depth; result.baseArray = baseArray; result.mipCount = maxMip + 1u; + result.allocatedMipCount = maxMip + 1u; result.baseLevel = baseLevel; result.lastLevel = lastLevel; result.tileMode = tileMode; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index 5169e3ef8..60ab8c3ce 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -511,22 +511,64 @@ std::array SurfaceKey(const Context& context, const GuestTextu return {static_cast(resource.baseAddress), static_cast(resource.baseAddress >> 32u), resource.width, resource.height, (resource.depthOrLastArray << 16u) | (resource.mipCount & 0xffffu), (static_cast(resource.tileMode) << 12u) | (static_cast(resource.dimension) << 20u), resource.baseArray, static_cast(StorageFormatForGuest(context, resource.format))}; } +struct ExtendedSurfaces { + std::mutex mutex; + std::map, std::uint32_t> levels; + std::atomic any{false}; +}; + +ExtendedSurfaces& ExtendedChains() { + static ExtendedSurfaces surfaces; + return surfaces; +} + +std::uint32_t AllocatedLevels(const GuestTextureResource& resource) { + return resource.allocatedMipCount != 0 ? resource.allocatedMipCount : resource.mipCount; +} + +GuestTextureResource StorageSurface(const Context& context, const GuestTextureResource& viewed) { + auto& surfaces = ExtendedChains(); + const bool extended = viewed.mipCount > AllocatedLevels(viewed); + if (!extended && !surfaces.any.load(std::memory_order_acquire)) return viewed; + auto surface = viewed; + surface.mipCount = AllocatedLevels(viewed); + const auto identity = SurfaceKey(context, surface); + std::lock_guard lock(surfaces.mutex); + if (extended) { + auto& levels = surfaces.levels[identity]; + levels = std::max(levels, viewed.mipCount); + surfaces.any.store(true, std::memory_order_release); + surface.mipCount = levels; + } else if (const auto it = surfaces.levels.find(identity); it != surfaces.levels.end()) { + surface.mipCount = it->second; + } else { + surface.mipCount = viewed.mipCount; + } + return surface; +} + // `guestBytes` is the surface size when the caller described the surface already (0: described here). -std::shared_ptr cachedStorageTexture(const Context& context, std::span words, const GuestTextureResource& resource, std::uint32_t mip, std::uint64_t guestBytes) { - if (DepthSurfaceAt(resource.baseAddress)) { +std::shared_ptr cachedStorageTexture(const Context& context, std::span words, const GuestTextureResource& viewed, std::uint32_t mip, std::uint64_t guestBytes) { + if (DepthSurfaceAt(viewed.baseAddress)) { char text[112]; - std::snprintf(text, sizeof(text), "AGC graphics: storage image access to depth/stencil surface 0x%llx is not implemented", static_cast(resource.baseAddress)); + std::snprintf(text, sizeof(text), "AGC graphics: storage image access to depth/stencil surface 0x%llx is not implemented", static_cast(viewed.baseAddress)); throw std::runtime_error(text); } static const bool disabled = std::getenv("APS5_NO_TEXTURE_CACHE") != nullptr; - if (disabled) return std::make_shared(context, *context.detiler, resource, mip); + if (disabled) return std::make_shared(context, *context.detiler, viewed, mip); static_cast(words); const bool profile = LookupOutcomes::Profiled(); auto start = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; auto& counters = TextureCounts(); + const auto resource = StorageSurface(context, viewed); const StorageKey key{context.device, SurfaceKey(context, resource)}; auto& cache = StorageTextures(); std::lock_guard lock(cache.mutex); + if (resource.mipCount > AllocatedLevels(resource)) { + auto allocated = resource; + allocated.mipCount = AllocatedLevels(resource); + if (const auto found = findStorage(cache, {context.device, SurfaceKey(context, allocated)}); found != cache.entries.end()) evictStorage(cache, found); + } auto it = findStorage(cache, key); if (it != cache.entries.end() && MetadataMoved(*it->texture, resource)) { evictStorage(cache, it); diff --git a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp index 732e684a1..07a370f0e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp @@ -768,6 +768,91 @@ void movedMetadataTests(const Device& device, Recorder& recorder) { Require(holds(*back, {0x55, 0x55, 0x55, 0x55}), "the image remade under the first keys does not hold the stored texels"); } +void viewPastLastMipTests(const Device& device, Recorder& recorder) { + const auto& base = device.GetContext(); + if (base.hostImportAlignment == 0) { + std::cout << "host imports unavailable: views past the last mip not tested\n"; + return; + } + constexpr std::uint32_t side = 256; + constexpr std::size_t bytes = 0x60000; +#ifdef _WIN32 + void* block = VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); +#else + void* block = std::aligned_alloc(65536, bytes); +#endif + Require(block != nullptr, "cannot allocate the past-last-mip block"); + auto* texels = static_cast(block); + const auto address = reinterpret_cast(block); + { + GuestAllocations::Mutation mutation; + mutation.Add(block, bytes, true, true); + } + struct Unregister { + const Context& context; + void* block; + std::uint64_t address; + ~Unregister() { + ClearCachedTextures(context.device); + { + GuestAllocations::Mutation mutation; + mutation.Remove(block); + } + HostImportFor(context, address, bytes); + } + } unregister{base, block, address}; + if (HostImportFor(base, address, bytes) == nullptr) { + std::cout << "host import of the past-last-mip block refused: views past the last mip not tested\n"; + return; + } + TextureDetiler detiler(base); + auto context = base; + context.detiler = &detiler; + const auto view = [&](std::uint32_t level) { + const std::array words{ + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (56u << 20u) | (((side - 1u) & 3u) << 30u), + ((side - 1u) >> 2u) | ((side - 1u) << 14u), + 0xfacu | (level << 12u) | (level << 16u) | (0x1bu << 20u) | (9u << 28u), + 0u, + 2u << 4u, + 0u, + 0u, + }; + return DecodeTextureResource(words); + }; + const auto texel = [&](const StorageTexture& image, std::uint32_t level) { + Buffer readback(context, 4, VK_BUFFER_USAGE_TRANSFER_DST_BIT); + const auto commands = recorder.Commands(); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); + VkBufferImageCopy copy{}; + copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level, 0, 1}; + copy.imageExtent = {1, 1, 1}; + context.Function("vkCmdCopyImageToBuffer")(commands, image.Image(), VK_IMAGE_LAYOUT_GENERAL, readback.Handle(), 1, ©); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT); + recorder.Submit(); + device.WaitQueue(); + recorder.Sync(); + std::uint32_t value = 0; + std::memcpy(&value, readback.Bytes().data(), sizeof(value)); + return value; + }; + std::memset(texels, 0x55, bytes); + std::memset(texels + 0x8400, 0x66, 4); + std::memset(texels + 0x4800, 0x77, 4); + const auto allocatedView = view(2); + const auto pastView = view(3); + Require(allocatedView.mipCount == 3 && pastView.mipCount == 4 && DescribeSurface(pastView).guestBytes == bytes, "a 256x256 32 bpp SW_64KB_R_X surface viewed past MAX_MIP 2 decoded wrongly"); + const auto allocated = CachedStorageSurface(context, allocatedView); + Require(allocated->Descriptor().mipCount == 3, "the allocated levels' storage image has an unexpected level count"); + const auto past = CachedStorageSurface(context, pastView); + Require(past->Descriptor().mipCount == 4, "the storage image of a view past the last mip lacks the level it names"); + Require(!StorageImageCached(context, allocated.get()), "the allocated levels' image is still the surface's after a view past its last mip"); + Require(CachedStorageSurface(context, allocatedView) == past, "a view of the allocated levels took another image than the view past the last mip"); + Require(texel(*past, 2) == 0x66666666u, "level 2 was not read from its addrlib tail slot"); + Require(texel(*past, 3) == 0x77777777u, "the level past the last mip was not read from its addrlib tail slot"); +} + void resourceReadTests(const Device& device, Recorder& recorder) { const auto& context = device.GetContext(); if (context.hostImportAlignment == 0) { @@ -2455,6 +2540,7 @@ int main() { RunResidentPresentTests(device.GetContext()); storeRunTests(device, recorder); movedMetadataTests(device, recorder); + viewPastLastMipTests(device, recorder); keysFillTests(device, recorder); unitShadowTests(device, recorder); storageRefreshTests(device, recorder, false); From 1233dd815dc6b91b58908505be7780835794dbb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 12:16:07 +0200 Subject: [PATCH 003/164] fix(shader): clamp f32 results to +0 for NaN and -0 Co-Authored-By: Claude --- .../tests/execution/F32ResultClamp.cpp | 189 ++++++++++++++++++ .../src/SpirvAlu/SpirvAluEmitterMath.cpp | 5 +- 2 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/F32ResultClamp.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F32ResultClamp.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F32ResultClamp.cpp new file mode 100644 index 000000000..9833bcc22 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F32ResultClamp.cpp @@ -0,0 +1,189 @@ +#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 +#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, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1e0306, 0xd503800a, 0x02020b04, + 0xd504800b, 0x02020b04, 0xd508800c, 0x02020b04, 0xd508800d, 0x22020b04, 0xd54b800e, 0x041a0b04, + 0xd52b800f, 0x02020b04, 0xd50f8010, 0x02020b04, 0xd5108011, 0x22020b04, 0xd5578012, 0x041a0b04, + 0xd5a48013, 0x02010104, 0xd5a08014, 0x02010104, 0xd5858015, 0x02010104, 0xd5a18016, 0x22010104, + 0xd5a38017, 0x02010104, 0xd5548018, 0x041a0b04, 0xd5a28019, 0x22010104, 0xe0781000, 0x80010a03, + 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x7f800000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0xbf000000u, 0x3f800000u, 0xff800000u, 0x00000000u}, + {0xff800000u, 0xff800000u, 0x80000000u, 0x00000000u}, + {0x3f800000u, 0x3f000000u, 0x3f800001u, 0x00000000u}, + {0xc0200000u, 0x00000000u, 0x3f000000u, 0x00000000u}, + {0x40000000u, 0xbf800000u, 0x3f000000u, 0x00000000u}, + {0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x40000000u, 0x3f800000u, 0xbf800000u, 0x00000000u}, + {0xbf800000u, 0x7f800000u, 0x7f800000u, 0x00000000u}, + {0x7fc00000u, 0xff800000u, 0x7fc00000u, 0x00000000u}, + {0x7f800000u, 0x3f800001u, 0x00000000u, 0x00000000u}, + {0xbf800000u, 0xbf800000u, 0x7fc00000u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x7fc00000u, 0x7f800000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0xbf800000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7fc00000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xffc00000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7f800001u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7fc12345u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xff800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3f000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3f800001u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xbf000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3f7fffffu, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x40000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xbf800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xc0200000u, 0x00000000u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u}, + {0x3f000000u, 0x00000000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x3f800000u, 0x3f000000u, 0x3f000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x3f000000u, 0x3f000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f000000u, 0x3f000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f000000u, 0x3f800000u}, + {0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u}, + {0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f000000u, 0x3f000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f7fffffu, 0x3f7fffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f7fffffu, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u} +}; +constexpr std::uint32_t Checked[32] = {0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xfeffu, 0xffffu, 0xffffu, 0xffffu, 0xfeffu, 0xffffu, 0xfeffu, 0xffffu, 0xfeffu, 0xffffu, 0xffffu, 0xfeffu, 0xfeffu, 0xbeffu, 0xfeffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu}; +constexpr const char* Names[16] = { + "v_add_f32_e64 v10, v4, v5 clamp", + "v_sub_f32_e64 v11, v4, v5 clamp", + "v_mul_f32_e64 v12, v4, v5 clamp", + "v_mul_f32_e64 v13, -v4, v5 clamp", + "v_fma_f32 v14, v4, v5, v6 clamp", + "v_fmac_f32_e64 v15, v4, v5 clamp", + "v_min_f32_e64 v16, v4, v5 clamp", + "v_max_f32_e64 v17, -v4, v5 clamp", + "v_med3_f32 v18, v4, v5, v6 clamp", + "v_floor_f32_e64 v19, v4 clamp", + "v_fract_f32_e64 v20, v4 clamp", + "v_cvt_f32_i32_e64 v21, v4 clamp", + "v_trunc_f32_e64 v22, -v4 clamp", + "v_rndne_f32_e64 v23, v4 clamp", + "v_max3_f32 v24, v4, v5, v6 clamp", + "v_ceil_f32_e64 v25, -v4 clamp", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("f32 result clamp: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + if ((Checked[tid] >> i) & 1u) Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("f32 result clamp tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp index 8ed46f17e..b6f3a4984 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp @@ -249,7 +249,10 @@ std::uint32_t EmitPackFloat2x16Rtz(SpirvEmitterState& state, std::uint32_t arg0, } std::uint32_t EmitFPSaturate32(SpirvEmitterState& state, std::uint32_t arg0) { - return EmitExt(state, TypeF32(state), GLSLstd450FClamp, {arg0, ConstantF32(state, 0u), ConstantF32(state, 0x3f800000u)}); + const auto bits = Unary(state, spv::OpBitcast, TypeU32(state), arg0); + const auto positive = Binary(state, spv::OpULessThan, TypeBool(state), Binary(state, spv::OpISub, TypeU32(state), bits, ConstantU32(state, 1u)), ConstantU32(state, 0x7f800000u)); + const auto upper = EmitExt(state, TypeU32(state), GLSLstd450UMin, {bits, ConstantU32(state, 0x3f800000u)}); + return Unary(state, spv::OpBitcast, TypeF32(state), Select(state, TypeU32(state), positive, upper, ConstantU32(state, 0u))); } std::uint32_t EmitIAdd64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1) { From 50256b0ac4448a08f48bd1f1cbc6e3e3eae16b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 12:41:07 +0200 Subject: [PATCH 004/164] fix(shader): VOP3 op_sel on v_alignbit/alignbyte_b32 and 16-bit max3/min3/med3 op_sel[1:0] on v_alignbit_b32/v_alignbyte_b32 selects the byte of src2 that holds the shift, op_sel[2] is ignored and op_sel[3] writes the low 16 result bits to the high half with the low half zeroed (measured on gfx1035). v_alignbyte_b32 now uses src2[1:0] only, as the ISA says; values >= 4 gave a wrong result before. v_max3/min3_{i16,u16} and v_med3_u16 take op_sel like v_med3_i16 already does. Co-Authored-By: Claude --- .../tests/execution/Vop3AlignOpSel.cpp | 190 ++++++++++++++++++ .../execution/Vop3Integer16TernaryOpSel.cpp | 190 ++++++++++++++++++ .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 28 ++- .../Translation/src/IntegerInstructions.cpp | 9 +- 4 files changed, 409 insertions(+), 8 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Vop3AlignOpSel.cpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Vop3Integer16TernaryOpSel.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3AlignOpSel.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3AlignOpSel.cpp new file mode 100644 index 000000000..8a07028cb --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3AlignOpSel.cpp @@ -0,0 +1,190 @@ +#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 +#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, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e140307, 0xd54e000a, 0x041a0b04, + 0x7e160307, 0xd54e080b, 0x041a0b04, 0x7e180307, 0xd54e100c, 0x041a0b04, 0x7e1a0307, 0xd54e180d, + 0x041a0b04, 0x7e1c0307, 0xd54e200e, 0x041a0b04, 0x7e1e0307, 0xd54e400f, 0x041a0b04, 0x7e200307, + 0xd54e4810, 0x041a0b04, 0x7e220307, 0xd54e7811, 0x041a0b04, 0x7e240307, 0xd54f0012, 0x041a0b04, + 0x7e260307, 0xd54f0813, 0x041a0b04, 0x7e280307, 0xd54f1014, 0x041a0b04, 0x7e2a0307, 0xd54f1815, + 0x041a0b04, 0x7e2c0307, 0xd54f2016, 0x041a0b04, 0x7e2e0307, 0xd54f4017, 0x041a0b04, 0x7e300307, + 0xd54f5018, 0x041a0b04, 0x7e320307, 0xd54f7819, 0x041a0b04, 0xe0781000, 0x80010a03, 0xe0781010, + 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0xc0001234u, 0x73ab4876u, 0x04082408u, 0x7253edc6u}, + {0x05b4d756u, 0xedccc000u, 0x1f210011u, 0x24aa1734u}, + {0x8000fffeu, 0xad0ee1e7u, 0x00ffffffu, 0x80018000u}, + {0xdc8cf93au, 0xe3653b50u, 0x21030300u, 0xffff0002u}, + {0x400000ffu, 0xfffe4000u, 0xff001101u, 0x80018000u}, + {0xf8800b51u, 0xfffe4000u, 0x20018024u, 0x7ffffffeu}, + {0x80017fffu, 0x8000edccu, 0x031f0180u, 0xb63f2c62u}, + {0xc0001234u, 0x80010002u, 0x01203f03u, 0x0001fffeu}, + {0x89e7d15fu, 0x986e86cbu, 0x01010300u, 0x30b17d0bu}, + {0x8001fffeu, 0x97491e23u, 0x0300241fu, 0x800000ffu}, + {0x00011234u, 0x7fffc000u, 0x3f040300u, 0x1b98fbe4u}, + {0xf542441du, 0x00007fffu, 0x03803f3fu, 0x7fff00ffu}, + {0x80000001u, 0x03e0d681u, 0x00041007u, 0xc000fffeu}, + {0x2d7c5048u, 0x728a6fcfu, 0x3f043f80u, 0x00007fffu}, + {0xedccc000u, 0xff769e37u, 0x0701043fu, 0x36971e1bu}, + {0x8001edccu, 0x62dc08d6u, 0x2100100fu, 0x5f27ff08u}, + {0xf589d99au, 0xddd4a054u, 0x0f10101fu, 0x2891dd3cu}, + {0xafdd8733u, 0x6be49ee7u, 0x3f101101u, 0xedccffffu}, + {0x4b1e943eu, 0x205bc308u, 0x000f0f11u, 0xffff8000u}, + {0x3fc2a908u, 0x482ea760u, 0xff072103u, 0xfb019df4u}, + {0x00000000u, 0x40008001u, 0x24030001u, 0x80004000u}, + {0x00010001u, 0x8bcce7cdu, 0x03212007u, 0x0001fffeu}, + {0x0000c000u, 0xedcc0000u, 0x0303003fu, 0x000100ffu}, + {0x4000ffffu, 0x40004000u, 0x0003ffffu, 0x0001edccu}, + {0xc0000002u, 0xa8f51ac5u, 0x0f113f20u, 0xa2592b9du}, + {0xe1594dc4u, 0x95468325u, 0x01212407u, 0xb6125e0cu}, + {0xfffefffeu, 0xffff7fffu, 0x20ff2024u, 0x131e2d48u}, + {0x80004000u, 0x4f1c9ce2u, 0x0fff3f07u, 0x065479e4u}, + {0xc77d357fu, 0x000100ffu, 0x0f032104u, 0x33080a1du}, + {0xc0000000u, 0x88ebd524u, 0x08211110u, 0x64bd7a63u}, + {0x4000ffffu, 0x2141c6d1u, 0xff203f00u, 0x53935c55u}, + {0xb8d0c65du, 0x369a9ad7u, 0x0011201fu, 0x5937c1f0u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x3473ab48u, 0x473ab487u, 0x3473ab48u, 0x473ab487u, 0x3473ab48u, 0xab480000u, 0xb4870000u, 0xb4870000u, 0x73ab4876u, 0x73ab4876u, 0x73ab4876u, 0x73ab4876u, 0x73ab4876u, 0x48760000u, 0x48760000u, 0x48760000u}, + {0x6bab76e6u, 0xedccc000u, 0x76e66000u, 0x0b69aeadu, 0x6bab76e6u, 0x76e60000u, 0xc0000000u, 0xaead0000u, 0x56edccc0u, 0xedccc000u, 0x56edccc0u, 0xb4d756edu, 0x56edccc0u, 0xccc00000u, 0xccc00000u, 0x56ed0000u}, + {0x0001fffdu, 0x0001fffdu, 0x0001fffdu, 0xad0ee1e7u, 0x0001fffdu, 0xfffd0000u, 0xfffd0000u, 0xe1e70000u, 0x00fffeadu, 0x00fffeadu, 0x00fffeadu, 0xad0ee1e7u, 0x00fffeadu, 0xfead0000u, 0xfead0000u, 0xe1e70000u}, + {0xe3653b50u, 0x5c6ca76au, 0x5c6ca76au, 0x71b29da8u, 0xe3653b50u, 0x3b500000u, 0xa76a0000u, 0x9da80000u, 0xe3653b50u, 0x8cf93ae3u, 0x8cf93ae3u, 0x3ae3653bu, 0xe3653b50u, 0x3b500000u, 0x3ae30000u, 0x653b0000u}, + {0xffff2000u, 0x007fffffu, 0xfffe4000u, 0x800001ffu, 0xffff2000u, 0x20000000u, 0xffff0000u, 0x01ff0000u, 0xfffffe40u, 0xfffffe40u, 0xfffe4000u, 0x0000ffffu, 0xfffffe40u, 0xfe400000u, 0x40000000u, 0xffff0000u}, + {0x1fffe400u, 0xfffe4000u, 0xffff2000u, 0xfffe4000u, 0x1fffe400u, 0xe4000000u, 0x40000000u, 0x40000000u, 0xfffe4000u, 0xfffe4000u, 0x51fffe40u, 0xfffe4000u, 0xfffe4000u, 0x40000000u, 0xfe400000u, 0x40000000u}, + {0x8000edccu, 0xc00076e6u, 0x0002ffffu, 0xf0001db9u, 0x8000edccu, 0xedcc0000u, 0x76e60000u, 0x1db90000u, 0x8000edccu, 0xff8000edu, 0x017fff80u, 0x017fff80u, 0x8000edccu, 0xedcc0000u, 0xff800000u, 0xff800000u}, + {0x90002000u, 0x80002469u, 0x80010002u, 0x40008001u, 0x90002000u, 0x20000000u, 0x24690000u, 0x80010000u, 0x00123480u, 0x00123480u, 0x80010002u, 0x34800100u, 0x00123480u, 0x34800000u, 0x00020000u, 0x01000000u}, + {0x986e86cbu, 0xf30dd0d9u, 0xcc374365u, 0xcc374365u, 0x986e86cbu, 0x86cb0000u, 0xd0d90000u, 0x43650000u, 0x986e86cbu, 0xe7d15f98u, 0x5f986e86u, 0x5f986e86u, 0x986e86cbu, 0x86cb0000u, 0x6e860000u, 0x6e860000u}, + {0x0003fffdu, 0xe97491e2u, 0x97491e23u, 0xd2e923c4u, 0x0003fffdu, 0xfffd0000u, 0x91e20000u, 0x23c40000u, 0x01fffe97u, 0x97491e23u, 0x97491e23u, 0x01fffe97u, 0x01fffe97u, 0xfe970000u, 0x1e230000u, 0xfe970000u}, + {0x7fffc000u, 0x8ffff800u, 0x47fffc00u, 0x00022468u, 0x7fffc000u, 0xc0000000u, 0xf8000000u, 0x24680000u, 0x7fffc000u, 0x0112347fu, 0x7fffc000u, 0x0112347fu, 0x7fffc000u, 0xc0000000u, 0xc0000000u, 0x347f0000u}, + {0xea84883au, 0xea84883au, 0x00007fffu, 0xa0000fffu, 0xea84883au, 0x883a0000u, 0x883a0000u, 0x0fff0000u, 0x42441d00u, 0x42441d00u, 0x00007fffu, 0x42441d00u, 0x42441d00u, 0x1d000000u, 0x7fff0000u, 0x1d000000u}, + {0x0207c1adu, 0x000103e0u, 0x103e0d68u, 0x03e0d681u, 0x0207c1adu, 0xc1ad0000u, 0x03e00000u, 0xd6810000u, 0x00000103u, 0x03e0d681u, 0x03e0d681u, 0x03e0d681u, 0x00000103u, 0x01030000u, 0xd6810000u, 0xd6810000u}, + {0x728a6fcfu, 0x5af8a090u, 0x8728a6fcu, 0x5af8a090u, 0x728a6fcfu, 0x6fcf0000u, 0xa0900000u, 0xa0900000u, 0x728a6fcfu, 0x7c504872u, 0x728a6fcfu, 0x7c504872u, 0x728a6fcfu, 0x6fcf0000u, 0x6fcf0000u, 0x48720000u}, + {0xdb998001u, 0x0ff769e3u, 0x7fbb4f1bu, 0x01feed3cu, 0xdb998001u, 0x80010000u, 0x69e30000u, 0xed3c0000u, 0xccc000ffu, 0xff769e37u, 0x00ff769eu, 0xccc000ffu, 0xccc000ffu, 0x00ff0000u, 0x769e0000u, 0x00ff0000u}, + {0xdb98c5b8u, 0xedcc62dcu, 0x62dc08d6u, 0x316e046bu, 0xdb98c5b8u, 0xc5b80000u, 0x62dc0000u, 0x046b0000u, 0x01edcc62u, 0x62dc08d6u, 0x62dc08d6u, 0xcc62dc08u, 0x01edcc62u, 0xcc620000u, 0x08d60000u, 0xdc080000u}, + {0xeb13b335u, 0xd99addd4u, 0xd99addd4u, 0xb335bba9u, 0xeb13b335u, 0xb3350000u, 0xddd40000u, 0xbba90000u, 0x89d99addu, 0xddd4a054u, 0xddd4a054u, 0x89d99addu, 0x89d99addu, 0x9add0000u, 0xa0540000u, 0x9add0000u}, + {0xb5f24f73u, 0xc399b5f2u, 0x87336be4u, 0x5fbb0e66u, 0xb5f24f73u, 0x4f730000u, 0xb5f20000u, 0x0e660000u, 0x336be49eu, 0x336be49eu, 0x6be49ee7u, 0xdd87336bu, 0x336be49eu, 0xe49e0000u, 0x9ee70000u, 0x336b0000u}, + {0x4a1f102du, 0x287c40b7u, 0x287c40b7u, 0x205bc308u, 0x4a1f102du, 0x102d0000u, 0x40b70000u, 0xc3080000u, 0x3e205bc3u, 0x1e943e20u, 0x1e943e20u, 0x205bc308u, 0x3e205bc3u, 0x5bc30000u, 0x3e200000u, 0xc3080000u}, + {0x0905d4ecu, 0x241753b0u, 0x10905d4eu, 0x7f855210u, 0x0905d4ecu, 0xd4ec0000u, 0x53b00000u, 0x52100000u, 0xc2a90848u, 0x08482ea7u, 0xc2a90848u, 0xc2a90848u, 0xc2a90848u, 0x08480000u, 0x08480000u, 0x08480000u}, + {0x20004000u, 0x40008001u, 0x08001000u, 0x04000800u, 0x20004000u, 0x40000000u, 0x80010000u, 0x08000000u, 0x00400080u, 0x40008001u, 0x00000040u, 0x40008001u, 0x00400080u, 0x00800000u, 0x00400000u, 0x80010000u}, + {0x031799cfu, 0x8bcce7cdu, 0xc5e673e6u, 0x31799cf9u, 0x031799cfu, 0x99cf0000u, 0xe7cd0000u, 0x9cf90000u, 0x0100018bu, 0x8bcce7cdu, 0x018bcce7u, 0x0100018bu, 0x0100018bu, 0x018b0000u, 0xcce70000u, 0x018b0000u}, + {0x00018001u, 0xedcc0000u, 0x1db98000u, 0x1db98000u, 0x00018001u, 0x80010000u, 0x00000000u, 0x80000000u, 0x00c000edu, 0xedcc0000u, 0x00c000edu, 0x00c000edu, 0x00c000edu, 0x00ed0000u, 0x00ed0000u, 0x00ed0000u}, + {0x8001fffeu, 0x8001fffeu, 0xe8000800u, 0x40004000u, 0x8001fffeu, 0xfffe0000u, 0xfffe0000u, 0x40000000u, 0x00ffff40u, 0x00ffff40u, 0x00ffff40u, 0x40004000u, 0x00ffff40u, 0xff400000u, 0xff400000u, 0x40000000u}, + {0xa8f51ac5u, 0x80000005u, 0x0001547au, 0x000551eau, 0xa8f51ac5u, 0x1ac50000u, 0x00050000u, 0x51ea0000u, 0xa8f51ac5u, 0x000002a8u, 0x02a8f51au, 0x000002a8u, 0xa8f51ac5u, 0x1ac50000u, 0xf51a0000u, 0x02a80000u}, + {0x892a8d06u, 0x49546832u, 0x4aa34192u, 0x4aa34192u, 0x892a8d06u, 0x8d060000u, 0x68320000u, 0x41920000u, 0x594dc495u, 0x95468325u, 0xc4954683u, 0xc4954683u, 0x594dc495u, 0xc4950000u, 0x46830000u, 0x46830000u}, + {0xeffff7ffu, 0xffff7fffu, 0xfffdfffdu, 0xffff7fffu, 0xeffff7ffu, 0xf7ff0000u, 0x7fff0000u, 0x7fff0000u, 0xffff7fffu, 0xffff7fffu, 0xfefffeffu, 0xffff7fffu, 0xffff7fffu, 0x7fff0000u, 0xfeff0000u, 0x7fff0000u}, + {0x009e3939u, 0x00008000u, 0x00008000u, 0x80009e39u, 0x009e3939u, 0x39390000u, 0x80000000u, 0x9e390000u, 0x0040004fu, 0x0040004fu, 0x0040004fu, 0x0040004fu, 0x0040004fu, 0x004f0000u, 0x004f0000u, 0x004f0000u}, + {0xf000100fu, 0x8000807fu, 0xe000201fu, 0x6afe0002u, 0xf000100fu, 0x100f0000u, 0x807f0000u, 0x00020000u, 0x000100ffu, 0x7f000100u, 0x7d357f00u, 0x7d357f00u, 0x000100ffu, 0x00ff0000u, 0x7f000000u, 0x7f000000u}, + {0x000088ebu, 0x00004475u, 0x4475ea92u, 0x0088ebd5u, 0x000088ebu, 0x88eb0000u, 0x44750000u, 0xebd50000u, 0x88ebd524u, 0x0088ebd5u, 0x0088ebd5u, 0x88ebd524u, 0x88ebd524u, 0xd5240000u, 0xebd50000u, 0xd5240000u}, + {0x2141c6d1u, 0x8001fffeu, 0x2141c6d1u, 0x8001fffeu, 0x2141c6d1u, 0xc6d10000u, 0xfffe0000u, 0xfffe0000u, 0x2141c6d1u, 0x00ffff21u, 0x2141c6d1u, 0x00ffff21u, 0x2141c6d1u, 0xc6d10000u, 0xc6d10000u, 0xff210000u}, + {0x71a18cbau, 0x369a9ad7u, 0x632e9b4du, 0x369a9ad7u, 0x71a18cbau, 0x8cba0000u, 0x9ad70000u, 0x9ad70000u, 0xd0c65d36u, 0x369a9ad7u, 0x5d369a9au, 0x369a9ad7u, 0xd0c65d36u, 0x5d360000u, 0x9a9a0000u, 0x9ad70000u} +}; +constexpr const char* Names[16] = { + "v_alignbit_b32 v10, v4, v5, v6 op_sel:[0,0,0,0]", + "v_alignbit_b32 v11, v4, v5, v6 op_sel:[1,0,0,0]", + "v_alignbit_b32 v12, v4, v5, v6 op_sel:[0,1,0,0]", + "v_alignbit_b32 v13, v4, v5, v6 op_sel:[1,1,0,0]", + "v_alignbit_b32 v14, v4, v5, v6 op_sel:[0,0,1,0]", + "v_alignbit_b32 v15, v4, v5, v6 op_sel:[0,0,0,1]", + "v_alignbit_b32 v16, v4, v5, v6 op_sel:[1,0,0,1]", + "v_alignbit_b32 v17, v4, v5, v6 op_sel:[1,1,1,1]", + "v_alignbyte_b32 v18, v4, v5, v6 op_sel:[0,0,0,0]", + "v_alignbyte_b32 v19, v4, v5, v6 op_sel:[1,0,0,0]", + "v_alignbyte_b32 v20, v4, v5, v6 op_sel:[0,1,0,0]", + "v_alignbyte_b32 v21, v4, v5, v6 op_sel:[1,1,0,0]", + "v_alignbyte_b32 v22, v4, v5, v6 op_sel:[0,0,1,0]", + "v_alignbyte_b32 v23, v4, v5, v6 op_sel:[0,0,0,1]", + "v_alignbyte_b32 v24, v4, v5, v6 op_sel:[0,1,0,1]", + "v_alignbyte_b32 v25, v4, v5, v6 op_sel:[1,1,1,1]", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop3 align op sel: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop3 align op sel tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3Integer16TernaryOpSel.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3Integer16TernaryOpSel.cpp new file mode 100644 index 000000000..ba6169955 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3Integer16TernaryOpSel.cpp @@ -0,0 +1,190 @@ +#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 +#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, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e140307, 0xd755280a, 0x041a0b04, + 0x7e160307, 0xd756500b, 0x041a0b04, 0x7e180307, 0xd752180c, 0x041a0b04, 0x7e1a0307, 0xd753600d, + 0x041a0b04, 0x7e1c0307, 0xd759380e, 0x041a0b04, 0x7e1e0307, 0xd759000f, 0x041a0b04, 0x7e200307, + 0xd7564810, 0x041a0b04, 0x7e220307, 0xd7527011, 0x041a0b04, 0x7e240307, 0xd7583012, 0x041a0b04, + 0x7e260307, 0xd7554013, 0x041a0b04, 0x7e280307, 0xd7530814, 0x041a0b04, 0x7e2a0307, 0xd7595815, + 0x041a0b04, 0x7e2c0307, 0xd7560016, 0x041a0b04, 0x7e2e0307, 0xd7520017, 0x041a0b04, 0x7e300307, + 0xd7551018, 0x041a0b04, 0x7e320307, 0xd7596019, 0x041a0b04, 0xe0781000, 0x80010a03, 0xe0781010, + 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x4000fffeu, 0x1eede1dcu, 0xc1dc7ed2u, 0xffff7fffu}, + {0xedccc000u, 0x00ff00ffu, 0x123400ffu, 0x00020002u}, + {0x9fe407c8u, 0x40004000u, 0x40000001u, 0xedccffffu}, + {0x00ff00ffu, 0x00ff00ffu, 0x80018001u, 0x12340001u}, + {0xffff00ffu, 0x24f75609u, 0x0000edccu, 0xa1ae691du}, + {0x40000000u, 0x75916032u, 0x00f77715u, 0x9e9c8053u}, + {0x78bd5cc4u, 0x80010000u, 0x000000ffu, 0xc000edccu}, + {0x123400ffu, 0x00011234u, 0x400000ffu, 0x4000c000u}, + {0x00011234u, 0x7fff1234u, 0x12341234u, 0x00fffffeu}, + {0x1234c000u, 0xc0000001u, 0x00021234u, 0x5eda0014u}, + {0x12347fffu, 0x40e8d5edu, 0x00ff0002u, 0x67604bebu}, + {0x00028001u, 0x00ff8001u, 0x00ff00ffu, 0x9b20386du}, + {0x2027553bu, 0x12341234u, 0x7fff1234u, 0xffffffffu}, + {0x00001234u, 0x12340000u, 0x024e6fa2u, 0x80252ce1u}, + {0x49ff7159u, 0xffff8000u, 0xffff8000u, 0x80010002u}, + {0xc0007fffu, 0x0000c000u, 0x00004000u, 0x80010001u}, + {0x00ff0000u, 0x7fff0000u, 0x7cfc2ed5u, 0x8001edccu}, + {0xc0007fffu, 0xedcc7fffu, 0x00021234u, 0xffff00ffu}, + {0x00021234u, 0x0002edccu, 0x00000001u, 0x9138a73cu}, + {0x3e2f6da7u, 0x00d72a89u, 0x0f50aef1u, 0x13022bdcu}, + {0x00ff8000u, 0xfcbf3c4bu, 0x8001c000u, 0x5df76ae5u}, + {0x00008001u, 0x8001edccu, 0x0001edccu, 0x5dd86332u}, + {0xdf891833u, 0xbe5937f8u, 0xfffe1234u, 0x542edc5fu}, + {0x0d9f3670u, 0x55502ea4u, 0x0002fffeu, 0xbcda0e3bu}, + {0x00020001u, 0x7e9868d8u, 0xc67c9920u, 0xf336da84u}, + {0xc000edccu, 0xfffe00ffu, 0x62aa0f64u, 0x80011234u}, + {0x80001234u, 0x401b25b8u, 0x3b404f9du, 0x9ec28382u}, + {0x0001ffffu, 0xdd91f71cu, 0xf13510b5u, 0x00020000u}, + {0x40004000u, 0x7fff8001u, 0x22c35d87u, 0xc0000000u}, + {0x2a18f40fu, 0x6bbd2914u, 0xc000fffeu, 0xedcc0002u}, + {0xa7f488d4u, 0x00ff4000u, 0x000100ffu, 0xedccfffeu}, + {0xfffefffeu, 0x7fff00ffu, 0x0002fffeu, 0xd816b1beu} +}; +constexpr std::uint32_t Expected[32][16] = { + {0xffff4000u, 0xfffe7fffu, 0xffff1eedu, 0xc1dc7fffu, 0xffff4000u, 0xffffe1dcu, 0xe1dc7fffu, 0xc1dc7fffu, 0xfffffffeu, 0x7ed27fffu, 0xffff4000u, 0x40007fffu, 0xfffffffeu, 0xffffe1dcu, 0xffff7ed2u, 0xe1dc7fffu}, + {0x00021234u, 0xc0000002u, 0x0002edccu, 0x00ff0002u, 0x00021234u, 0x000200ffu, 0xedcc0002u, 0xc0000002u, 0x000200ffu, 0x00ff0002u, 0x000200ffu, 0x00ff0002u, 0x0002c000u, 0x0002c000u, 0x000200ffu, 0x12340002u}, + {0xedcc4000u, 0x4000ffffu, 0xedcc9fe4u, 0x07c8ffffu, 0xedcc4000u, 0xedcc07c8u, 0x9fe4ffffu, 0x07c8ffffu, 0xedcc4000u, 0x4000ffffu, 0xedcc0001u, 0x4000ffffu, 0xedcc4000u, 0xedcc0001u, 0xedcc4000u, 0x4000ffffu}, + {0x123400ffu, 0x80010001u, 0x12348001u, 0x00ff0001u, 0x123400ffu, 0x123400ffu, 0x80010001u, 0x80010001u, 0x123400ffu, 0x00ff0001u, 0x123400ffu, 0x00ff0001u, 0x12348001u, 0x12348001u, 0x123400ffu, 0x00ff0001u}, + {0xa1ae5609u, 0xedcc691du, 0xa1aeedccu, 0x0000691du, 0xa1ae24f7u, 0xa1ae5609u, 0xffff691du, 0x0000691du, 0xa1ae00ffu, 0x5609691du, 0xa1ae5609u, 0xedcc691du, 0xa1aeedccu, 0xa1aeedccu, 0xa1ae24f7u, 0x00ff691du}, + {0x9e9c6032u, 0x77158053u, 0x9e9c4000u, 0x00008053u, 0x9e9c4000u, 0x9e9c6032u, 0x77158053u, 0x00008053u, 0x9e9c00f7u, 0x77158053u, 0x9e9c4000u, 0x75918053u, 0x9e9c7715u, 0x9e9c0000u, 0x9e9c7715u, 0x00f78053u}, + {0xc00078bdu, 0x8001edccu, 0xc0008001u, 0x0000edccu, 0xc00078bdu, 0xc00000ffu, 0x78bdedccu, 0x8001edccu, 0xc0000000u, 0x5cc4edccu, 0xc0000000u, 0x78bdedccu, 0xc0005cc4u, 0xc0000000u, 0xc0005cc4u, 0x0000edccu}, + {0x40004000u, 0x00ffc000u, 0x40000001u, 0x00ffc000u, 0x40001234u, 0x400000ffu, 0x1234c000u, 0x0001c000u, 0x400000ffu, 0x1234c000u, 0x400000ffu, 0x00ffc000u, 0x40001234u, 0x400000ffu, 0x400000ffu, 0x1234c000u}, + {0x00ff1234u, 0x7ffffffeu, 0x00ff0001u, 0x1234fffeu, 0x00ff1234u, 0x00ff1234u, 0x1234fffeu, 0x1234fffeu, 0x00ff1234u, 0x1234fffeu, 0x00ff0001u, 0x1234fffeu, 0x00ff1234u, 0x00ff1234u, 0x00ff7fffu, 0x1234fffeu}, + {0x5eda1234u, 0xc0000014u, 0x5edac000u, 0x00010014u, 0x5eda1234u, 0x5eda1234u, 0x12340014u, 0xc0000014u, 0x5edac000u, 0x12340014u, 0x5eda0001u, 0x12340014u, 0x5edac000u, 0x5edac000u, 0x5eda1234u, 0x00020014u}, + {0x67601234u, 0x7fff4bebu, 0x67600002u, 0x00ff4bebu, 0x67601234u, 0x67607fffu, 0xd5ed4bebu, 0x00ff4bebu, 0x676040e8u, 0x7fff4bebu, 0x67600002u, 0x12344bebu, 0x6760d5edu, 0x6760d5edu, 0x67607fffu, 0x7fff4bebu}, + {0x9b2000ffu, 0x8001386du, 0x9b200002u, 0x00ff386du, 0x9b2000ffu, 0x9b208001u, 0x8001386du, 0x8001386du, 0x9b2000ffu, 0x00ff386du, 0x9b200002u, 0x00ff386du, 0x9b208001u, 0x9b208001u, 0x9b2000ffu, 0x8001386du}, + {0xffff7fffu, 0x553bffffu, 0xffff1234u, 0x1234ffffu, 0xffff2027u, 0xffff1234u, 0x2027ffffu, 0x1234ffffu, 0xffff553bu, 0x553bffffu, 0xffff1234u, 0x1234ffffu, 0xffff553bu, 0xffff1234u, 0xffff553bu, 0x553bffffu}, + {0x8025024eu, 0x6fa22ce1u, 0x80250000u, 0x00002ce1u, 0x8025024eu, 0x80251234u, 0x6fa22ce1u, 0x024e2ce1u, 0x80251234u, 0x6fa22ce1u, 0x80250000u, 0x12342ce1u, 0x80256fa2u, 0x80250000u, 0x80256fa2u, 0x024e2ce1u}, + {0x800149ffu, 0xffff0002u, 0x80018000u, 0x71590002u, 0x8001ffffu, 0x80018000u, 0x80000002u, 0xffff0002u, 0x8001ffffu, 0x71590002u, 0x800149ffu, 0x80000002u, 0x80018000u, 0x80018000u, 0x80017159u, 0x80000002u}, + {0x80010000u, 0x7fff0001u, 0x8001c000u, 0x00000001u, 0x80010000u, 0x80017fffu, 0xc0000001u, 0x00000001u, 0x80010000u, 0x7fff0001u, 0x80014000u, 0x40000001u, 0x8001c000u, 0x8001c000u, 0x80017fffu, 0x7fff0001u}, + {0x80017cfcu, 0x7fffedccu, 0x800100ffu, 0x0000edccu, 0x80017cfcu, 0x80010000u, 0x2ed5edccu, 0x0000edccu, 0x80017cfcu, 0x2ed5edccu, 0x80010000u, 0x2ed5edccu, 0x80012ed5u, 0x80010000u, 0x80017fffu, 0x0000edccu}, + {0xffff7fffu, 0xedcc00ffu, 0xffffc000u, 0x000200ffu, 0xffffc000u, 0xffff7fffu, 0xc00000ffu, 0xedcc00ffu, 0xffff0002u, 0x7fff00ffu, 0xffff1234u, 0xc00000ffu, 0xffff7fffu, 0xffff1234u, 0xffff7fffu, 0x7fff00ffu}, + {0x91380002u, 0x1234a73cu, 0x91380001u, 0x0000a73cu, 0x91380002u, 0x91381234u, 0xedcca73cu, 0x0000a73cu, 0x91380002u, 0x1234a73cu, 0x91380001u, 0x0002a73cu, 0x9138edccu, 0x9138edccu, 0x91381234u, 0x1234a73cu}, + {0x13023e2fu, 0xaef12bdcu, 0x1302aef1u, 0x0f502bdcu, 0x13020f50u, 0x13026da7u, 0xaef12bdcu, 0x00d72bdcu, 0x13020f50u, 0x6da72bdcu, 0x13022a89u, 0x3e2f2bdcu, 0x1302aef1u, 0x1302aef1u, 0x13026da7u, 0x2a892bdcu}, + {0x5df73c4bu, 0xfcbf6ae5u, 0x5df7c000u, 0x3c4b6ae5u, 0x5df78001u, 0x5df78000u, 0xc0006ae5u, 0x80006ae5u, 0x5df78001u, 0x3c4b6ae5u, 0x5df700ffu, 0xc0006ae5u, 0x5df7c000u, 0x5df78000u, 0x5df7fcbfu, 0x80006ae5u}, + {0x5dd80001u, 0xedcc6332u, 0x5dd88001u, 0x00016332u, 0x5dd80001u, 0x5dd8edccu, 0xedcc6332u, 0x80016332u, 0x5dd88001u, 0xedcc6332u, 0x5dd80000u, 0x80016332u, 0x5dd8edccu, 0x5dd88001u, 0x5dd8edccu, 0x80016332u}, + {0x542e37f8u, 0xbe59dc5fu, 0x542ebe59u, 0x1833dc5fu, 0x542edf89u, 0x542e1833u, 0xdf89dc5fu, 0xbe59dc5fu, 0x542efffeu, 0x37f8dc5fu, 0x542e1234u, 0xbe59dc5fu, 0x542e37f8u, 0x542e1234u, 0x542e1833u, 0x37f8dc5fu}, + {0xbcda2ea4u, 0xfffe0e3bu, 0xbcdafffeu, 0x00020e3bu, 0xbcda0d9fu, 0xbcda3670u, 0xfffe0e3bu, 0x00020e3bu, 0xbcda3670u, 0x36700e3bu, 0xbcda0d9fu, 0x55500e3bu, 0xbcdafffeu, 0xbcdafffeu, 0xbcda5550u, 0x2ea40e3bu}, + {0xf33668d8u, 0x9920da84u, 0xf3369920u, 0x0001da84u, 0xf3367e98u, 0xf33668d8u, 0x9920da84u, 0xc67cda84u, 0xf3360001u, 0x68d8da84u, 0xf3360002u, 0x7e98da84u, 0xf3369920u, 0xf3369920u, 0xf3367e98u, 0x68d8da84u}, + {0x800162aau, 0xfffe1234u, 0x8001c000u, 0x00ff1234u, 0x8001c000u, 0x80010f64u, 0xc0001234u, 0xedcc1234u, 0x8001fffeu, 0x0f641234u, 0x800100ffu, 0xc0001234u, 0x8001edccu, 0x8001edccu, 0x80010f64u, 0x62aa1234u}, + {0x9ec23b40u, 0x4f9d8382u, 0x9ec28000u, 0x12348382u, 0x9ec2401bu, 0x9ec225b8u, 0x80008382u, 0x12348382u, 0x9ec23b40u, 0x4f9d8382u, 0x9ec225b8u, 0x4f9d8382u, 0x9ec24f9du, 0x9ec21234u, 0x9ec24f9du, 0x25b88382u}, + {0x00020001u, 0xffff0000u, 0x0002dd91u, 0xf1350000u, 0x0002dd91u, 0x0002f71cu, 0xf71c0000u, 0xdd910000u, 0x0002f135u, 0x10b50000u, 0x00020001u, 0x10b50000u, 0x0002ffffu, 0x0002f71cu, 0x000210b5u, 0xf71c0000u}, + {0xc0004000u, 0x7fff0000u, 0xc0004000u, 0x22c30000u, 0xc0004000u, 0xc0005d87u, 0x80010000u, 0x22c30000u, 0xc0004000u, 0x5d870000u, 0xc0004000u, 0x5d870000u, 0xc0008001u, 0xc0008001u, 0xc0007fffu, 0x40000000u}, + {0xedcc2a18u, 0xfffe0002u, 0xedccfffeu, 0x29140002u, 0xedcc6bbdu, 0xedccf40fu, 0xfffe0002u, 0xc0000002u, 0xedccf40fu, 0x29140002u, 0xedcc2914u, 0x6bbd0002u, 0xedccfffeu, 0xedccf40fu, 0xedcc6bbdu, 0xc0000002u}, + {0xedcc4000u, 0x88d4fffeu, 0xedcca7f4u, 0x0001fffeu, 0xedcc00ffu, 0xedcc4000u, 0xa7f4fffeu, 0x88d4fffeu, 0xedcc0001u, 0x4000fffeu, 0xedcc00ffu, 0x00fffffeu, 0xedcc88d4u, 0xedcc88d4u, 0xedcc00ffu, 0x4000fffeu}, + {0xd81600ffu, 0xfffeb1beu, 0xd816fffeu, 0x0002b1beu, 0xd8167fffu, 0xd816fffeu, 0xfffeb1beu, 0xfffeb1beu, 0xd8160002u, 0x00ffb1beu, 0xd81600ffu, 0xfffeb1beu, 0xd816fffeu, 0xd816fffeu, 0xd8167fffu, 0x00ffb1beu} +}; +constexpr const char* Names[16] = { + "v_max3_i16 v10, v4, v5, v6 op_sel:[1,0,1,0]", + "v_max3_u16 v11, v4, v5, v6 op_sel:[0,1,0,1]", + "v_min3_i16 v12, v4, v5, v6 op_sel:[1,1,0,0]", + "v_min3_u16 v13, v4, v5, v6 op_sel:[0,0,1,1]", + "v_med3_u16 v14, v4, v5, v6 op_sel:[1,1,1,0]", + "v_med3_u16 v15, v4, v5, v6 op_sel:[0,0,0,0]", + "v_max3_u16 v16, v4, v5, v6 op_sel:[1,0,0,1]", + "v_min3_i16 v17, v4, v5, v6 op_sel:[0,1,1,1]", + "v_med3_i16 v18, v4, v5, v6 op_sel:[0,1,1,0]", + "v_max3_i16 v19, v4, v5, v6 op_sel:[0,0,0,1]", + "v_min3_u16 v20, v4, v5, v6 op_sel:[1,0,0,0]", + "v_med3_u16 v21, v4, v5, v6 op_sel:[1,1,0,1]", + "v_max3_u16 v22, v4, v5, v6 op_sel:[0,0,0,0]", + "v_min3_i16 v23, v4, v5, v6 op_sel:[0,0,0,0]", + "v_max3_i16 v24, v4, v5, v6 op_sel:[0,1,0,0]", + "v_med3_u16 v25, v4, v5, v6 op_sel:[0,0,1,1]", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop3 integer16 ternary op sel: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop3 integer16 ternary op sel tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index 5a9a8f172..f0f85c271 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -694,8 +694,14 @@ bool isNativeVop3F16TernaryOpcode(RdnaOpcode opcode) { } bool isNativeVop3I16TernaryOpcode(RdnaOpcode opcode) { - return opcode == RdnaOpcode::VMed3I16 || opcode == RdnaOpcode::VMadU16 || opcode == RdnaOpcode::VMadI16 || - opcode == RdnaOpcode::VMadU32U16 || opcode == RdnaOpcode::VMadI32I16; + return opcode == RdnaOpcode::VMed3I16 || opcode == RdnaOpcode::VMed3U16 || opcode == RdnaOpcode::VMax3I16 || + opcode == RdnaOpcode::VMax3U16 || opcode == RdnaOpcode::VMin3I16 || opcode == RdnaOpcode::VMin3U16 || + opcode == RdnaOpcode::VMadU16 || opcode == RdnaOpcode::VMadI16 || opcode == RdnaOpcode::VMadU32U16 || + opcode == RdnaOpcode::VMadI32I16; +} + +bool isNativeVop3AlignOpcode(RdnaOpcode opcode) { + return opcode == RdnaOpcode::VAlignbitB32 || opcode == RdnaOpcode::VAlignbyteB32; } bool usesSignedSaturateClamp(RdnaOpcode opcode) { @@ -1709,6 +1715,16 @@ void applyNativeVop3I16TernarySelectors(RdnaInstruction& instruction, std::uint3 } } +void applyNativeVop3AlignSelectors(RdnaInstruction& instruction, std::uint32_t opSel) { + if ((opSel & 0x3u) != 0u) { + instruction.source2.sdwaSel = opSel & 0x3u; + } + if ((opSel & 0x8u) != 0u) { + instruction.destination.sdwaSel = 5u; + instruction.destination.sdwaDstUnused = 0u; + } +} + void applyNativeVop3B16BinaryModifiers(RdnaInstruction& instruction, std::uint32_t opSel) { instruction.source0.opSel = (opSel & 0x1u) != 0u; instruction.source1.opSel = (opSel & 0x2u) != 0u; @@ -1822,6 +1838,12 @@ void checkNativeVop3Modifiers(RdnaOpcode opcode, bool permlane, bool carryInOut, } return; } + if (isNativeVop3AlignOpcode(opcode)) { + if (abs != 0u || clamp != 0u || omod != 0u || neg != 0u) { + throw std::invalid_argument("VOP3 source modifiers are not implemented"); + } + return; + } if (isNativeVop3B16BinaryOpcode(opcode)) { if (abs != 0u || clamp != 0u || omod != 0u || neg != 0u) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); @@ -2136,6 +2158,8 @@ RdnaInstruction DecodeRdnaVop3(std::uint32_t programCounter, std::span extracted = extractU64(shifted); - const IrU32 result(ir.Select(inRange.Value(), extracted[0].Value(), ir.Constant(0u))); - writeOperand(inst.destination, &result.Value()); + writeOperand(inst.destination, &extracted[0].Value()); return true; } From 978dad19b14cdcabcbba7afc8f004886c79c0573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 13:29:20 +0200 Subject: [PATCH 005/164] feat(shader): accept every SDWA selector on VOP2 integer ops Co-Authored-By: Claude --- .../tests/execution/SdwaIntegerSelectors.cpp | 189 ++++++++++++++++++ .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 54 +++-- 2 files changed, 212 insertions(+), 31 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/SdwaIntegerSelectors.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SdwaIntegerSelectors.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaIntegerSelectors.cpp new file mode 100644 index 000000000..2d9b96b79 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaIntegerSelectors.cpp @@ -0,0 +1,189 @@ +#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 +#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, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x22140af9, 0x05090604, 0x7e160307, + 0x24160af9, 0x0b041504, 0x7d8a0c80, 0x52180af9, 0x05000204, 0xd501000d, 0x01a90280, 0x7d8a0c80, + 0x541c0af9, 0x020c0c04, 0xd501000f, 0x01a90280, 0x7e200307, 0x7d8a0c80, 0x50200af9, 0x0d0b1504, + 0xd5010011, 0x01a90280, 0x12240af9, 0x060d0904, 0x7e260307, 0x28260af9, 0x06061304, 0x2c280af9, + 0x06000d04, 0x302a0af9, 0x0d060204, 0x7e2c0307, 0x342c0af9, 0x01061004, 0x362e0af9, 0x06060104, + 0x3c300af9, 0x06020b04, 0x4e320af9, 0x04080d04, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, + 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000000u, 0x00800000u, 0x000000ffu}, + {0x08f2e4e2u, 0x857c2007u, 0x01010101u, 0xebf2481cu}, + {0x8020aa77u, 0x00000001u, 0x00ff00ffu, 0x9c1571f4u}, + {0x80000000u, 0x80808080u, 0x00008000u, 0x5e028715u}, + {0x80808080u, 0x00ffffffu, 0x00ffffffu, 0x5914081cu}, + {0x007fffffu, 0xfffffffeu, 0x091bcae1u, 0xdb1f699au}, + {0x007fffffu, 0x00008000u, 0x0000007fu, 0x80000000u}, + {0x80659126u, 0x80808080u, 0x24c5a003u, 0xbab7d0d3u}, + {0x00000000u, 0x01010101u, 0x6b8644adu, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x254c883fu, 0xffff0000u}, + {0x00000000u, 0x00000000u, 0x31fb13a6u, 0x7f7f7f7fu}, + {0x00000000u, 0x00000001u, 0x00000002u, 0x80000000u}, + {0x00000000u, 0x00000001u, 0x00800000u, 0x00000080u}, + {0x00000000u, 0x00000001u, 0x00ffffffu, 0x007fffffu}, + {0x00000000u, 0x00000001u, 0xad138795u, 0x43a562a0u}, + {0x00000000u, 0x00000001u, 0xff00ff00u, 0x20046976u}, + {0x00000000u, 0x00000001u, 0xffff0000u, 0x80808080u}, + {0x00000000u, 0x00000010u, 0x00008000u, 0x00000080u}, + {0x00000000u, 0x00000010u, 0x00008000u, 0x00007fffu}, + {0x00000000u, 0x00000010u, 0x0000fff0u, 0x00000001u}, + {0x00000000u, 0x0000007fu, 0x00007fffu, 0x00007fffu}, + {0x00000000u, 0x0000007fu, 0x00008000u, 0x80000000u}, + {0x00000000u, 0x0000007fu, 0x80808080u, 0xbde9de02u}, + {0x00000000u, 0x00000080u, 0x0000fff0u, 0xf91a74d9u}, + {0x00000000u, 0x00000080u, 0xfffffffeu, 0xdd7491acu}, + {0x00000000u, 0x000000ffu, 0x00007fffu, 0xfefefefeu}, + {0x00000000u, 0x000000ffu, 0x0000fff0u, 0xf940015bu}, + {0x00000000u, 0x000000ffu, 0x00ffffffu, 0xfacb11deu}, + {0x00000000u, 0x000000ffu, 0x5901b440u, 0x0000fff0u}, + {0x00000000u, 0x000000ffu, 0xff00ff00u, 0x000000ffu}, + {0x00000000u, 0x00007fffu, 0x0964340cu, 0x931d22eau}, + {0x00000000u, 0x00007fffu, 0x14a4a43du, 0xbad19730u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000000u, 0x000000ffu, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x000100ffu, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff000000u, 0x00000000u}, + {0xffffffe4u, 0xe4e2481cu, 0x00650000u, 0x00000001u, 0x00001b99u, 0x00000001u, 0x8585481cu, 0x00000000u, 0xffff9e00u, 0x07f2481cu, 0x08010000u, 0x005f0000u, 0xebf24880u, 0x00000200u, 0x0a000000u, 0x20250000u}, + {0xffffffaau, 0xaa7771f4u, 0x00760000u, 0x00000000u, 0x00005588u, 0x00000001u, 0xff8171f4u, 0x00000000u, 0x00002000u, 0x771571f4u, 0x00000000u, 0x00000000u, 0x9c157100u, 0x00000100u, 0xde000000u, 0xff8a0000u}, + {0x00000000u, 0x00008715u, 0x007f0000u, 0x00000001u, 0x0000007fu, 0x00000000u, 0x80018715u, 0x00000001u, 0x00000000u, 0x80028715u, 0x80800000u, 0x00800000u, 0x5e028780u, 0x00000000u, 0x7f000000u, 0x80800000u}, + {0xffffff80u, 0x8080081cu, 0x00800000u, 0x00000001u, 0xffff807eu, 0x00000001u, 0x0080081cu, 0x00000001u, 0xffff8000u, 0x8014081cu, 0xffff0000u, 0x00ff0000u, 0x591408ffu, 0x00008000u, 0x80000000u, 0x007f0000u}, + {0xffffffffu, 0xffff699au, 0x00ff0000u, 0x00000001u, 0x000000ffu, 0x00000001u, 0x0000699au, 0x00000001u, 0x00000200u, 0xfe1f699au, 0x00010000u, 0x00ff0000u, 0xdb1f6900u, 0x0000fe00u, 0x7e000000u, 0xffff0000u}, + {0xffffffffu, 0xffff0000u, 0x00fe0000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00010000u, 0x00000000u, 0x00000000u, 0xff000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x80010000u}, + {0xffffff91u, 0x9126d0d3u, 0x00a50000u, 0x00000001u, 0x00006f59u, 0x00000001u, 0x8001d0d3u, 0x00000001u, 0xffff8000u, 0x80b7d0d3u, 0x02020000u, 0x00020000u, 0xbab7d000u, 0x00000000u, 0x1a000000u, 0x805a0000u}, + {0x00000000u, 0x00010000u, 0x00fe0000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x01020000u, 0x00000000u, 0x00000000u, 0x01000000u, 0x01010000u, 0x00010000u, 0x00000001u, 0x00000000u, 0xfe000000u, 0x01010000u}, + {0x00000000u, 0x00000000u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00010000u, 0x00000000u, 0x00000000u, 0x00ff0000u, 0x00000000u, 0x00000000u, 0xffff0000u, 0x00000000u, 0xff000000u, 0x00000000u}, + {0x00000000u, 0x00007f7fu, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00017f7fu, 0x00000000u, 0x00000000u, 0x007f7f7fu, 0x00000000u, 0x00000000u, 0x7f7f7f00u, 0x00000000u, 0xff000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00010000u, 0x00000000u, 0x00000000u, 0x01000000u, 0x00010000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xfe000000u, 0x00010000u}, + {0x00000000u, 0x00000080u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00010080u, 0x00000000u, 0x00000000u, 0x01000080u, 0x00010000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfe000000u, 0x00010000u}, + {0x00000000u, 0x0000ffffu, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x0001ffffu, 0x00000000u, 0x00000000u, 0x017fffffu, 0x00010000u, 0x00000000u, 0x007fff00u, 0x00000000u, 0xfe000000u, 0x00010000u}, + {0x00000000u, 0x000062a0u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x000162a0u, 0x00000000u, 0x00000000u, 0x01a562a0u, 0x00010000u, 0x00000000u, 0x43a56200u, 0x00000000u, 0xfe000000u, 0x00010000u}, + {0x00000000u, 0x00006976u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00016976u, 0x00000000u, 0x00000000u, 0x01046976u, 0x00010000u, 0x00000000u, 0x20046900u, 0x00000000u, 0xfe000000u, 0x00010000u}, + {0x00000000u, 0x00008080u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00018080u, 0x00000000u, 0x00000000u, 0x01808080u, 0x00010000u, 0x00000000u, 0x80808000u, 0x00000000u, 0xfe000000u, 0x00010000u}, + {0x00000000u, 0x00000080u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00010080u, 0x00000000u, 0x00000000u, 0x10000080u, 0x00100000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xef000000u, 0x00100000u}, + {0x00000000u, 0x00007fffu, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00017fffu, 0x00000000u, 0x00000000u, 0x10007fffu, 0x00100000u, 0x00000000u, 0x00007f00u, 0x00000000u, 0xef000000u, 0x00100000u}, + {0x00000000u, 0x00000001u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00010001u, 0x00000000u, 0x00000000u, 0x10000001u, 0x00100000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xef000000u, 0x00100000u}, + {0x00000000u, 0x00007fffu, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00017fffu, 0x00000000u, 0x00000000u, 0x7f007fffu, 0x007f0000u, 0x00000000u, 0x00007f00u, 0x00000000u, 0x80000000u, 0x007f0000u}, + {0x00000000u, 0x00000000u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00010000u, 0x00000000u, 0x00000000u, 0x7f000000u, 0x007f0000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x007f0000u}, + {0x00000000u, 0x0000de02u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x0001de02u, 0x00000000u, 0x00000000u, 0x7fe9de02u, 0x007f0000u, 0x00000000u, 0xbde9de00u, 0x00000000u, 0x80000000u, 0x007f0000u}, + {0x00000000u, 0x000074d9u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x000174d9u, 0x00000000u, 0x00000000u, 0x801a74d9u, 0x00800000u, 0x00000000u, 0xf91a7400u, 0x00000000u, 0x7f000000u, 0x00800000u}, + {0x00000000u, 0x000091acu, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x000191acu, 0x00000000u, 0x00000000u, 0x807491acu, 0x00800000u, 0x00000000u, 0xdd749100u, 0x00000000u, 0x7f000000u, 0x00800000u}, + {0x00000000u, 0x0000fefeu, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x0001fefeu, 0x00000000u, 0x00000000u, 0xfffefefeu, 0x00ff0000u, 0x00000000u, 0xfefefe00u, 0x00000000u, 0x00000000u, 0x00ff0000u}, + {0x00000000u, 0x0000015bu, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x0001015bu, 0x00000000u, 0x00000000u, 0xff40015bu, 0x00ff0000u, 0x00000000u, 0xf9400100u, 0x00000000u, 0x00000000u, 0x00ff0000u}, + {0x00000000u, 0x000011deu, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x000111deu, 0x00000000u, 0x00000000u, 0xffcb11deu, 0x00ff0000u, 0x00000000u, 0xfacb1100u, 0x00000000u, 0x00000000u, 0x00ff0000u}, + {0x00000000u, 0x0000fff0u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x0001fff0u, 0x00000000u, 0x00000000u, 0xff00fff0u, 0x00ff0000u, 0x00000000u, 0x0000ff00u, 0x00000000u, 0x00000000u, 0x00ff0000u}, + {0x00000000u, 0x000000ffu, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x000100ffu, 0x00000000u, 0x00000000u, 0xff0000ffu, 0x00ff0000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00ff0000u}, + {0x00000000u, 0x000022eau, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x000122eau, 0x00000000u, 0x00000000u, 0xff1d22eau, 0x7fff0000u, 0x00000000u, 0x931d227fu, 0x00000000u, 0x00000000u, 0x7fff0000u}, + {0x00000000u, 0x00009730u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00019730u, 0x00000000u, 0x00000000u, 0xffd19730u, 0x7fff0000u, 0x00000000u, 0xbad1977fu, 0x00000000u, 0x00000000u, 0x7fff0000u} +}; +constexpr const char* Names[16] = { + "min_i32", + "max_i32", + "sub_co_ci_u32", + "sub_co_ci_u32 carry", + "subrev_co_ci_u32", + "subrev_co_ci_u32 carry", + "add_co_ci_u32", + "add_co_ci_u32 carry", + "mul_i32_i24", + "max_u32", + "lshrrev_b32", + "ashrrev_i32", + "lshlrev_b32", + "and_b32", + "xnor_b32", + "subrev_nc_u32", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("sdwa integer selectors: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("sdwa integer selectors tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index 5a9a8f172..1d2517240 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -26,11 +26,7 @@ enum class Vop2SdwaProfile { Float32, Float16, PackedFloat16, - IntegerFullDestination, - IntegerPartialDestination, - ReverseRightShift, - ReverseLogicalLeft, - Bitwise, + Integer, }; struct Vop2OpcodeInfo { @@ -46,45 +42,45 @@ struct VopcOpcodeInfo { }; constexpr Vop2OpcodeInfo vop2Opcodes[] = { - {0x01u, RdnaOpcode::VCndmaskB32, Vop2SdwaProfile::IntegerPartialDestination}, + {0x01u, RdnaOpcode::VCndmaskB32, Vop2SdwaProfile::Integer}, {0x02u, RdnaOpcode::VDot2cF32F16}, {0x03u, RdnaOpcode::VAddF32, Vop2SdwaProfile::Float32}, {0x04u, RdnaOpcode::VSubF32, Vop2SdwaProfile::Float32}, {0x05u, RdnaOpcode::VSubrevF32}, {0x08u, RdnaOpcode::VMulF32, Vop2SdwaProfile::Float32}, - {0x09u, RdnaOpcode::VMulI32I24, Vop2SdwaProfile::IntegerFullDestination}, - {0x0au, RdnaOpcode::VMulHiI32I24, Vop2SdwaProfile::IntegerFullDestination}, - {0x0bu, RdnaOpcode::VMulU32U24, Vop2SdwaProfile::IntegerFullDestination}, - {0x0cu, RdnaOpcode::VMulHiU32U24, Vop2SdwaProfile::IntegerFullDestination}, + {0x09u, RdnaOpcode::VMulI32I24, Vop2SdwaProfile::Integer}, + {0x0au, RdnaOpcode::VMulHiI32I24, Vop2SdwaProfile::Integer}, + {0x0bu, RdnaOpcode::VMulU32U24, Vop2SdwaProfile::Integer}, + {0x0cu, RdnaOpcode::VMulHiU32U24, Vop2SdwaProfile::Integer}, {0x0du, RdnaOpcode::VDot4cI32I8}, {0x0fu, RdnaOpcode::VMinF32}, {0x10u, RdnaOpcode::VMaxF32}, - {0x11u, RdnaOpcode::VMinI32}, - {0x12u, RdnaOpcode::VMaxI32}, - {0x13u, RdnaOpcode::VMinU32, Vop2SdwaProfile::IntegerPartialDestination}, - {0x14u, RdnaOpcode::VMaxU32, Vop2SdwaProfile::IntegerFullDestination}, + {0x11u, RdnaOpcode::VMinI32, Vop2SdwaProfile::Integer}, + {0x12u, RdnaOpcode::VMaxI32, Vop2SdwaProfile::Integer}, + {0x13u, RdnaOpcode::VMinU32, Vop2SdwaProfile::Integer}, + {0x14u, RdnaOpcode::VMaxU32, Vop2SdwaProfile::Integer}, {0x15u, RdnaOpcode::VLshrB32}, - {0x16u, RdnaOpcode::VLshrrevB32, Vop2SdwaProfile::ReverseRightShift}, + {0x16u, RdnaOpcode::VLshrrevB32, Vop2SdwaProfile::Integer}, {0x17u, RdnaOpcode::VAshrI32}, - {0x18u, RdnaOpcode::VAshrrevI32, Vop2SdwaProfile::ReverseRightShift}, + {0x18u, RdnaOpcode::VAshrrevI32, Vop2SdwaProfile::Integer}, {0x19u, RdnaOpcode::VLshlB32}, - {0x1au, RdnaOpcode::VLshlrevB32, Vop2SdwaProfile::ReverseLogicalLeft}, - {0x1bu, RdnaOpcode::VAndB32, Vop2SdwaProfile::Bitwise}, - {0x1cu, RdnaOpcode::VOrB32, Vop2SdwaProfile::Bitwise}, - {0x1du, RdnaOpcode::VXorB32, Vop2SdwaProfile::Bitwise}, - {0x1eu, RdnaOpcode::VXnorB32, Vop2SdwaProfile::Bitwise}, + {0x1au, RdnaOpcode::VLshlrevB32, Vop2SdwaProfile::Integer}, + {0x1bu, RdnaOpcode::VAndB32, Vop2SdwaProfile::Integer}, + {0x1cu, RdnaOpcode::VOrB32, Vop2SdwaProfile::Integer}, + {0x1du, RdnaOpcode::VXorB32, Vop2SdwaProfile::Integer}, + {0x1eu, RdnaOpcode::VXnorB32, Vop2SdwaProfile::Integer}, {0x1fu, RdnaOpcode::VMacF32}, {0x20u, RdnaOpcode::VMadmkF32}, {0x21u, RdnaOpcode::VMadakF32}, {0x22u, RdnaOpcode::VBcntU32B32}, {0x23u, RdnaOpcode::VMbcntLoU32B32}, {0x24u, RdnaOpcode::VMbcntHiU32B32}, - {0x25u, RdnaOpcode::VAddNcU32, Vop2SdwaProfile::IntegerPartialDestination}, - {0x28u, RdnaOpcode::VAddcU32, Vop2SdwaProfile::IntegerFullDestination}, - {0x29u, RdnaOpcode::VSubCoCiU32}, - {0x2au, RdnaOpcode::VSubrevCoCiU32}, - {0x26u, RdnaOpcode::VSubNcU32, Vop2SdwaProfile::IntegerPartialDestination}, - {0x27u, RdnaOpcode::VSubrevNcU32, Vop2SdwaProfile::IntegerFullDestination}, + {0x25u, RdnaOpcode::VAddNcU32, Vop2SdwaProfile::Integer}, + {0x28u, RdnaOpcode::VAddcU32, Vop2SdwaProfile::Integer}, + {0x29u, RdnaOpcode::VSubCoCiU32, Vop2SdwaProfile::Integer}, + {0x2au, RdnaOpcode::VSubrevCoCiU32, Vop2SdwaProfile::Integer}, + {0x26u, RdnaOpcode::VSubNcU32, Vop2SdwaProfile::Integer}, + {0x27u, RdnaOpcode::VSubrevNcU32, Vop2SdwaProfile::Integer}, {0x2bu, RdnaOpcode::VMacF32}, {0x2cu, RdnaOpcode::VMadmkF32}, {0x2du, RdnaOpcode::VMadakF32}, @@ -1218,11 +1214,7 @@ constexpr Vop2SdwaRule vop2SdwaRules[] = { {sdwaSelFull(), sdwaSelFull(), sdwaSelFull(), false, true}, {sdwaSelWords() | sdwaSelFull(), sdwaSelWords() | sdwaSelFull(), sdwaSelWords() | sdwaSelFull(), true, true}, {sdwaSelAll(), sdwaSelAll(), sdwaSelAll(), true, true}, - {sdwaSelFull(), sdwaSelAll(), sdwaSelAll(), false, false}, {sdwaSelAll(), sdwaSelAll(), sdwaSelAll(), true, false}, - {sdwaSelFull(), sdwaSelAll(), sdwaSelAll(), false, false}, - {sdwaSelFull(), sdwaSelAll(), sdwaSelAll(), false, false}, - {sdwaSelWords() | sdwaSelFull(), sdwaSelAll(), sdwaSelAll(), true, false}, }; const Vop2SdwaRule* findVop2SdwaRule(std::uint32_t encoding) { From c5effd409e017a55f1c9e6f6628754d90e511365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 13:36:35 +0200 Subject: [PATCH 006/164] feat(shader): saturate VOP2 integer SDWA results on clamp Co-Authored-By: Claude --- .../tests/execution/SdwaIntegerClamp.cpp | 188 ++++++++++++++++++ .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 8 +- .../Translation/TranslationContext.hpp | 1 + .../src/ControlFlowInstructions.cpp | 20 +- .../Translation/src/IntegerInstructions.cpp | 6 + .../Translation/src/VectorInstructions.cpp | 6 +- 6 files changed, 222 insertions(+), 7 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/SdwaIntegerClamp.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SdwaIntegerClamp.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaIntegerClamp.cpp new file mode 100644 index 000000000..3174ec093 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaIntegerClamp.cpp @@ -0,0 +1,188 @@ +#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 +#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, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x4a140af9, 0x06012604, 0x7e160307, + 0x4c160af9, 0x030c3504, 0x4e180af9, 0x0a062804, 0x7d8a0c80, 0x501a0af9, 0x05062404, 0xd501000e, + 0x01a90280, 0x7e1e0307, 0x7d8a0c80, 0x521e0af9, 0x06023304, 0xd5010010, 0x01a90280, 0x7d8a0c80, + 0x54220af9, 0x04082604, 0xd5010012, 0x01a90280, 0x12260af9, 0x0d062d04, 0x16280af9, 0x06062604, + 0x142a0af9, 0x06062604, 0x262c0af9, 0x01062604, 0x7e2e0307, 0x302e0af9, 0x06003404, 0x3c300af9, + 0x06062604, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, + 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000000u, 0x00010000u, 0x51bb9a4eu}, + {0x007fffffu, 0xfd503aeeu, 0x00000000u, 0x00008000u}, + {0x00000001u, 0x219db754u, 0x00000000u, 0xfdf13c3fu}, + {0x00008000u, 0xfffffffeu, 0x000000ffu, 0x7f7f7f7fu}, + {0x00010000u, 0x80000001u, 0x00007fffu, 0x00008000u}, + {0xfffffffeu, 0x00000000u, 0x00008000u, 0x7fffffffu}, + {0x00000000u, 0x00000002u, 0x00000000u, 0x00008000u}, + {0x00000000u, 0x00010000u, 0x00000000u, 0xe5224648u}, + {0x00010000u, 0x00000000u, 0x00007fffu, 0x80000000u}, + {0x0013fd86u, 0x08ce4bd3u, 0xa671bed2u, 0xffff0000u}, + {0x00ffffffu, 0x00000002u, 0x00000000u, 0xa8d118f0u}, + {0xffff0000u, 0xffff0000u, 0x007fffffu, 0x8bc63fd0u}, + {0xfffffffeu, 0x00ff00ffu, 0x00000000u, 0x00000002u}, + {0x00000000u, 0x00000000u, 0x7f7f7f7fu, 0x00ff00ffu}, + {0x00000000u, 0x00000000u, 0x93552cc2u, 0xffffffffu}, + {0x00000000u, 0x00000000u, 0xccaf1cddu, 0xf37566c1u}, + {0x00000000u, 0x00000000u, 0xffffffffu, 0x0000ffffu}, + {0x00000000u, 0x00000000u, 0xffffffffu, 0x12345678u}, + {0x00000000u, 0x00000001u, 0x00ffffffu, 0x33869405u}, + {0x00000000u, 0x00000001u, 0xff00ff00u, 0x202be01bu}, + {0x00000000u, 0x00000002u, 0x4c31a5c3u, 0x5534d12cu}, + {0x00000000u, 0x00000002u, 0x7ba28771u, 0x007fffffu}, + {0x00000000u, 0x00000002u, 0x7fffffffu, 0xb8cf0c88u}, + {0x00000000u, 0x00000002u, 0xf6340ef5u, 0xcf296973u}, + {0x00000000u, 0x00000002u, 0xffff0000u, 0xffffffffu}, + {0x00000000u, 0x0000007fu, 0x0000ffffu, 0x8d2cc93bu}, + {0x00000000u, 0x0000007fu, 0x121b4baau, 0x00000002u}, + {0x00000000u, 0x0000007fu, 0x12345678u, 0x00ff00ffu}, + {0x00000000u, 0x0000007fu, 0x7f7f7f7fu, 0x000000ffu}, + {0x00000000u, 0x00000080u, 0x00000001u, 0x00010000u}, + {0x00000000u, 0x00000080u, 0x0000ffffu, 0x00ff00ffu}, + {0x00000000u, 0x00000080u, 0x0000ffffu, 0x80000000u} +}; +constexpr std::uint32_t Expected[32][15] = { + {0x00000000u, 0x00009a4eu, 0x00000000u, 0x00000001u, 0x00000000u, 0x00bb9a4eu, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x51bb0000u, 0xffffffffu}, + {0xfd503bedu, 0xff028000u, 0x00000000u, 0x0000fd4fu, 0x00000000u, 0x00008000u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0xffffffffu, 0x0000281du, 0x0000003au, 0x0000ffffu, 0x02d03aeeu}, + {0x219db754u, 0x00003c3fu, 0xffffff9cu, 0x0000219eu, 0x00000000u, 0x00f13c3fu, 0x00000001u, 0x0000b753u, 0x00000000u, 0x219d0000u, 0x009db754u, 0xffffffffu, 0x00000001u, 0xfdf1dbaau, 0xde6248aau}, + {0xffffffffu, 0x7f017f7fu, 0xffffffffu, 0x00008000u, 0x00000000u, 0x007f7f7fu, 0x00000001u, 0x0000fffdu, 0x00000000u, 0x80000000u, 0xffffffffu, 0xffffffffu, 0x000000ffu, 0x7f7ffffeu, 0x00008001u}, + {0x80000001u, 0x00008000u, 0x00000000u, 0x00008001u, 0x00000000u, 0x00008000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00010000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x7ffefffeu}, + {0x000000ffu, 0xfffeffffu, 0x00000000u, 0x0000ffffu, 0x00000000u, 0xfeffffffu, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fff0000u, 0x00000001u}, + {0x00000002u, 0x00008000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00008000u, 0x00000001u, 0x00000002u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000002u, 0xfffffffdu}, + {0x00010000u, 0x00004648u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00224648u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xe5220000u, 0xfffeffffu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0xfffeffffu}, + {0x08ce4cd0u, 0xfd7e0000u, 0x00000048u, 0x00000655u, 0x00000000u, 0x00ff0000u, 0x00000001u, 0x00000000u, 0x00000001u, 0xffff0000u, 0xffffffffu, 0xfffffc1eu, 0x0000004bu, 0xffff392fu, 0xf72249aau}, + {0x00000101u, 0xffff18f0u, 0x00000000u, 0x0000ffffu, 0x00000000u, 0xfdd118f0u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x01fffffeu, 0xffffffffu, 0x00000000u, 0xa8d10000u, 0xff000002u}, + {0xffff0000u, 0x00003fd0u, 0xffffffffu, 0x0000ffffu, 0x00000001u, 0x00c63fd0u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0xffffffffu, 0x00000001u, 0x00000000u, 0x8bc60000u, 0xffffffffu}, + {0x00ff01feu, 0xfffe0002u, 0x00000001u, 0x0000ffffu, 0x00000001u, 0x00000002u, 0x00000001u, 0x00000000u, 0x00000001u, 0xfe020000u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00ff00feu}, + {0x00000000u, 0x000000ffu, 0x00000000u, 0x00000001u, 0x00000000u, 0x00ff00ffu, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00ff0000u, 0xffffffffu}, + {0x00000000u, 0x0000ffffu, 0x00000000u, 0x00000001u, 0x00000000u, 0x00ffffffu, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffff0000u, 0xffffffffu}, + {0x00000000u, 0x000066c1u, 0x00000000u, 0x00000001u, 0x00000000u, 0x007566c1u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xf3750000u, 0xffffffffu}, + {0x00000000u, 0x0000ffffu, 0x00000000u, 0x00000001u, 0x00000000u, 0x0000ffffu, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu}, + {0x00000000u, 0x00005678u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00345678u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x12340000u, 0xffffffffu}, + {0x00000001u, 0x00009405u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00869405u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x33860001u, 0xfffffffeu}, + {0x00000001u, 0x0000e01bu, 0x00000000u, 0x00000001u, 0x00000000u, 0x002be01bu, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x202b0001u, 0xfffffffeu}, + {0x00000002u, 0x0000d12cu, 0x00000000u, 0x00000001u, 0x00000000u, 0x0034d12cu, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x55340002u, 0xfffffffdu}, + {0x00000002u, 0x0000ffffu, 0x00000000u, 0x00000001u, 0x00000000u, 0x007fffffu, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x007f0002u, 0xfffffffdu}, + {0x00000002u, 0x00000c88u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00cf0c88u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xb8cf0002u, 0xfffffffdu}, + {0x00000002u, 0x00006973u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00296973u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xcf290002u, 0xfffffffdu}, + {0x00000002u, 0x0000ffffu, 0x00000000u, 0x00000001u, 0x00000000u, 0x00ffffffu, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffff0002u, 0xfffffffdu}, + {0x0000007fu, 0x0000c93bu, 0x00000000u, 0x00000001u, 0x00000000u, 0x002cc93bu, 0x00000001u, 0x0000007eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x8d2c007fu, 0xffffff80u}, + {0x0000007fu, 0x00000002u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000002u, 0x00000001u, 0x0000007eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000007fu, 0xffffff80u}, + {0x0000007fu, 0x000000ffu, 0x00000000u, 0x00000001u, 0x00000000u, 0x00ff00ffu, 0x00000001u, 0x0000007eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00ff007fu, 0xffffff80u}, + {0x0000007fu, 0x000000ffu, 0x00000000u, 0x00000001u, 0x00000000u, 0x000000ffu, 0x00000001u, 0x0000007eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000007fu, 0xffffff80u}, + {0x00000080u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00010000u, 0x00000001u, 0x0000007fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00010080u, 0xffffff7fu}, + {0x00000080u, 0x000000ffu, 0x00000000u, 0x00000001u, 0x00000000u, 0x00ff00ffu, 0x00000001u, 0x0000007fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00ff0080u, 0xffffff7fu}, + {0x00000080u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000001u, 0x0000007fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000080u, 0xffffff7fu} +}; +constexpr const char* Names[15] = { + "add_nc_u32", + "sub_nc_u32", + "subrev_nc_u32", + "add_co_ci_u32", + "add_co_ci_u32 carry", + "sub_co_ci_u32", + "sub_co_ci_u32 carry", + "subrev_co_ci_u32", + "subrev_co_ci_u32 carry", + "mul_i32_i24", + "mul_u32_u24", + "mul_hi_i32_i24", + "min_u32", + "ashrrev_i32", + "xnor_b32", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("sdwa integer clamp: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 15; ++i) { + Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("sdwa integer clamp tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index 1d2517240..301839d57 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -1225,6 +1225,11 @@ const Vop2SdwaRule* findVop2SdwaRule(std::uint32_t encoding) { return &vop2SdwaRules[static_cast(info->sdwaProfile)]; } +bool supportsVop2SdwaIntegerClamp(RdnaOpcode op, std::uint32_t encoding) { + const auto* info = findVectorOpcodeEntry(vop2Opcodes, encoding); + return info != nullptr && info->sdwaProfile == Vop2SdwaProfile::Integer && op != RdnaOpcode::VCndmaskB32; +} + bool isVop2SdwaDestinationSupported(const Vop2SdwaRule& rule, const Vop2SdwaFields& fields) { if (!hasSdwaSelector(rule.dstSelectors, fields.dstSel)) { return false; @@ -1243,7 +1248,8 @@ void validateVop2Sdwa(const RdnaInstruction& instruction, std::uint32_t opcode, if (fields.src0Sel > 6u || fields.src1Sel > 6u || fields.dstSel > 6u) { throw std::invalid_argument("VOP2 SDWA selector is invalid"); } - if ((fields.clamp != 0u || fields.omod != 0u) && !isVop2FloatOpcode(instruction.op)) { + const bool clampSupported = isVop2FloatOpcode(instruction.op) || supportsVop2SdwaIntegerClamp(instruction.op, opcode); + if ((fields.clamp != 0u && !clampSupported) || (fields.omod != 0u && !isVop2FloatOpcode(instruction.op))) { throw std::invalid_argument("VOP2 SDWA output modifiers are not supported"); } if (isFullWidthVop2Sdwa(fields)) { diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index 4be3e2626..b253182df 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -234,6 +234,7 @@ private: void addU32(const RdnaInstruction& inst, bool vector, bool useCarryIn); void subU32(const RdnaInstruction& inst, bool vector, bool reverse); void subbU32(const RdnaInstruction& inst, bool vector, bool reverse); + bool vAddSubNcU32(const RdnaInstruction& inst, bool subtract, bool reverse); void sAbsdiffI32(const RdnaInstruction& inst); void sAddSubI32(const RdnaInstruction& inst, bool subtract); void sLshlAddU32(const RdnaInstruction& inst, std::uint32_t shiftAmount); diff --git a/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp b/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp index 48fedcf5c..2f12f996a 100644 --- a/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp +++ b/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp @@ -72,8 +72,8 @@ void TranslationContext::addU32(const RdnaInstruction& inst, bool vector, bool u const IrU32 sum(ir.Emit(IrOpcode::CompositeExtractU32x2, IrType::U32, {&firstAdd, &ir.Constant(0u)})); const IrU32 firstCarry(ir.Emit(IrOpcode::CompositeExtractU32x2, IrType::U32, {&firstAdd, &ir.Constant(1u)})); if (!useCarryIn) { - writeRawU32(inst.destination, sum); const IrU1 carryOut(ir.INotEqual(firstCarry.Value(), ir.Constant(0u))); + writeRawU32(inst.destination, inst.destination.clamp ? IrU32(ir.Select(carryOut.Value(), ir.Constant(0xffffffffu), sum.Value())) : sum); if (vector) { writeMask(inst.destination2, carryOut); return; @@ -87,7 +87,7 @@ void TranslationContext::addU32(const RdnaInstruction& inst, bool vector, bool u const IrU32 result(ir.Emit(IrOpcode::CompositeExtractU32x2, IrType::U32, {&secondAdd, &ir.Constant(0u)})); const IrU32 secondCarry(ir.Emit(IrOpcode::CompositeExtractU32x2, IrType::U32, {&secondAdd, &ir.Constant(1u)})); const IrU1 carryOut(ir.LogicalOr(ir.INotEqual(firstCarry.Value(), ir.Constant(0u)), ir.INotEqual(secondCarry.Value(), ir.Constant(0u)))); - writeRawU32(inst.destination, result); + writeRawU32(inst.destination, inst.destination.clamp ? IrU32(ir.Select(carryOut.Value(), ir.Constant(0xffffffffu), result.Value())) : result); if (vector) { writeMask(inst.destination2, carryOut); return; @@ -122,7 +122,7 @@ void TranslationContext::subbU32(const RdnaInstruction& inst, bool vector, bool const IrU32 result(ir.ISub(partial.Value(), borrowInU32.Value())); const IrU1 secondBorrow(ir.ULessThan(partial.Value(), borrowInU32.Value())); const IrU1 borrowOut(ir.LogicalOr(firstBorrow.Value(), secondBorrow.Value())); - writeRawU32(inst.destination, result); + writeRawU32(inst.destination, inst.destination.clamp ? IrU32(ir.Select(borrowOut.Value(), ir.Constant(0u), result.Value())) : result); if (vector) { writeMask(inst.destination2, borrowOut); return; @@ -130,6 +130,20 @@ void TranslationContext::subbU32(const RdnaInstruction& inst, bool vector, bool ir.SetScc(borrowOut.Value()); } +bool TranslationContext::vAddSubNcU32(const RdnaInstruction& inst, bool subtract, bool reverse) { + const IrU32 first = readU32(sourceAt(inst, 0u)); + const IrU32 second = readU32(sourceAt(inst, 1u)); + const IrU32& lhs = reverse ? second : first; + const IrU32& rhs = reverse ? first : second; + IrU32 result(subtract ? ir.ISub(lhs.Value(), rhs.Value()) : ir.IAdd(lhs.Value(), rhs.Value())); + if (inst.destination.clamp) { + const IrU1 carry(subtract ? ir.ULessThan(lhs.Value(), rhs.Value()) : ir.ULessThan(result.Value(), lhs.Value())); + result = IrU32(ir.Select(carry.Value(), ir.Constant(subtract ? 0u : 0xffffffffu), result.Value())); + } + writeOperand(inst.destination, &result.Value()); + return true; +} + void TranslationContext::sAbsdiffI32(const RdnaInstruction& inst) { const IrU32 lhs = readU32(sourceAt(inst, 0u)); const IrU32 rhs = readU32(sourceAt(inst, 1u)); diff --git a/core/shader/recompiler/Translation/src/IntegerInstructions.cpp b/core/shader/recompiler/Translation/src/IntegerInstructions.cpp index 7e895e5f3..82a35748f 100644 --- a/core/shader/recompiler/Translation/src/IntegerInstructions.cpp +++ b/core/shader/recompiler/Translation/src/IntegerInstructions.cpp @@ -305,6 +305,12 @@ bool TranslationContext::integer24(const RdnaInstruction& inst, bool sign, bool const IrU32 rhs(ir.Emit(extractOpcode, IrType::U32, {&rhsSource.Value(), &ir.Constant(0u), &ir.Constant(24u)})); const IrOpcode multiplyOpcode = high ? (sign ? IrOpcode::SMulHi : IrOpcode::UMulHi) : IrOpcode::IMul32; IrU32 result(ir.Emit(multiplyOpcode, IrType::U32, {&lhs.Value(), &rhs.Value()})); + if (inst.destination.clamp && !high && !addend) { + const IrU32 top(ir.Emit(sign ? IrOpcode::SMulHi : IrOpcode::UMulHi, IrType::U32, {&lhs.Value(), &rhs.Value()})); + IrValue& expected = sign ? ir.ShiftRightArithmetic(result.Value(), ir.Constant(31u)) : ir.Constant(0u); + IrValue& saturated = sign ? ir.BitwiseXor(ir.ShiftRightArithmetic(top.Value(), ir.Constant(31u)), ir.Constant(0x7fffffffu)) : ir.Constant(0xffffffffu); + result = IrU32(ir.Select(ir.INotEqual(top.Value(), expected), saturated, result.Value())); + } if (addend) { result = IrU32(ir.IAdd(result.Value(), readU32(sourceAt(inst, 2u)).Value())); } diff --git a/core/shader/recompiler/Translation/src/VectorInstructions.cpp b/core/shader/recompiler/Translation/src/VectorInstructions.cpp index 2782618ee..8a3c1687c 100644 --- a/core/shader/recompiler/Translation/src/VectorInstructions.cpp +++ b/core/shader/recompiler/Translation/src/VectorInstructions.cpp @@ -972,11 +972,11 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { case RdnaOpcode::VMulHiI32: return simpleInteger(inst, IrOpcode::SMulHi, IrType::U32, false, false, false); case RdnaOpcode::VAddNcU32: - return simpleInteger(inst, IrOpcode::IAdd32, IrType::U32, false, false, false); + return vAddSubNcU32(inst, false, false); case RdnaOpcode::VSubNcU32: - return simpleInteger(inst, IrOpcode::ISub32, IrType::U32, false, false, false); + return vAddSubNcU32(inst, true, false); case RdnaOpcode::VSubrevNcU32: - return simpleInteger(inst, IrOpcode::ISub32, IrType::U32, true, false, false); + return vAddSubNcU32(inst, true, true); case RdnaOpcode::VMinI32: return simpleInteger(inst, IrOpcode::SMin32, IrType::U32, false, false, false); case RdnaOpcode::VMaxI32: From f64a683bdf7d40918287185aeff1c20561b7124c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 16:56:48 +0200 Subject: [PATCH 007/164] fix(shader): s_wqm SCC and s_andn{1,2}_wrexec destination Co-Authored-By: Claude --- .../tests/execution/ScalarSop1.cpp | 274 ++++++++++++++++++ .../Translation/TranslationContext.hpp | 2 +- .../src/ControlFlowInstructions.cpp | 18 +- .../Translation/src/ScalarInstructions.cpp | 8 +- 4 files changed, 291 insertions(+), 11 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/ScalarSop1.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSop1.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSop1.cpp new file mode 100644 index 000000000..c26c22609 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSop1.cpp @@ -0,0 +1,274 @@ +#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 +#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 = 176; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x160600ff, 0x000002c0, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xbe940380, 0xd7600008, + 0x02002904, 0xd7600009, 0x02002905, 0xd760000a, 0x02002906, 0xd760000b, 0x02002907, 0xbe9e0380, + 0xbf060b0b, 0xbe8c0408, 0x821e1e1e, 0xd761000a, 0x0200280c, 0xd761000b, 0x0200280d, 0xbe8c040a, + 0xbe8e0408, 0x871f810b, 0xbe8c0508, 0x821e1e1e, 0xd761000c, 0x0200280c, 0xbe8c040a, 0xbe8e0408, + 0x871f810b, 0xbe8c0608, 0x821e1e1e, 0xd761000d, 0x0200280c, 0xd761000e, 0x0200280d, 0xbe8c0708, + 0x821e1e1e, 0xd761000f, 0x0200280c, 0xbe8c0808, 0x821e1e1e, 0xd7610010, 0x0200280c, 0xd7610011, + 0x0200280d, 0xbe8c0908, 0x821e1e1e, 0xd7610012, 0x0200280c, 0xbe8c0a08, 0x821e1e1e, 0xd7610013, + 0x0200280c, 0xd7610014, 0x0200280d, 0xbf060b0b, 0xbe8c0b08, 0x821e1e1e, 0xd7610015, 0x0200280c, + 0xbf060b0b, 0xbe8c0c08, 0x821e1e1e, 0xd7610016, 0x0200280c, 0xd7610017, 0x0200280d, 0xbe8c0d08, + 0x821e1e1e, 0xd7610018, 0x0200280c, 0xd7610019, 0x0200281e, 0x80148114, 0xbf0aa014, 0xbf85ffb7, + 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, + 0xbe940380, 0xd7600008, 0x02002904, 0xd7600009, 0x02002905, 0xd760000a, 0x02002906, 0xd760000b, + 0x02002907, 0xbe9e0380, 0xbe8c0e08, 0x821e1e1e, 0xd761000a, 0x0200280c, 0xbe8c0f08, 0x821e1e1e, + 0xd761000b, 0x0200280c, 0xbe8c1008, 0x821e1e1e, 0xd761000c, 0x0200280c, 0xbf060b0b, 0xbe8c1108, + 0x821e1e1e, 0xd761000d, 0x0200280c, 0xbf060b0b, 0xbe8c1208, 0x821e1e1e, 0xd761000e, 0x0200280c, + 0xbf060b0b, 0xbe8c1308, 0x821e1e1e, 0xd761000f, 0x0200280c, 0xbf060b0b, 0xbe8c1408, 0x821e1e1e, + 0xd7610010, 0x0200280c, 0xbf060b0b, 0xbe8c1508, 0x821e1e1e, 0xd7610011, 0x0200280c, 0xbf060b0b, + 0xbe8c1608, 0x821e1e1e, 0xd7610012, 0x0200280c, 0xbf060b0b, 0xbe8c1708, 0x821e1e1e, 0xd7610013, + 0x0200280c, 0xbf060b0b, 0xbe8c1808, 0x821e1e1e, 0xd7610014, 0x0200280c, 0xbf060b0b, 0xbe8c1908, + 0x821e1e1e, 0xd7610015, 0x0200280c, 0xbf060b0b, 0xbe8c1a08, 0x821e1e1e, 0xd7610016, 0x0200280c, + 0xbe8c040a, 0xbe8e0408, 0xbf060b0b, 0xbe8c1b0a, 0x821e1e1e, 0xd7610017, 0x0200280c, 0xbe8c040a, + 0xbe8e0408, 0xbf060b0b, 0xbe8c1d0a, 0x821e1e1e, 0xd7610018, 0x0200280c, 0xd7610019, 0x0200281e, + 0x80148114, 0xbf0aa014, 0xbf85ffa6, 0xe0781040, 0x80010a03, 0xe0781050, 0x80010e03, 0xe0781060, + 0x80011203, 0xe0781070, 0x80011603, 0xbe940380, 0xd7600008, 0x02002904, 0xd7600009, 0x02002905, + 0xd760000a, 0x02002906, 0xd760000b, 0x02002907, 0xbe9e0380, 0xbe8c040a, 0xbe8e0408, 0xbf060b0b, + 0xbe8c1c0a, 0x821e1e1e, 0xd761000a, 0x0200280c, 0xd761000b, 0x0200280d, 0xbe8c040a, 0xbe8e0408, + 0xbf060b0b, 0xbe8c1e0a, 0x821e1e1e, 0xd761000c, 0x0200280c, 0xd761000d, 0x0200280d, 0xbe8c2c08, + 0x821e1e1e, 0xd761000e, 0x0200280c, 0xbe8c2d08, 0x821e1e1e, 0xd761000f, 0x0200280c, 0xd7610010, + 0x0200280d, 0xbe8c3408, 0x821e1e1e, 0xd7610011, 0x0200280c, 0xbf060b0b, 0xbe8c3b08, 0x821e1e1e, + 0xd7610012, 0x0200280c, 0xd7610013, 0x0200280d, 0x877c830a, 0xbf060b0b, 0xbe8c2e08, 0x821e1e1e, + 0xd7610014, 0x0200280c, 0x877c820a, 0xbf060b0b, 0xbe8c2f08, 0x821e1e1e, 0xd7610015, 0x0200280c, + 0xd7610016, 0x0200280d, 0xd7610019, 0x0200281e, 0x80148114, 0xbf0aa014, 0xbf85ffbd, 0xe0781080, + 0x80010a03, 0xe0781090, 0x80010e03, 0xe07810a0, 0x80011203, 0xe07810b0, 0x80011603, 0xbe940380, + 0xd7600008, 0x02002904, 0xd7600009, 0x02002905, 0xd760000a, 0x02002906, 0xd760000b, 0x02002907, + 0xbe9e0380, 0xbe8c040a, 0xbe8e0408, 0x877c830a, 0xbf060b0b, 0xbe8c3008, 0x821e1e1e, 0xd761000a, + 0x0200280c, 0xd761000b, 0x0200280d, 0xd761000c, 0x0200280e, 0xd761000d, 0x0200280f, 0xbe8c040a, + 0xbe8e0408, 0x877c820a, 0xbf060b0b, 0xbe8c3108, 0x821e1e1e, 0xd761000e, 0x0200280c, 0xd761000f, + 0x0200280d, 0xd7610010, 0x0200280e, 0xd7610011, 0x0200280f, 0xbe8c040a, 0xbe8e0408, 0x877cff0a, + 0x00030003, 0xbf060b0b, 0xbe8c4908, 0x821e1e1e, 0xd7610012, 0x0200280c, 0xd7610013, 0x0200280d, + 0xd7610014, 0x0200280e, 0xd7610015, 0x0200280f, 0xbefe0309, 0xbe8c3c08, 0xbe8d037e, 0xbefe03c1, + 0x821e1e1e, 0xd7610016, 0x0200280c, 0xd7610017, 0x0200280d, 0xd7610019, 0x0200281e, 0x80148114, + 0xbf0aa014, 0xbf85ffbe, 0xe07810c0, 0x80010a03, 0xe07810d0, 0x80010e03, 0xe07810e0, 0x80011203, + 0xe07810f0, 0x80011603, 0xbe940380, 0xd7600008, 0x02002904, 0xd7600009, 0x02002905, 0xd760000a, + 0x02002906, 0xd760000b, 0x02002907, 0xbe9e0380, 0xbefe0309, 0xbe8c3d08, 0xbe8d037e, 0xbefe03c1, + 0x821e1e1e, 0xd761000a, 0x0200280c, 0xd761000b, 0x0200280d, 0xbefe0309, 0xbe8c3e08, 0xbe8d037e, + 0xbefe03c1, 0x821e1e1e, 0xd761000c, 0x0200280c, 0xd761000d, 0x0200280d, 0xbefe0309, 0xbe8c3f08, + 0xbe8d037e, 0xbefe03c1, 0x821e1e1e, 0xd761000e, 0x0200280c, 0xd761000f, 0x0200280d, 0xbefe0309, + 0xbe8c4008, 0xbe8d037e, 0xbefe03c1, 0x821e1e1e, 0xd7610010, 0x0200280c, 0xd7610011, 0x0200280d, + 0xbefe0309, 0xbe8c4108, 0xbe8d037e, 0xbefe03c1, 0x821e1e1e, 0xd7610012, 0x0200280c, 0xd7610013, + 0x0200280d, 0xbefe0309, 0xbe8c4208, 0xbe8d037e, 0xbefe03c1, 0x821e1e1e, 0xd7610014, 0x0200280c, + 0xd7610015, 0x0200280d, 0xbefe0309, 0xbe8c4308, 0xbe8d037e, 0xbefe03c1, 0x821e1e1e, 0xd7610016, + 0x0200280c, 0xd7610017, 0x0200280d, 0xd7610019, 0x0200281e, 0x80148114, 0xbf0aa014, 0xbf85ffb3, + 0xe0781100, 0x80010a03, 0xe0781110, 0x80010e03, 0xe0781120, 0x80011203, 0xe0781130, 0x80011603, + 0xbe940380, 0xd7600008, 0x02002904, 0xd7600009, 0x02002905, 0xd760000a, 0x02002906, 0xd760000b, + 0x02002907, 0xbe9e0380, 0xbefe0309, 0xbe8c4408, 0xbe8d037e, 0xbefe03c1, 0x821e1e1e, 0xd761000a, + 0x0200280c, 0xd761000b, 0x0200280d, 0xbefe0309, 0xbe8c4508, 0xbe8d037e, 0xbefe03c1, 0x821e1e1e, + 0xd761000c, 0x0200280c, 0xd761000d, 0x0200280d, 0xbefe0309, 0xbe8c4608, 0xbe8d037e, 0xbefe03c1, + 0x821e1e1e, 0xd761000e, 0x0200280c, 0xd761000f, 0x0200280d, 0xbefe0309, 0xbe8c4708, 0xbe8d037e, + 0xbefe03c1, 0x821e1e1e, 0xd7610010, 0x0200280c, 0xd7610011, 0x0200280d, 0xbefe030a, 0xbeff030b, + 0xbe8c2408, 0xbe8e037e, 0xbe8f037f, 0xbefe03c1, 0xbeff0380, 0x821e1e1e, 0xd7610012, 0x0200280c, + 0xd7610013, 0x0200280d, 0xd7610014, 0x0200280e, 0xd7610015, 0x0200280f, 0xd7610019, 0x0200281e, + 0x80148114, 0xbf0aa014, 0xbf85ffbe, 0xe0781140, 0x80010a03, 0xe0781150, 0x80010e03, 0xe0781160, + 0x80011203, 0xe0781170, 0x80011603, 0xbe940380, 0xd7600008, 0x02002904, 0xd7600009, 0x02002905, + 0xd760000a, 0x02002906, 0xd760000b, 0x02002907, 0xbe9e0380, 0xbefe030a, 0xbeff030b, 0xbe8c2508, + 0xbe8e037e, 0xbe8f037f, 0xbefe03c1, 0xbeff0380, 0x821e1e1e, 0xd761000a, 0x0200280c, 0xd761000b, + 0x0200280d, 0xd761000c, 0x0200280e, 0xd761000d, 0x0200280f, 0xbefe030a, 0xbeff030b, 0xbe8c2608, + 0xbe8e037e, 0xbe8f037f, 0xbefe03c1, 0xbeff0380, 0x821e1e1e, 0xd761000e, 0x0200280c, 0xd761000f, + 0x0200280d, 0xd7610010, 0x0200280e, 0xd7610011, 0x0200280f, 0xbefe030a, 0xbeff030b, 0xbe8c2708, + 0xbe8e037e, 0xbe8f037f, 0xbefe03c1, 0xbeff0380, 0x821e1e1e, 0xd7610012, 0x0200280c, 0xd7610013, + 0x0200280d, 0xd7610014, 0x0200280e, 0xd7610015, 0x0200280f, 0xd7610019, 0x0200281e, 0x80148114, + 0xbf0aa014, 0xbf85ffc2, 0xe0781180, 0x80010a03, 0xe0781190, 0x80010e03, 0xe07811a0, 0x80011203, + 0xe07811b0, 0x80011603, 0xbe940380, 0xd7600008, 0x02002904, 0xd7600009, 0x02002905, 0xd760000a, + 0x02002906, 0xd760000b, 0x02002907, 0xbe9e0380, 0xbefe030a, 0xbeff030b, 0xbe8c2808, 0xbe8e037e, + 0xbe8f037f, 0xbefe03c1, 0xbeff0380, 0x821e1e1e, 0xd761000a, 0x0200280c, 0xd761000b, 0x0200280d, + 0xd761000c, 0x0200280e, 0xd761000d, 0x0200280f, 0xbefe030a, 0xbeff030b, 0xbe8c2908, 0xbe8e037e, + 0xbe8f037f, 0xbefe03c1, 0xbeff0380, 0x821e1e1e, 0xd761000e, 0x0200280c, 0xd761000f, 0x0200280d, + 0xd7610010, 0x0200280e, 0xd7610011, 0x0200280f, 0xbefe030a, 0xbeff030b, 0xbe8c2a08, 0xbe8e037e, + 0xbe8f037f, 0xbefe03c1, 0xbeff0380, 0x821e1e1e, 0xd7610012, 0x0200280c, 0xd7610013, 0x0200280d, + 0xd7610014, 0x0200280e, 0xd7610015, 0x0200280f, 0xd7610019, 0x0200281e, 0x80148114, 0xbf0aa014, + 0xbf85ffc2, 0xe07811c0, 0x80010a03, 0xe07811d0, 0x80010e03, 0xe07811e0, 0x80011203, 0xe07811f0, + 0x80011603, 0xbe940380, 0xd7600008, 0x02002904, 0xd7600009, 0x02002905, 0xd760000a, 0x02002906, + 0xd760000b, 0x02002907, 0xbe9e0380, 0xbefe030a, 0xbeff030b, 0xbe8c2b08, 0xbe8e037e, 0xbe8f037f, + 0xbefe03c1, 0xbeff0380, 0x821e1e1e, 0xd761000a, 0x0200280c, 0xd761000b, 0x0200280d, 0xd761000c, + 0x0200280e, 0xd761000d, 0x0200280f, 0xbefe030a, 0xbeff030b, 0xbe8c3708, 0xbe8e037e, 0xbe8f037f, + 0xbefe03c1, 0xbeff0380, 0x821e1e1e, 0xd761000e, 0x0200280c, 0xd761000f, 0x0200280d, 0xd7610010, + 0x0200280e, 0xd7610011, 0x0200280f, 0xbefe030a, 0xbeff030b, 0xbe8c3808, 0xbe8e037e, 0xbe8f037f, + 0xbefe03c1, 0xbeff0380, 0x821e1e1e, 0xd7610012, 0x0200280c, 0xd7610013, 0x0200280d, 0xd7610014, + 0x0200280e, 0xd7610015, 0x0200280f, 0xd7610019, 0x0200281e, 0x80148114, 0xbf0aa014, 0xbf85ffc2, + 0xe0781200, 0x80010a03, 0xe0781210, 0x80010e03, 0xe0781220, 0x80011203, 0xe0781230, 0x80011603, + 0xbe940380, 0xd7600008, 0x02002904, 0xd7600009, 0x02002905, 0xd760000a, 0x02002906, 0xd760000b, + 0x02002907, 0xbe9e0380, 0xbefe030a, 0xbeff030b, 0xbe8c3908, 0xbe8e037e, 0xbe8f037f, 0xbefe03c1, + 0xbeff0380, 0x821e1e1e, 0xd761000a, 0x0200280c, 0xd761000b, 0x0200280d, 0xd761000c, 0x0200280e, + 0xd761000d, 0x0200280f, 0xbefe030a, 0xbeff030b, 0xbe8c3a08, 0xbe8e037e, 0xbe8f037f, 0xbefe03c1, + 0xbeff0380, 0x821e1e1e, 0xd761000e, 0x0200280c, 0xd761000f, 0x0200280d, 0xd7610010, 0x0200280e, + 0xd7610011, 0x0200280f, 0xbf000a08, 0x821e1e1e, 0xbf010a08, 0x821e1e1e, 0xbf020a08, 0x821e1e1e, + 0xbf030a08, 0x821e1e1e, 0xbf040a08, 0x821e1e1e, 0xbf050a08, 0x821e1e1e, 0xbf060a08, 0x821e1e1e, + 0xbf070a08, 0x821e1e1e, 0xbf080a08, 0x821e1e1e, 0xbf090a08, 0x821e1e1e, 0xbf0a0a08, 0x821e1e1e, + 0xbf0b0a08, 0x821e1e1e, 0xbf120a08, 0x821e1e1e, 0xbf130a08, 0x821e1e1e, 0xbf0c0a08, 0x821e1e1e, + 0xbf0d0a08, 0x821e1e1e, 0xbf0e0a08, 0x821e1e1e, 0xbf0f0a08, 0x821e1e1e, 0xb1888000, 0x821e1e1e, + 0xb2087fff, 0x821e1e1e, 0xb288ffff, 0x821e1e1e, 0xb3080000, 0x821e1e1e, 0xb3888000, 0x821e1e1e, + 0xb4080001, 0x821e1e1e, 0xb4888000, 0x821e1e1e, 0xb508ffff, 0x821e1e1e, 0xb5888000, 0x821e1e1e, + 0xb608ffff, 0x821e1e1e, 0xb6887fff, 0x821e1e1e, 0xd7610019, 0x0200281e, 0x80148114, 0xbf0aa014, + 0xbf85ff98, 0xe0781240, 0x80010a03, 0xe0781250, 0x80010e03, 0xe0781260, 0x80011203, 0xe0781270, + 0x80011603, 0xbe940380, 0xd7600008, 0x02002904, 0xd7600009, 0x02002905, 0xd760000a, 0x02002906, + 0xd760000b, 0x02002907, 0xbe9e0380, 0xb708ffff, 0x821e1e1e, 0xbf060b0b, 0xb00c8001, 0x821e1e1e, + 0xd761000a, 0x0200280c, 0xbe8c0308, 0xb78c8000, 0x821e1e1e, 0xd761000b, 0x0200280c, 0xbe8c0308, + 0xb78c7fff, 0x821e1e1e, 0xd761000c, 0x0200280c, 0xbe8c0308, 0xbf060b0b, 0xb80cfffd, 0x821e1e1e, + 0xd761000d, 0x0200280c, 0xd7610019, 0x0200281e, 0x80148114, 0xbf0aa014, 0xbf85ffdb, 0xe0781280, + 0x80010a03, 0xe0781290, 0x80010e03, 0xe07812a0, 0x80011203, 0xe07812b0, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0xffff8001u, 0x0000003fu, 0x00000040u, 0xffffffffu}, + {0x00000000u, 0x00000000u, 0x00010000u, 0x00000001u}, + {0xffffffffu, 0xffffffffu, 0xffff8001u, 0x12345678u}, + {0x00007fffu, 0xd4cbd76du, 0x00007fffu, 0xd4cbd76du}, + {0x80000000u, 0x2bf2354eu, 0xcb2e075eu, 0x9abcdef0u}, + {0xffff8000u, 0x00007fffu, 0x74d6d11fu, 0x55555555u}, + {0x00008000u, 0x9628ee8du, 0x00000000u, 0xffffffffu}, + {0x0000ffffu, 0x831ef5c3u, 0x0f0f0f0fu, 0x00f000f0u}, + {0x00000020u, 0x00000003u, 0x7fffffffu, 0x00000003u}, + {0x00f000f0u, 0x80000000u, 0x00000003u, 0x1fb17c23u}, + {0x00000003u, 0x11111111u, 0x00010000u, 0x0000001fu}, + {0x12345678u, 0x7731af10u, 0x40000000u, 0x00008000u}, + {0xffff8001u, 0x00010003u, 0xffff8001u, 0x12bd4aceu}, + {0x26e87555u, 0x6bf46c69u, 0xffffffffu, 0x92b1d3f2u}, + {0x00020001u, 0x40000000u, 0x80000000u, 0x40000000u}, + {0x4f426dcbu, 0xfe3b890bu, 0x72158370u, 0xf0f0f0f0u}, + {0x5affb229u, 0xfffffffeu, 0x0000ffffu, 0x211c70cfu}, + {0x7f1b103cu, 0x9abcdef0u, 0x00007fffu, 0xd1bc52d9u}, + {0xfc891b4au, 0xf0f0f0f0u, 0x26a2c0bdu, 0x00000020u}, + {0x0000003fu, 0x00000000u, 0x0f0f0f0fu, 0x519088f5u}, + {0x00000003u, 0x11111111u, 0x00000003u, 0x11111111u}, + {0x00000021u, 0x00000003u, 0x00000020u, 0x00000003u}, + {0xf0f0f0f0u, 0xffff8000u, 0xf0f0f0f0u, 0xfffffffeu}, + {0x7afb2c68u, 0x80000000u, 0x00010003u, 0x7a86f7a2u}, + {0xaaaaaaaau, 0x00000001u, 0xaaaaaaaau, 0xdd02de92u}, + {0x00000021u, 0x00010000u, 0x55555555u, 0x00010000u}, + {0x31f51707u, 0xd17e4497u, 0x00010000u, 0xd17e4497u}, + {0x00007fffu, 0x00000040u, 0xf4de2c08u, 0x00020001u}, + {0x00010000u, 0x00010003u, 0x00000000u, 0x00010003u}, + {0xe8e72789u, 0x00000040u, 0xe8e72789u, 0x00010003u}, + {0x7691b06fu, 0x80000000u, 0x2b855c1fu, 0x070d7109u}, + {0x256badf9u, 0x988af3fbu, 0xa842bc19u, 0x27e9e06fu} +}; +constexpr std::uint32_t Expected[32][176] = { + {0xffff8001u, 0x0000003fu, 0xffff8001u, 0xffff8001u, 0x0000003fu, 0x00007ffeu, 0x00007ffeu, 0xffffffc0u, 0xfffff00fu, 0xfffff00fu, 0x000000ffu, 0x8001ffffu, 0xfc000000u, 0x8001ffffu, 0x0000000eu, 0x000003ffu, 0x00000028u, 0x00000012u, 0x00000018u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001au, 0x00000011u, 0x0000001au, 0x00000001u, 0xffff8001u, 0x00000040u, 0x00000041u, 0x00007fffu, 0x00000040u, 0xffffffffu, 0x00000041u, 0xffffffffu, 0x000000f9u, 0x000003f9u, 0x00000000u, 0x00007fffu, 0xc0000003u, 0xffffffffu, 0xffff8001u, 0xffff8001u, 0x0000003fu, 0x00000000u, 0x00000000u, 0x000000ffu, 0xffff8001u, 0xffffffffu, 0xffff8001u, 0x0000003fu, 0xffff8001u, 0x0000003fu, 0xffff8001u, 0x0000003fu, 0xffff8001u, 0xffffffffu, 0xffff8001u, 0x0000003fu, 0x0000003fu, 0x00000001u, 0x00000000u, 0x0000000fu, 0x0000003fu, 0xffff803fu, 0x0000003fu, 0xffff803eu, 0x0000003fu, 0xffff8000u, 0x0000003fu, 0xffffffc1u, 0x0000003fu, 0xfffffffeu, 0x0000003fu, 0x00007fc0u, 0x0000003fu, 0x00007fc1u, 0x00000000u, 0x0000007fu, 0x0000003fu, 0x0000003eu, 0x0000003fu, 0x00007fffu, 0x0000003eu, 0x0000003eu, 0xffff8000u, 0xffff8000u, 0x00000040u, 0xffffffffu, 0x00000000u, 0x0000003fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x00000040u, 0xffffffffu, 0xffff8041u, 0xffffffffu, 0x00000040u, 0xffffffffu, 0xffff8041u, 0xffffffc0u, 0x00000040u, 0xffffffffu, 0xffff8001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000040u, 0xffffffffu, 0xffffffbfu, 0x0000003fu, 0x00000040u, 0xffffffffu, 0xffffffffu, 0xffffffc0u, 0x00000040u, 0xffffffffu, 0x00007fbeu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000040u, 0xffffffffu, 0x00007fbeu, 0x0000003fu, 0x00000040u, 0xffffffffu, 0x00000040u, 0xffffffc0u, 0x00000040u, 0xffffffffu, 0x00007ffeu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000040u, 0xffffffc0u, 0x00000040u, 0xffffffc0u, 0xffff8001u, 0x00000000u, 0xffff8001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x69b8aa2eu, 0xffff8001u, 0xffff0001u, 0x00000000u, 0x00017ffdu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000020u, 0x000003e7u, 0x00000040u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00010000u, 0x00010001u, 0x00004fffu, 0x00010000u, 0x00000001u, 0x00010001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000000c7u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00010000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000000eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x00000000u, 0xffffffffu, 0x00000000u, 0xffffffffu, 0x00000000u, 0xffffffffu, 0x00000000u, 0x0000000fu, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00010000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000008u, 0x00010000u, 0x00000001u, 0x00010000u, 0x00000001u, 0x00010000u, 0x00000001u, 0x00010000u, 0x00000001u, 0x00010000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000006u, 0x00010000u, 0x00000001u, 0xfffeffffu, 0xfffffffeu, 0x00010000u, 0x00000001u, 0xffffffffu, 0xffffffffu, 0x00010000u, 0x00000001u, 0xfffeffffu, 0xfffffffeu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00010000u, 0x00000001u, 0xfffeffffu, 0xfffffffeu, 0x00010000u, 0x00000001u, 0x00010000u, 0x00000001u, 0x00010000u, 0x00000001u, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00010000u, 0x00000001u, 0x00010000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x49a6d3a9u, 0xffff8001u, 0xffff8000u, 0x00007fffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u}, + {0xffffffffu, 0xffffffffu, 0xffff8001u, 0xffff8001u, 0x12345678u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x0000021eu, 0x00000000u, 0x00000020u, 0x00000040u, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffff8001u, 0xffff8003u, 0x00003fffu, 0xffff8001u, 0x12345678u, 0xffff8003u, 0x12345678u, 0x000000ffu, 0x0000ffffu, 0x00000000u, 0x00000001u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x000000ffu, 0xffff8001u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffff8001u, 0x12345678u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x0000000fu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x00000000u, 0xffffffffu, 0x00000000u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x00000000u, 0xffffffffu, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000049u, 0xffffffffu, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffff8001u, 0x12345678u, 0xffff8001u, 0x12345678u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u, 0xffff8001u, 0x12345678u, 0xffffffffu, 0xffffffffu, 0xffff8001u, 0x12345678u, 0x00007ffeu, 0xedcba987u, 0xffff8001u, 0x12345678u, 0x00007ffeu, 0xedcba987u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xffff8001u, 0x12345678u, 0xffffffffu, 0xffffffffu, 0xffff8001u, 0x12345678u, 0x00007ffeu, 0xedcba987u, 0xffff8001u, 0x12345678u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000006u, 0xffff8001u, 0x12345678u, 0xffff8001u, 0x12345678u, 0xffff8001u, 0x12345678u, 0x00000000u, 0x00000000u, 0xffff8001u, 0x12345678u, 0xffff8001u, 0x12345678u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000005u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00007ffeu, 0xedcba987u, 0x00007ffeu, 0xedcba987u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x2e38aa2eu, 0xffff8001u, 0xffff7fffu, 0x00007ffeu, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x00007fffu, 0xd4cbd76du, 0x00007fffu, 0x00007fffu, 0xd4cbd76du, 0xffff8000u, 0xffff8000u, 0x2b342892u, 0x0000ffffu, 0x0000ffffu, 0xffffffffu, 0xfffe0000u, 0xb6ebd32bu, 0xfffe0000u, 0x00000011u, 0x000003ffu, 0x0000001du, 0x0000000fu, 0x00000023u, 0x0000000fu, 0x0000000fu, 0x00000000u, 0x00000000u, 0x00000011u, 0x00000000u, 0x00000011u, 0x00000002u, 0xffffffffu, 0x00007fffu, 0x00007fffu, 0x80007fffu, 0x00007fffu, 0x00007fffu, 0x54cbd76du, 0x00007fffu, 0xd4cbd76du, 0x0000000fu, 0x0000ff0fu, 0x00000000u, 0x00007fffu, 0x3fffffffu, 0x00000000u, 0xd4cbd76du, 0x00007fffu, 0xd4cbd76du, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00007fffu, 0xd4cbd76du, 0x00007fffu, 0x00007fffu, 0x00007fffu, 0xd4cbd76du, 0x00007fffu, 0xd4cbd76du, 0xd4cbd76du, 0xd4cbd76du, 0x00007fffu, 0xd4cbd76du, 0xd4cbd76du, 0x0000576du, 0x00000000u, 0x0000000fu, 0xd4cbd76du, 0xd4cbffffu, 0xd4cbd76du, 0xd4cba892u, 0xd4cbd76du, 0x00002892u, 0xd4cbd76du, 0x2b347fffu, 0xd4cbd76du, 0xffffa892u, 0xd4cbd76du, 0x2b340000u, 0xd4cbd76du, 0x2b34576du, 0x00000000u, 0x0000007fu, 0xd4cbd76du, 0xd4cb8000u, 0xd4cbd76du, 0xffffd76du, 0xd4cb8000u, 0xd4cb8000u, 0x00002892u, 0x00002892u, 0x00007fffu, 0xd4cbd76du, 0x00007fffu, 0xd4cbd76du, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x00007fffu, 0xd4cbd76du, 0x00007fffu, 0xd4cbd76du, 0x00007fffu, 0xd4cbd76du, 0x00000000u, 0x00000000u, 0x00007fffu, 0xd4cbd76du, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00007fffu, 0xd4cbd76du, 0xffffffffu, 0xffffffffu, 0x00007fffu, 0xd4cbd76du, 0xffff8000u, 0x2b342892u, 0x00007fffu, 0xd4cbd76du, 0xffff8000u, 0x2b342892u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00007fffu, 0xd4cbd76du, 0xffffffffu, 0xffffffffu, 0x00007fffu, 0xd4cbd76du, 0x00000000u, 0x00000000u, 0x00007fffu, 0xd4cbd76du, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000005u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x12cb4988u, 0xffff8001u, 0xffffffffu, 0x0000fffeu, 0xfffe8003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u}, + {0x80000000u, 0x2bf2354eu, 0xcb2e075eu, 0xcb2e075eu, 0x9abcdef0u, 0x7fffffffu, 0x7fffffffu, 0xd40dcab1u, 0xf0000000u, 0xf0000000u, 0xffffffffu, 0x00000001u, 0x72ac4fd4u, 0x00000001u, 0x0000001fu, 0x0000027fu, 0x0000002eu, 0x00000001u, 0x00000012u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x0000001fu, 0x00000000u, 0x00000002u, 0x00000001u, 0x00000002u, 0x00000000u, 0x00000000u, 0x8b2e075eu, 0xcb2e075eu, 0x00007fffu, 0x8b2e075eu, 0x9abcdef0u, 0xcb2e075eu, 0x9abcdef0u, 0x00000080u, 0x0000ff80u, 0x00000000u, 0x80000000u, 0x00000000u, 0xc0000000u, 0xcb2e075eu, 0xcb2e075eu, 0x9abcdef0u, 0x00000000u, 0x00000000u, 0x000000ffu, 0xcb2e075eu, 0x9abcdef0u, 0x80000000u, 0x2bf2354eu, 0xcb2e075eu, 0x9abcdef0u, 0x80000000u, 0x2bf2354eu, 0xcb2e075eu, 0x9abcdef0u, 0xcb2e075eu, 0x2bf2354eu, 0x2bf2354eu, 0x00000000u, 0x00000000u, 0x0000000eu, 0x2bf2354eu, 0xabf2354eu, 0x2bf2354eu, 0xabf2354eu, 0x2bf2354eu, 0x80000000u, 0x2bf2354eu, 0xd40dcab1u, 0x2bf2354eu, 0xffffffffu, 0x2bf2354eu, 0x540dcab1u, 0x2bf2354eu, 0x540dcab1u, 0x00000000u, 0x0000007fu, 0x2bf2354eu, 0x2bf2354eu, 0x2bf2354eu, 0x7fffffffu, 0x2bf2354eu, 0x2bf2354eu, 0x80000000u, 0x80000000u, 0xcb2e075eu, 0x9abcdef0u, 0x80000000u, 0x0ab01440u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0xcb2e075eu, 0x9abcdef0u, 0xcb2e075eu, 0xbbfefffeu, 0xcb2e075eu, 0x9abcdef0u, 0x4b2e075eu, 0xb14eebbeu, 0xcb2e075eu, 0x9abcdef0u, 0x00000000u, 0x2142210eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xcb2e075eu, 0x9abcdef0u, 0xb4d1f8a1u, 0x6ff3354fu, 0xcb2e075eu, 0x9abcdef0u, 0x7fffffffu, 0xf54febbfu, 0xcb2e075eu, 0x9abcdef0u, 0x34d1f8a1u, 0x44010001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xcb2e075eu, 0x9abcdef0u, 0xb4d1f8a1u, 0x4eb11441u, 0xcb2e075eu, 0x9abcdef0u, 0x4b2e075eu, 0x900ccab0u, 0xcb2e075eu, 0x9abcdef0u, 0xffffffffu, 0xdebddef1u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x4b2e075eu, 0x900ccab0u, 0x4b2e075eu, 0x900ccab0u, 0x00000000u, 0x2142210eu, 0x00000000u, 0x2142210eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x69a6d26eu, 0xffff8001u, 0x7fff8000u, 0x80007fffu, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000000du}, + {0xffff8000u, 0x00007fffu, 0xffff8000u, 0xffff8000u, 0x00007fffu, 0x00007fffu, 0x00007fffu, 0xffff8000u, 0xfffff000u, 0xfffff000u, 0x0000ffffu, 0x0001ffffu, 0xfffe0000u, 0x0001ffffu, 0x0000000fu, 0x000003ffu, 0x00000020u, 0x00000011u, 0x00000020u, 0x00000000u, 0x00000000u, 0x0000000fu, 0x0000000fu, 0x00000000u, 0x00000011u, 0x00000011u, 0x00000011u, 0x00000000u, 0xffff8000u, 0x74d6d11fu, 0xf4d6d11fu, 0x00007fffu, 0x74d6d11fu, 0x55555555u, 0xf4d6d11fu, 0x55555555u, 0x000000f8u, 0x00000ff8u, 0x00000000u, 0x00008000u, 0xc0000000u, 0xffffffffu, 0x55555555u, 0x74d6d11fu, 0x55555555u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x74d6d11fu, 0x55555555u, 0xffff8000u, 0xffff8000u, 0x74d6d11fu, 0x55555555u, 0xffff8000u, 0x00007fffu, 0x74d6d11fu, 0x55555555u, 0x55555555u, 0x00007fffu, 0x00007fffu, 0x00000000u, 0x00000000u, 0x0000000eu, 0x00007fffu, 0xffffffffu, 0x00007fffu, 0xffffffffu, 0x00007fffu, 0xffff8000u, 0x00007fffu, 0xffff8000u, 0x00007fffu, 0xffffffffu, 0x00007fffu, 0x00000000u, 0x00007fffu, 0x00000000u, 0x00000000u, 0x0000007cu, 0x00007fffu, 0x00007fffu, 0x00007fffu, 0x00007fffu, 0x00007fffu, 0x00007fffu, 0xffff8000u, 0xffff8000u, 0x74d6d11fu, 0x55555555u, 0x74d68000u, 0x00005555u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x74d6d11fu, 0x55555555u, 0xffffd11fu, 0x55557fffu, 0x74d6d11fu, 0x55555555u, 0x8b29511fu, 0x55552aaau, 0x74d6d11fu, 0x55555555u, 0x8b290000u, 0x00002aaau, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x74d6d11fu, 0x55555555u, 0xffffaee0u, 0xaaaaffffu, 0x74d6d11fu, 0x55555555u, 0x8b297fffu, 0xffffaaaau, 0x74d6d11fu, 0x55555555u, 0x00002ee0u, 0xaaaa8000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x74d6d11fu, 0x55555555u, 0x74d6aee0u, 0xaaaad555u, 0x74d6d11fu, 0x55555555u, 0x0000511fu, 0x55550000u, 0x74d6d11fu, 0x55555555u, 0x74d6ffffu, 0xffffd555u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x0000511fu, 0x55550000u, 0x0000511fu, 0x55550000u, 0x8b290000u, 0x00002aaau, 0x8b290000u, 0x00002aaau, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x69b8ae2eu, 0xffff8001u, 0xffff0000u, 0xffffffffu, 0x00018000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x00008000u, 0x9628ee8du, 0x00008000u, 0x00008000u, 0x9628ee8du, 0xffff7fffu, 0xffff7fffu, 0x69d71172u, 0x0000f000u, 0x0000f000u, 0xffffffffu, 0x00010000u, 0xb1771469u, 0x00010000u, 0x0000001fu, 0x000003ffu, 0x0000002fu, 0x00000001u, 0x00000011u, 0x00000000u, 0x00000000u, 0x0000000fu, 0x0000000fu, 0x00000010u, 0x00000000u, 0x00000010u, 0x00000001u, 0x00000000u, 0xffff8000u, 0x00000000u, 0x00000001u, 0x00007fffu, 0x00000000u, 0xffffffffu, 0x00000001u, 0xffffffffu, 0x00000008u, 0x0000ff08u, 0x00000000u, 0x00008000u, 0xc0000000u, 0x00000000u, 0x00008000u, 0x00008000u, 0x9628ee8du, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00008000u, 0xffffffffu, 0x00008000u, 0x9628ee8du, 0x00008000u, 0x9628ee8du, 0x00008000u, 0x9628ee8du, 0x00008000u, 0xffffffffu, 0x00008000u, 0x9628ee8du, 0x9628ee8du, 0x00008000u, 0x00000000u, 0x0000000fu, 0x9628ee8du, 0x9628ee8du, 0x9628ee8du, 0x96286e8du, 0x9628ee8du, 0x00000000u, 0x9628ee8du, 0x69d79172u, 0x9628ee8du, 0xffff7fffu, 0x9628ee8du, 0x69d71172u, 0x9628ee8du, 0x69d79172u, 0x00000000u, 0x0000006fu, 0x9628ee8du, 0x96286e8du, 0x9628ee8du, 0xffffffffu, 0x96286e8du, 0x96286e8du, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x00000000u, 0x9628ee8du, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001du, 0x00000000u, 0xffffffffu, 0x00008000u, 0xffffffffu, 0x00000000u, 0xffffffffu, 0x00008000u, 0x69d71172u, 0x00000000u, 0xffffffffu, 0x00008000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x9628ee8du, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x69d71172u, 0x00000000u, 0xffffffffu, 0xffff7fffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000000u, 0xffffffffu, 0xffff7fffu, 0x9628ee8du, 0x00000000u, 0xffffffffu, 0x00000000u, 0x69d71172u, 0x00000000u, 0xffffffffu, 0xffff7fffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000000u, 0x69d71172u, 0x00000000u, 0x69d71172u, 0x00008000u, 0x00000000u, 0x00008000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6e38d398u, 0xffff8001u, 0x00000000u, 0x0000ffffu, 0xfffe8000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u}, + {0x0000ffffu, 0x831ef5c3u, 0x0f0f0f0fu, 0x0f0f0f0fu, 0x00f000f0u, 0xffff0000u, 0xffff0000u, 0x7ce10a3cu, 0x0000ffffu, 0x0000ffffu, 0xffffffffu, 0xffff0000u, 0xc3af78c1u, 0xffff0000u, 0x00000010u, 0x0000027fu, 0x0000001fu, 0x00000010u, 0x00000021u, 0x00000010u, 0x00000010u, 0x00000000u, 0x00000000u, 0x00000010u, 0x00000000u, 0x00000010u, 0x00000001u, 0xffffffffu, 0xffffffffu, 0x0f0f0f0fu, 0x0f0f8f0fu, 0x00007fffu, 0x0f0f0f0fu, 0x00f000f0u, 0x0f0f8f0fu, 0x00f000f0u, 0x0000000fu, 0x0000ff0fu, 0x00000000u, 0x0000ffffu, 0xffffffffu, 0x00000000u, 0x00f000f0u, 0x0f0f0f0fu, 0x00f000f0u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x0f0f0f0fu, 0x00f000f0u, 0x0000ffffu, 0x0000ffffu, 0x0f0f0f0fu, 0x00f000f0u, 0x0000ffffu, 0x831ef5c3u, 0x0f0f0f0fu, 0x00f000f0u, 0x0000ffffu, 0x00f000f0u, 0x831ef5c3u, 0x0000f5c3u, 0x00000000u, 0x0000000fu, 0x831ef5c3u, 0x831effffu, 0x831ef5c3u, 0x831e0a3cu, 0x831ef5c3u, 0x00000a3cu, 0x831ef5c3u, 0x7ce1ffffu, 0x831ef5c3u, 0xffff0a3cu, 0x831ef5c3u, 0x7ce10000u, 0x831ef5c3u, 0x7ce1f5c3u, 0x00000000u, 0x0000007fu, 0x831ef5c3u, 0x831e0000u, 0x831ef5c3u, 0xfffff5c3u, 0x831e0000u, 0x831e0000u, 0x00000a3cu, 0x00000a3cu, 0x0f0f0f0fu, 0x00f000f0u, 0x00000f0fu, 0x001000c0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x0f0f0f0fu, 0x00f000f0u, 0x0f0fffffu, 0x83fef5f3u, 0x0f0f0f0fu, 0x00f000f0u, 0x0f0ff0f0u, 0x83eef533u, 0x0f0f0f0fu, 0x00f000f0u, 0x0000f0f0u, 0x830ef503u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x0f0f0f0fu, 0x00f000f0u, 0xf0f0ffffu, 0xff1fffcfu, 0x0f0f0f0fu, 0x00f000f0u, 0xfffff0f0u, 0xffefff3fu, 0x0f0f0f0fu, 0x00f000f0u, 0xf0f00000u, 0x7c010a0cu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x0f0f0f0fu, 0x00f000f0u, 0xf0f00f0fu, 0x7c110accu, 0x0f0f0f0fu, 0x00f000f0u, 0x0f0f0000u, 0x00e00030u, 0x0f0f0f0fu, 0x00f000f0u, 0xffff0f0fu, 0x7cf10afcu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x0f0f0000u, 0x00e00030u, 0x0f0f0000u, 0x00e00030u, 0x0000f0f0u, 0x830ef503u, 0x0000f0f0u, 0x830ef503u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x69a6ab86u, 0xffff8001u, 0x00007fffu, 0x00017ffeu, 0xfffd0003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u}, + {0x00000020u, 0x00000003u, 0x00000020u, 0x00000020u, 0x00000003u, 0xffffffdfu, 0xffffffdfu, 0xfffffffcu, 0x000000f0u, 0x000000f0u, 0x0000000fu, 0x04000000u, 0xc0000000u, 0x04000000u, 0x0000001fu, 0x000003ffu, 0x0000003du, 0x00000001u, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000005u, 0x00000005u, 0x0000001au, 0x0000001eu, 0x0000001au, 0x0000001eu, 0x00000020u, 0x00000020u, 0x7fffffffu, 0xffffffffu, 0x00007fffu, 0x7fffffffu, 0x00000003u, 0x7fffffffu, 0x80000003u, 0x00000002u, 0x00000102u, 0x00000000u, 0x00000020u, 0x00000c00u, 0x00000000u, 0x00000003u, 0x7fffffffu, 0x00000003u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x7fffffffu, 0x00000003u, 0x00000020u, 0x00000020u, 0x7fffffffu, 0x00000003u, 0x00000020u, 0x00000003u, 0x7fffffffu, 0x00000003u, 0x00000020u, 0x00000003u, 0x00000003u, 0x00000000u, 0x00000000u, 0x0000000eu, 0x00000003u, 0x00000023u, 0x00000003u, 0x00000023u, 0x00000003u, 0x00000020u, 0x00000003u, 0xfffffffcu, 0x00000003u, 0xffffffffu, 0x00000003u, 0xffffffdcu, 0x00000003u, 0xffffffdcu, 0x00000000u, 0x0000007fu, 0x00000003u, 0x00000003u, 0x00000003u, 0xffffffdfu, 0x00000003u, 0x00000003u, 0x00000020u, 0x00000020u, 0x7fffffffu, 0x00000003u, 0x00000020u, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x7fffffffu, 0x00000003u, 0x7fffffffu, 0x00000003u, 0x7fffffffu, 0x00000003u, 0x7fffffdfu, 0x00000000u, 0x7fffffffu, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000006u, 0x7fffffffu, 0x00000003u, 0x80000020u, 0xffffffffu, 0x7fffffffu, 0x00000003u, 0xffffffdfu, 0xfffffffcu, 0x7fffffffu, 0x00000003u, 0x80000000u, 0xfffffffcu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x7fffffffu, 0x00000003u, 0x80000020u, 0xffffffffu, 0x7fffffffu, 0x00000003u, 0x7fffffdfu, 0x00000000u, 0x7fffffffu, 0x00000003u, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x7fffffdfu, 0x00000000u, 0x7fffffdfu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x49a6d389u, 0xffff8001u, 0xffff8020u, 0x0000801fu, 0xffffffa0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u}, + {0x00f000f0u, 0x80000000u, 0x00f000f0u, 0x00f000f0u, 0x80000000u, 0xff0fff0fu, 0xff0fff0fu, 0x7fffffffu, 0x00f000f0u, 0x00f000f0u, 0xf0000000u, 0x0f000f00u, 0x00000001u, 0x0f000f00u, 0x00000018u, 0x000003ffu, 0x00000037u, 0x00000008u, 0x00000009u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000004u, 0x00000008u, 0x00000000u, 0x00000008u, 0x00000001u, 0xfffffff0u, 0x000000f0u, 0x00000003u, 0x0000000bu, 0x00007fffu, 0x00000003u, 0x1fb17c23u, 0x0000000bu, 0x1fb17c23u, 0x00000022u, 0x00008022u, 0x00000000u, 0x00f000f0u, 0x0000ff00u, 0x0000ff00u, 0x1fb17c23u, 0x00000003u, 0x1fb17c23u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00000003u, 0x1fb17c23u, 0x00f000f0u, 0x00f000f0u, 0x00000003u, 0x1fb17c23u, 0x00f000f0u, 0x80000000u, 0x1fb17c23u, 0x1fb17c23u, 0x00f000f0u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x0000000eu, 0x80000000u, 0x80f000f0u, 0x80000000u, 0x80f000f0u, 0x80000000u, 0x00f000f0u, 0x80000000u, 0x7fffffffu, 0x80000000u, 0xffffffffu, 0x80000000u, 0x7f0fff0fu, 0x80000000u, 0x7f0fff0fu, 0x00000000u, 0x0000007fu, 0x80000000u, 0x80000000u, 0x80000000u, 0xff0fff0fu, 0x80000000u, 0x80000000u, 0x00f000f0u, 0x00f000f0u, 0x00000003u, 0x1fb17c23u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001eu, 0x00000003u, 0x1fb17c23u, 0x00f000f3u, 0x9fb17c23u, 0x00000003u, 0x1fb17c23u, 0x00f000f3u, 0x9fb17c23u, 0x00000003u, 0x1fb17c23u, 0x00f000f0u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000003u, 0x1fb17c23u, 0xfffffffcu, 0xe04e83dcu, 0x00000003u, 0x1fb17c23u, 0xffffffffu, 0xffffffffu, 0x00000003u, 0x1fb17c23u, 0xff0fff0cu, 0x604e83dcu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000003u, 0x1fb17c23u, 0xff0fff0cu, 0x604e83dcu, 0x00000003u, 0x1fb17c23u, 0x00000003u, 0x1fb17c23u, 0x00000003u, 0x1fb17c23u, 0xff0fff0fu, 0x7fffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000003u, 0x1fb17c23u, 0x00000003u, 0x1fb17c23u, 0x00f000f0u, 0x80000000u, 0x00f000f0u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6e38d38eu, 0xffff8001u, 0x00ef80f0u, 0x00f080efu, 0xfd2ffd30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x00000003u, 0x11111111u, 0x00000003u, 0x00000003u, 0x11111111u, 0xfffffffcu, 0xfffffffcu, 0xeeeeeeeeu, 0x0000000fu, 0x0000000fu, 0xffffffffu, 0xc0000000u, 0x88888888u, 0xc0000000u, 0x0000001eu, 0x000003ffu, 0x00000036u, 0x00000002u, 0x0000000au, 0x00000002u, 0x00000002u, 0x00000000u, 0x00000000u, 0x0000001eu, 0x00000003u, 0x0000001eu, 0x00000003u, 0x00000003u, 0x00000003u, 0x00010000u, 0x00010001u, 0x00007fffu, 0x00010000u, 0x0000001fu, 0x00010001u, 0x0000001fu, 0x00000001u, 0x0000ff01u, 0x00000000u, 0x00000003u, 0x0000000fu, 0x00000000u, 0x00000003u, 0x00000003u, 0x11111111u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00000003u, 0x0000001fu, 0x00000003u, 0x11111111u, 0x00000003u, 0x11111111u, 0x00000003u, 0x11111111u, 0x00010000u, 0x00000003u, 0x00000003u, 0x11111111u, 0x11111111u, 0x00000001u, 0x00000000u, 0x0000000fu, 0x11111111u, 0x11111113u, 0x11111111u, 0x11111112u, 0x11111111u, 0x00000002u, 0x11111111u, 0xeeeeeeefu, 0x11111111u, 0xfffffffeu, 0x11111111u, 0xeeeeeeecu, 0x11111111u, 0xeeeeeeedu, 0x00000000u, 0x0000007fu, 0x11111111u, 0x11111110u, 0x11111111u, 0xfffffffdu, 0x11111110u, 0x11111110u, 0x00000002u, 0x00000002u, 0x00010000u, 0x0000001fu, 0x00000000u, 0x00000011u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x00010000u, 0x0000001fu, 0x00010003u, 0x1111111fu, 0x00010000u, 0x0000001fu, 0x00010003u, 0x1111110eu, 0x00010000u, 0x0000001fu, 0x00000003u, 0x11111100u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00010000u, 0x0000001fu, 0xfffeffffu, 0xfffffff1u, 0x00010000u, 0x0000001fu, 0xffffffffu, 0xffffffeeu, 0x00010000u, 0x0000001fu, 0xfffefffcu, 0xeeeeeee0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00010000u, 0x0000001fu, 0xfffefffcu, 0xeeeeeef1u, 0x00010000u, 0x0000001fu, 0x00010000u, 0x0000000eu, 0x00010000u, 0x0000001fu, 0xfffffffcu, 0xeeeeeeffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00010000u, 0x0000000eu, 0x00010000u, 0x0000000eu, 0x00000003u, 0x11111100u, 0x00000003u, 0x11111100u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x69a6ab89u, 0xffff8001u, 0xffff8003u, 0x00008002u, 0xfffffff7u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u}, + {0x12345678u, 0x7731af10u, 0x40000000u, 0x40000000u, 0x00008000u, 0xedcba987u, 0xedcba987u, 0x88ce50efu, 0xffffffffu, 0xffffffffu, 0xfffffff0u, 0x1e6a2c48u, 0x08f58ceeu, 0x1e6a2c48u, 0x00000013u, 0x0000027fu, 0x00000023u, 0x0000000du, 0x0000001du, 0x00000000u, 0x00000000u, 0x00000003u, 0x00000003u, 0x00000003u, 0x00000001u, 0x00000003u, 0x00000001u, 0x00000078u, 0x00005678u, 0x40000000u, 0x40000001u, 0x00007fffu, 0x40000000u, 0x00008000u, 0x40000001u, 0x00008000u, 0x000000ffu, 0x0000feffu, 0x00000000u, 0x12345678u, 0x333c3fc0u, 0x030c0f30u, 0x12345678u, 0x12345678u, 0x7731af10u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x12345678u, 0x00008000u, 0x12345678u, 0x7731af10u, 0x12345678u, 0x7731af10u, 0x12345678u, 0x7731af10u, 0x12345678u, 0x00008000u, 0x12345678u, 0x7731af10u, 0x7731af10u, 0x12300610u, 0x00000000u, 0x0000000fu, 0x7731af10u, 0x7735ff78u, 0x7731af10u, 0x6505f968u, 0x7731af10u, 0x00045068u, 0x7731af10u, 0x9afe56ffu, 0x7731af10u, 0xedcff9efu, 0x7731af10u, 0x88ca0087u, 0x7731af10u, 0x9afa0697u, 0x00000000u, 0x0000007fu, 0x7731af10u, 0x6501a900u, 0x7731af10u, 0xfffbaf97u, 0x6501a900u, 0x6501a900u, 0x00045068u, 0x00045068u, 0x40000000u, 0x00008000u, 0x00000000u, 0x00008000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x40000000u, 0x00008000u, 0x52345678u, 0x7731af10u, 0x40000000u, 0x00008000u, 0x52345678u, 0x77312f10u, 0x40000000u, 0x00008000u, 0x12345678u, 0x77312f10u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x40000000u, 0x00008000u, 0xbfffffffu, 0xffffffffu, 0x40000000u, 0x00008000u, 0xffffffffu, 0xffff7fffu, 0x40000000u, 0x00008000u, 0xadcba987u, 0x88ce50efu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x40000000u, 0x00008000u, 0xadcba987u, 0x88ced0efu, 0x40000000u, 0x00008000u, 0x40000000u, 0x00000000u, 0x40000000u, 0x00008000u, 0xedcba987u, 0x88ced0efu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x40000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x12345678u, 0x77312f10u, 0x12345678u, 0x77312f10u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x69a6d38eu, 0xffff8001u, 0x1233d678u, 0x1234d677u, 0xc962fc98u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0xffff8001u, 0x00010003u, 0xffff8001u, 0xffff8001u, 0x12bd4aceu, 0x00007ffeu, 0x00007ffeu, 0xfffefffcu, 0xfffff00fu, 0xfffff00fu, 0x000f000fu, 0x8001ffffu, 0xc0008000u, 0x8001ffffu, 0x0000000eu, 0x0000027fu, 0x0000002bu, 0x00000012u, 0x00000015u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000000fu, 0x00000011u, 0x0000000fu, 0x00000001u, 0xffff8001u, 0xffff8001u, 0xffff8003u, 0x00007fffu, 0xffff8001u, 0x12bd4aceu, 0xffff8003u, 0x12bd4aceu, 0x000000f9u, 0x000011f9u, 0x00000000u, 0x00007fffu, 0xc0000003u, 0xffffffffu, 0x00010003u, 0xffff8001u, 0x00010003u, 0x00000000u, 0x00000000u, 0x000000ffu, 0xffff8001u, 0xffff8001u, 0xffff8001u, 0x00010003u, 0xffff8001u, 0x00010003u, 0xffff8001u, 0x00010003u, 0xffff8001u, 0x12bd4aceu, 0xffff8001u, 0x00010003u, 0x00010003u, 0x00010001u, 0x00000000u, 0x0000000fu, 0x00010003u, 0xffff8003u, 0x00010003u, 0xfffe8002u, 0x00010003u, 0xfffe8000u, 0x00010003u, 0xfffffffdu, 0x00010003u, 0xfffefffeu, 0x00010003u, 0x00007ffcu, 0x00010003u, 0x00017ffdu, 0x00000000u, 0x0000007fu, 0x00010003u, 0x00000002u, 0x00010003u, 0x00017fffu, 0x00000002u, 0x00000002u, 0xfffe8000u, 0xfffe8000u, 0xffff8001u, 0x12bd4aceu, 0xffff8001u, 0x00010002u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0xffff8001u, 0x12bd4aceu, 0xffff8001u, 0x12bd4acfu, 0xffff8001u, 0x12bd4aceu, 0x00000000u, 0x12bc4acdu, 0xffff8001u, 0x12bd4aceu, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xffff8001u, 0x12bd4aceu, 0xffffffffu, 0xed43b533u, 0xffff8001u, 0x12bd4aceu, 0x00007ffeu, 0xfffefffdu, 0xffff8001u, 0x12bd4aceu, 0x00007ffeu, 0xed42b530u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xffff8001u, 0x12bd4aceu, 0xffffffffu, 0xed43b532u, 0xffff8001u, 0x12bd4aceu, 0x00000000u, 0x12bc4accu, 0xffff8001u, 0x12bd4aceu, 0xffffffffu, 0xfffffffeu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000000u, 0x12bc4accu, 0x00000000u, 0x12bc4accu, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x72cad22eu, 0xffff8001u, 0xffff0001u, 0x00000000u, 0x00017ffdu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x26e87555u, 0x6bf46c69u, 0xffffffffu, 0xffffffffu, 0x92b1d3f2u, 0xd9178aaau, 0xd9178aaau, 0x940b9396u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xaaae1764u, 0x96362fd6u, 0xaaae1764u, 0x00000010u, 0x0000027fu, 0x0000001eu, 0x00000010u, 0x00000022u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000002u, 0x00000001u, 0x00000002u, 0x00000001u, 0x00000055u, 0x00007555u, 0x7fffffffu, 0xffffffffu, 0x00007fffu, 0xffffffffu, 0x12b1d3f2u, 0xffffffffu, 0x92b1d3f2u, 0x000000ffu, 0x0000ffffu, 0x00000000u, 0x26e87555u, 0x3f333333u, 0x0c3cfcc0u, 0x92b1d3f2u, 0xffffffffu, 0x92b1d3f2u, 0x00000000u, 0x00000000u, 0x000000ffu, 0xffffffffu, 0x92b1d3f2u, 0x26e87555u, 0x26e87555u, 0xffffffffu, 0x92b1d3f2u, 0x26e87555u, 0x6bf46c69u, 0xffffffffu, 0x92b1d3f2u, 0x26e87555u, 0x92b1d3f2u, 0x6bf46c69u, 0x22e06441u, 0x00000000u, 0x0000000fu, 0x6bf46c69u, 0x6ffc7d7du, 0x6bf46c69u, 0x4d1c193cu, 0x6bf46c69u, 0x04081114u, 0x6bf46c69u, 0xb6ebf7d7u, 0x6bf46c69u, 0xdd1f9bbeu, 0x6bf46c69u, 0x90038282u, 0x6bf46c69u, 0xb2e3e6c3u, 0x00000000u, 0x0000007fu, 0x6bf46c69u, 0x49140828u, 0x6bf46c69u, 0xfbf7eeebu, 0x49140828u, 0x49140828u, 0x04081114u, 0x04081114u, 0xffffffffu, 0x92b1d3f2u, 0x26e87555u, 0x02b04060u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0xffffffffu, 0x92b1d3f2u, 0xffffffffu, 0xfbf5fffbu, 0xffffffffu, 0x92b1d3f2u, 0xd9178aaau, 0xf945bf9bu, 0xffffffffu, 0x92b1d3f2u, 0x00000000u, 0x69442c09u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xffffffffu, 0x92b1d3f2u, 0x26e87555u, 0x6ffe6c6du, 0xffffffffu, 0x92b1d3f2u, 0xd9178aaau, 0xfd4fbf9fu, 0xffffffffu, 0x92b1d3f2u, 0x00000000u, 0x040a0004u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xffffffffu, 0x92b1d3f2u, 0x26e87555u, 0x06ba4064u, 0xffffffffu, 0x92b1d3f2u, 0xd9178aaau, 0x90019392u, 0xffffffffu, 0x92b1d3f2u, 0xffffffffu, 0x96bbd3f6u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xd9178aaau, 0x90019392u, 0xd9178aaau, 0x90019392u, 0x00000000u, 0x69442c09u, 0x00000000u, 0x69442c09u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6e26d38eu, 0xffff8001u, 0x26e7f555u, 0x26e8f554u, 0x8b46a001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x00020001u, 0x40000000u, 0x80000000u, 0x80000000u, 0x40000000u, 0xfffdfffeu, 0xfffdfffeu, 0xbfffffffu, 0x000f000fu, 0x000f000fu, 0xf0000000u, 0x80004000u, 0x00000002u, 0x80004000u, 0x0000001eu, 0x0000027fu, 0x0000003du, 0x00000002u, 0x00000003u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x0000000eu, 0x00000001u, 0x0000000eu, 0x00000001u, 0x00000001u, 0x00000001u, 0x80000000u, 0x80000001u, 0x00007fffu, 0x80000000u, 0x40000000u, 0x80000001u, 0x40000000u, 0x00000011u, 0x00008011u, 0x00000000u, 0x00020001u, 0x00000003u, 0x0000000cu, 0x00020001u, 0x00020001u, 0x40000000u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00020001u, 0x40000000u, 0x00020001u, 0x40000000u, 0x00020001u, 0x40000000u, 0x00020001u, 0x40000000u, 0x00020001u, 0x40000000u, 0x00020001u, 0x40000000u, 0x40000000u, 0x00000000u, 0x00000000u, 0x0000000eu, 0x40000000u, 0x40020001u, 0x40000000u, 0x40020001u, 0x40000000u, 0x00020001u, 0x40000000u, 0xbfffffffu, 0x40000000u, 0xffffffffu, 0x40000000u, 0xbffdfffeu, 0x40000000u, 0xbffdfffeu, 0x00000000u, 0x0000007fu, 0x40000000u, 0x40000000u, 0x40000000u, 0xfffdfffeu, 0x40000000u, 0x40000000u, 0x00020001u, 0x00020001u, 0x80000000u, 0x40000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x80000000u, 0x40000000u, 0x80020001u, 0x40000000u, 0x80000000u, 0x40000000u, 0x80020001u, 0x00000000u, 0x80000000u, 0x40000000u, 0x00020001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x80000000u, 0x40000000u, 0x7fffffffu, 0xffffffffu, 0x80000000u, 0x40000000u, 0xffffffffu, 0xbfffffffu, 0x80000000u, 0x40000000u, 0x7ffdfffeu, 0xbfffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x80000000u, 0x40000000u, 0x7ffdfffeu, 0xffffffffu, 0x80000000u, 0x40000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x40000000u, 0xfffdfffeu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00020001u, 0x00000000u, 0x00020001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6e26ab8eu, 0xffff8001u, 0x00018001u, 0x00028000u, 0xfff9fffdu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x4f426dcbu, 0xfe3b890bu, 0x72158370u, 0x72158370u, 0xf0f0f0f0u, 0xb0bd9234u, 0xb0bd9234u, 0x01c476f4u, 0xffffffffu, 0xffffffffu, 0xffffff0fu, 0xd3b642f2u, 0xd091dc7fu, 0xd3b642f2u, 0x0000000fu, 0x0000027fu, 0x0000001du, 0x00000011u, 0x00000023u, 0x00000002u, 0x00000002u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000007u, 0xffffffcbu, 0x00006dcbu, 0x72148370u, 0x72158370u, 0x00007fffu, 0x72158370u, 0xf0f0f0f0u, 0x72158370u, 0xf0f1f0f0u, 0x000000ffu, 0x0000fdffu, 0x00000000u, 0x4f426dcbu, 0x3cf3f0cfu, 0x30ff300cu, 0x4f426dcbu, 0x4f426dcbu, 0xfe3b890bu, 0x00000000u, 0x00000000u, 0x000000ffu, 0x4f426dcbu, 0xf0f0f0f0u, 0x4f426dcbu, 0xfe3b890bu, 0x4f426dcbu, 0xfe3b890bu, 0x4f426dcbu, 0xfe3b890bu, 0x72158370u, 0x4f426dcbu, 0x4f426dcbu, 0xfe3b890bu, 0xfe3b890bu, 0x4e02090bu, 0x00000000u, 0x0000000fu, 0xfe3b890bu, 0xff7bedcbu, 0xfe3b890bu, 0xb179e4c0u, 0xfe3b890bu, 0x014064c0u, 0xfe3b890bu, 0x4fc67fffu, 0xfe3b890bu, 0xb1fdf6f4u, 0xfe3b890bu, 0x00841234u, 0xfe3b890bu, 0x4e861b3fu, 0x00000000u, 0x0000007fu, 0xfe3b890bu, 0xb0398000u, 0xfe3b890bu, 0xfebf9b3fu, 0xb0398000u, 0xb0398000u, 0x014064c0u, 0x014064c0u, 0x72158370u, 0xf0f0f0f0u, 0x42000140u, 0xf0308000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x72158370u, 0xf0f0f0f0u, 0x7f57effbu, 0xfefbf9fbu, 0x72158370u, 0xf0f0f0f0u, 0x3d57eebbu, 0x0ecb79fbu, 0x72158370u, 0xf0f0f0f0u, 0x0d426c8bu, 0x0e0b090bu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x72158370u, 0xf0f0f0f0u, 0xcfea7dcfu, 0xff3f8f0fu, 0x72158370u, 0xf0f0f0f0u, 0xbdfffebfu, 0x0fcf7fffu, 0x72158370u, 0xf0f0f0f0u, 0x80a81004u, 0x01040604u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x72158370u, 0xf0f0f0f0u, 0xc2a81144u, 0xf1348604u, 0x72158370u, 0xf0f0f0f0u, 0x30158230u, 0x00c070f0u, 0x72158370u, 0xf0f0f0f0u, 0xf2bd9374u, 0xf1f4f6f4u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x30158230u, 0x00c070f0u, 0x30158230u, 0x00c070f0u, 0x0d426c8bu, 0x0e0b090bu, 0x0d426c8bu, 0x0e0b090bu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x69a6cb8eu, 0xffff8001u, 0x4f41edcbu, 0x4f42edcau, 0x1238b69fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x5affb229u, 0xfffffffeu, 0x5affb229u, 0x5affb229u, 0xfffffffeu, 0xa5004dd6u, 0xa5004dd6u, 0x00000001u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x944dff5au, 0x7fffffffu, 0x944dff5au, 0x0000000du, 0x000003ffu, 0x0000000eu, 0x00000013u, 0x00000032u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000001u, 0x0000001fu, 0x00000029u, 0xffffb229u, 0x0000ffffu, 0x8000ffffu, 0x00007fffu, 0x0000ffffu, 0x211c70cfu, 0x0000ffffu, 0xa11c70cfu, 0x000000ffu, 0x0000ffffu, 0x00000000u, 0x5affb229u, 0xcf0c0cc3u, 0x33ccffffu, 0x211c70cfu, 0x0000ffffu, 0x211c70cfu, 0x00000000u, 0x00000000u, 0x000000ffu, 0x0000ffffu, 0x211c70cfu, 0x5affb229u, 0x5affb229u, 0x0000ffffu, 0x211c70cfu, 0x5affb229u, 0xfffffffeu, 0x211c70cfu, 0x211c70cfu, 0x5affb229u, 0xfffffffeu, 0xfffffffeu, 0x5affb228u, 0x00000000u, 0x0000000fu, 0xfffffffeu, 0xffffffffu, 0xfffffffeu, 0xa5004dd7u, 0xfffffffeu, 0x00000001u, 0xfffffffeu, 0x5affb229u, 0xfffffffeu, 0xa5004dd7u, 0xfffffffeu, 0x00000000u, 0xfffffffeu, 0x5affb228u, 0x00000000u, 0x0000007du, 0xfffffffeu, 0xa5004dd6u, 0xfffffffeu, 0xfffffffeu, 0xa5004dd6u, 0xa5004dd6u, 0x00000001u, 0x00000001u, 0x0000ffffu, 0x211c70cfu, 0x0000b229u, 0x211c70ceu, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x0000ffffu, 0x211c70cfu, 0x5affffffu, 0xffffffffu, 0x0000ffffu, 0x211c70cfu, 0x5aff4dd6u, 0xdee38f31u, 0x0000ffffu, 0x211c70cfu, 0x5aff0000u, 0xdee38f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x0000ffffu, 0x211c70cfu, 0xffffb229u, 0xfffffffeu, 0x0000ffffu, 0x211c70cfu, 0xffff4dd6u, 0xdee38f31u, 0x0000ffffu, 0x211c70cfu, 0xa5000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x0000ffffu, 0x211c70cfu, 0xa500b229u, 0x211c70ceu, 0x0000ffffu, 0x211c70cfu, 0x00004dd6u, 0x00000001u, 0x0000ffffu, 0x211c70cfu, 0xa500ffffu, 0x211c70cfu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00004dd6u, 0x00000001u, 0x00004dd6u, 0x00000001u, 0x5aff0000u, 0xdee38f30u, 0x5aff0000u, 0xdee38f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6e38cb8eu, 0xffff8001u, 0x5aff3229u, 0x5b003228u, 0xef00e985u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x7f1b103cu, 0x9abcdef0u, 0x7f1b103cu, 0x7f1b103cu, 0x9abcdef0u, 0x80e4efc3u, 0x80e4efc3u, 0x6543210fu, 0xfffff0ffu, 0xfffff0ffu, 0xfffffff0u, 0x3c08d8feu, 0x0f7b3d59u, 0x3c08d8feu, 0x00000010u, 0x000003ffu, 0x0000001du, 0x00000010u, 0x00000023u, 0x00000000u, 0x00000000u, 0x00000002u, 0x00000002u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000001u, 0x0000003cu, 0x0000103cu, 0x00007fffu, 0x80007fffu, 0x00007fffu, 0x00007fffu, 0x51bc52d9u, 0x00007fffu, 0xd1bc52d9u, 0x000000fbu, 0x0000fefbu, 0x00000000u, 0x7f1b103cu, 0x03000ff0u, 0x3fff03cfu, 0xd1bc52d9u, 0x00007fffu, 0xd1bc52d9u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00007fffu, 0xd1bc52d9u, 0x7f1b103cu, 0x7f1b103cu, 0x00007fffu, 0xd1bc52d9u, 0x7f1b103cu, 0x9abcdef0u, 0xd1bc52d9u, 0xd1bc52d9u, 0x7f1b103cu, 0x9abcdef0u, 0x9abcdef0u, 0x1a181030u, 0x00000000u, 0x0000000fu, 0x9abcdef0u, 0xffbfdefcu, 0x9abcdef0u, 0xe5a7ceccu, 0x9abcdef0u, 0x6503000cu, 0x9abcdef0u, 0x7f5b313fu, 0x9abcdef0u, 0xe5e7efcfu, 0x9abcdef0u, 0x00402103u, 0x9abcdef0u, 0x1a583133u, 0x00000000u, 0x0000007fu, 0x9abcdef0u, 0x80a4cec0u, 0x9abcdef0u, 0x9afcfff3u, 0x80a4cec0u, 0x80a4cec0u, 0x6503000cu, 0x6503000cu, 0x00007fffu, 0xd1bc52d9u, 0x0000103cu, 0x90bc52d0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x00007fffu, 0xd1bc52d9u, 0x7f1b7fffu, 0xdbbcdef9u, 0x00007fffu, 0xd1bc52d9u, 0x7f1b6fc3u, 0x4b008c29u, 0x00007fffu, 0xd1bc52d9u, 0x7f1b0000u, 0x0a008c20u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00007fffu, 0xd1bc52d9u, 0xffff903cu, 0xbefffff6u, 0x00007fffu, 0xd1bc52d9u, 0xffffefc3u, 0x6f43ad2fu, 0x00007fffu, 0xd1bc52d9u, 0x80e48000u, 0x24432106u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00007fffu, 0xd1bc52d9u, 0x80e4903cu, 0xb4ff73d6u, 0x00007fffu, 0xd1bc52d9u, 0x00006fc3u, 0x41000009u, 0x00007fffu, 0xd1bc52d9u, 0x80e4ffffu, 0xf5ff73dfu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00006fc3u, 0x41000009u, 0x00006fc3u, 0x41000009u, 0x7f1b0000u, 0x0a008c20u, 0x7f1b0000u, 0x0a008c20u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6e38cb8eu, 0xffff8001u, 0x7f1a903cu, 0x7f1b903bu, 0x82aecf4cu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0xfc891b4au, 0xf0f0f0f0u, 0x26a2c0bdu, 0x26a2c0bdu, 0x00000020u, 0x0376e4b5u, 0x0376e4b5u, 0x0f0f0f0fu, 0xffffffffu, 0xffffffffu, 0xf0f0f0f0u, 0x52d8913fu, 0x0f0f0f0fu, 0x52d8913fu, 0x00000010u, 0x0000027fu, 0x00000020u, 0x00000010u, 0x00000020u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000006u, 0x00000004u, 0x0000004au, 0x00001b4au, 0x06a2c0bdu, 0x26a2c0bdu, 0x00007fffu, 0x26a2c0bdu, 0x00000020u, 0x26a2c0bdu, 0x20000020u, 0x000000ffu, 0x0000aaffu, 0x00000000u, 0x0376e4b6u, 0x03cf30ccu, 0xfff0c0c3u, 0xf0f0f0f0u, 0xfc891b4au, 0xf0f0f0f0u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x26a2c0bdu, 0xfc891b4au, 0xfc891b4au, 0xf0f0f0f0u, 0xfc891b4au, 0xf0f0f0f0u, 0xfc891b4au, 0xf0f0f0f0u, 0x26a2c0bdu, 0x00000020u, 0xf0f0f0f0u, 0xf0f0f0f0u, 0xf0f0f0f0u, 0xf0801040u, 0x00000000u, 0x0000000fu, 0xf0f0f0f0u, 0xfcf9fbfau, 0xf0f0f0f0u, 0x0c79ebbau, 0xf0f0f0f0u, 0x0c090b0au, 0xf0f0f0f0u, 0xff8f1f4fu, 0xf0f0f0f0u, 0x0f7fefbfu, 0xf0f0f0f0u, 0x03060405u, 0xf0f0f0f0u, 0xf3861445u, 0x00000000u, 0x0000007fu, 0xf0f0f0f0u, 0x0070e0b0u, 0xf0f0f0f0u, 0xf3f6f4f5u, 0x0070e0b0u, 0x0070e0b0u, 0x0c090b0au, 0x0c090b0au, 0x26a2c0bdu, 0x00000020u, 0x24800008u, 0x00000020u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x26a2c0bdu, 0x00000020u, 0xfeabdbffu, 0xf0f0f0f0u, 0x26a2c0bdu, 0x00000020u, 0xda2bdbf7u, 0xf0f0f0d0u, 0x26a2c0bdu, 0x00000020u, 0xd8091b42u, 0xf0f0f0d0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x26a2c0bdu, 0x00000020u, 0xfddd3f4au, 0xffffffffu, 0x26a2c0bdu, 0x00000020u, 0xdb7ffff7u, 0xffffffdfu, 0x26a2c0bdu, 0x00000020u, 0x01542400u, 0x0f0f0f0fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x26a2c0bdu, 0x00000020u, 0x25d42408u, 0x0f0f0f2fu, 0x26a2c0bdu, 0x00000020u, 0x0222c0b5u, 0x00000000u, 0x26a2c0bdu, 0x00000020u, 0x27f6e4bdu, 0x0f0f0f2fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x0222c0b5u, 0x00000000u, 0x0222c0b5u, 0x00000000u, 0xd8091b42u, 0xf0f0f0d0u, 0xd8091b42u, 0xf0f0f0d0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x69b8aa6eu, 0xffff8001u, 0xfc889b4au, 0xfc899b49u, 0x0a64ae22u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x0000003fu, 0x00000000u, 0x0000003fu, 0x0000003fu, 0x00000000u, 0xffffffc0u, 0xffffffc0u, 0xffffffffu, 0x000000ffu, 0x000000ffu, 0x00000000u, 0xfc000000u, 0x00000000u, 0xfc000000u, 0x0000001au, 0x000003ffu, 0x0000003au, 0x00000006u, 0x00000006u, 0x00000006u, 0x00000006u, 0x00000000u, 0x00000000u, 0x0000001au, 0x0000003au, 0x0000001au, 0x0000003au, 0x0000003fu, 0x0000003fu, 0x0f0f0f0fu, 0x0f0f8f0fu, 0x00007fffu, 0x0f0f0f0fu, 0x519088f5u, 0x0f0f8f0fu, 0x519088f5u, 0x00000003u, 0x00000003u, 0x00000000u, 0x0000003fu, 0x00000fffu, 0x00000000u, 0x519088f5u, 0x0f0f0f0fu, 0x519088f5u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x0f0f0f0fu, 0x519088f5u, 0x0000003fu, 0x0000003fu, 0x0f0f0f0fu, 0x519088f5u, 0x0000003fu, 0x00000000u, 0x0f0f0f0fu, 0x519088f5u, 0x0000003fu, 0x519088f5u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000000eu, 0x00000000u, 0x0000003fu, 0x00000000u, 0x0000003fu, 0x00000000u, 0x0000003fu, 0x00000000u, 0xffffffffu, 0x00000000u, 0xffffffffu, 0x00000000u, 0xffffffc0u, 0x00000000u, 0xffffffc0u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffc0u, 0x00000000u, 0x00000000u, 0x0000003fu, 0x0000003fu, 0x0f0f0f0fu, 0x519088f5u, 0x0000000fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000000bu, 0x0f0f0f0fu, 0x519088f5u, 0x0f0f0f3fu, 0x519088f5u, 0x0f0f0f0fu, 0x519088f5u, 0x0f0f0f30u, 0x519088f5u, 0x0f0f0f0fu, 0x519088f5u, 0x00000030u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x0f0f0f0fu, 0x519088f5u, 0xf0f0f0ffu, 0xae6f770au, 0x0f0f0f0fu, 0x519088f5u, 0xfffffff0u, 0xffffffffu, 0x0f0f0f0fu, 0x519088f5u, 0xf0f0f0c0u, 0xae6f770au, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x0f0f0f0fu, 0x519088f5u, 0xf0f0f0cfu, 0xae6f770au, 0x0f0f0f0fu, 0x519088f5u, 0x0f0f0f00u, 0x519088f5u, 0x0f0f0f0fu, 0x519088f5u, 0xffffffcfu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x0f0f0f00u, 0x519088f5u, 0x0f0f0f00u, 0x519088f5u, 0x00000030u, 0x00000000u, 0x00000030u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x69a6d389u, 0xffff8001u, 0xffff803fu, 0x0000803eu, 0xffffff43u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u}, + {0x00000003u, 0x11111111u, 0x00000003u, 0x00000003u, 0x11111111u, 0xfffffffcu, 0xfffffffcu, 0xeeeeeeeeu, 0x0000000fu, 0x0000000fu, 0xffffffffu, 0xc0000000u, 0x88888888u, 0xc0000000u, 0x0000001eu, 0x000003ffu, 0x00000036u, 0x00000002u, 0x0000000au, 0x00000002u, 0x00000002u, 0x00000000u, 0x00000000u, 0x0000001eu, 0x00000003u, 0x0000001eu, 0x00000003u, 0x00000003u, 0x00000003u, 0x00000003u, 0x0000000bu, 0x00007fffu, 0x00000003u, 0x11111111u, 0x0000000bu, 0x11111111u, 0x00000001u, 0x0000ff01u, 0x00000000u, 0x00000003u, 0x0000000fu, 0x00000000u, 0x11111111u, 0x00000003u, 0x11111111u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00000003u, 0x11111111u, 0x00000003u, 0x00000003u, 0x00000003u, 0x11111111u, 0x00000003u, 0x11111111u, 0x11111111u, 0x11111111u, 0x00000003u, 0x11111111u, 0x11111111u, 0x00000001u, 0x00000000u, 0x0000000fu, 0x11111111u, 0x11111113u, 0x11111111u, 0x11111112u, 0x11111111u, 0x00000002u, 0x11111111u, 0xeeeeeeefu, 0x11111111u, 0xfffffffeu, 0x11111111u, 0xeeeeeeecu, 0x11111111u, 0xeeeeeeedu, 0x00000000u, 0x0000007fu, 0x11111111u, 0x11111110u, 0x11111111u, 0xfffffffdu, 0x11111110u, 0x11111110u, 0x00000002u, 0x00000002u, 0x00000003u, 0x11111111u, 0x00000003u, 0x11111111u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x00000003u, 0x11111111u, 0x00000003u, 0x11111111u, 0x00000003u, 0x11111111u, 0x00000000u, 0x00000000u, 0x00000003u, 0x11111111u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000003u, 0x11111111u, 0xffffffffu, 0xffffffffu, 0x00000003u, 0x11111111u, 0xfffffffcu, 0xeeeeeeeeu, 0x00000003u, 0x11111111u, 0xfffffffcu, 0xeeeeeeeeu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000003u, 0x11111111u, 0xffffffffu, 0xffffffffu, 0x00000003u, 0x11111111u, 0x00000000u, 0x00000000u, 0x00000003u, 0x11111111u, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000005u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x12cb5389u, 0xffff8001u, 0xffff8003u, 0x00008002u, 0xfffffff7u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u}, + {0x00000021u, 0x00000003u, 0x00000021u, 0x00000021u, 0x00000003u, 0xffffffdeu, 0xffffffdeu, 0xfffffffcu, 0x000000ffu, 0x000000ffu, 0x0000000fu, 0x84000000u, 0xc0000000u, 0x84000000u, 0x0000001eu, 0x000003ffu, 0x0000003cu, 0x00000002u, 0x00000004u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x0000001au, 0x0000001eu, 0x0000001au, 0x0000001eu, 0x00000021u, 0x00000021u, 0x00000020u, 0x00000021u, 0x00007fffu, 0x00000020u, 0x00000002u, 0x00000020u, 0x00000003u, 0x00000003u, 0x00000103u, 0x00000000u, 0x00000021u, 0x00000c03u, 0x00000000u, 0x00000021u, 0x00000021u, 0x00000003u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00000021u, 0x00000003u, 0x00000021u, 0x00000003u, 0x00000021u, 0x00000003u, 0x00000021u, 0x00000003u, 0x00000021u, 0x00000003u, 0x00000021u, 0x00000003u, 0x00000003u, 0x00000001u, 0x00000000u, 0x0000000fu, 0x00000003u, 0x00000023u, 0x00000003u, 0x00000022u, 0x00000003u, 0x00000020u, 0x00000003u, 0xfffffffdu, 0x00000003u, 0xfffffffeu, 0x00000003u, 0xffffffdcu, 0x00000003u, 0xffffffddu, 0x00000000u, 0x0000007fu, 0x00000003u, 0x00000002u, 0x00000003u, 0xffffffdfu, 0x00000002u, 0x00000002u, 0x00000020u, 0x00000020u, 0x00000020u, 0x00000003u, 0x00000020u, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x00000020u, 0x00000003u, 0x00000021u, 0x00000003u, 0x00000020u, 0x00000003u, 0x00000001u, 0x00000000u, 0x00000020u, 0x00000003u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000020u, 0x00000003u, 0xffffffffu, 0xffffffffu, 0x00000020u, 0x00000003u, 0xffffffdfu, 0xfffffffcu, 0x00000020u, 0x00000003u, 0xffffffdeu, 0xfffffffcu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000020u, 0x00000003u, 0xfffffffeu, 0xffffffffu, 0x00000020u, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000020u, 0x00000003u, 0xfffffffeu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000005u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x2e38ab89u, 0xffff8001u, 0xffff8021u, 0x00008020u, 0xffffff9du, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u}, + {0xf0f0f0f0u, 0xffff8000u, 0xf0f0f0f0u, 0xf0f0f0f0u, 0xfffffffeu, 0x0f0f0f0fu, 0x0f0f0f0fu, 0x00007fffu, 0xf0f0f0f0u, 0xf0f0f0f0u, 0xfffff000u, 0x0f0f0f0fu, 0x0001ffffu, 0x0f0f0f0fu, 0x00000010u, 0x0000027fu, 0x0000001fu, 0x00000010u, 0x00000021u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000004u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000011u, 0xfffffff0u, 0xfffff0f0u, 0xf0f0f0f0u, 0xf0f1f0f0u, 0x00007fffu, 0xf0f0f0f0u, 0xfffefffeu, 0xf0f0f0f0u, 0xfffffffeu, 0x000000aau, 0x0000f8aau, 0x00000000u, 0x0f0f0f10u, 0xff00ff00u, 0xff00ff00u, 0xf0f0f0f0u, 0xf0f0f0f0u, 0xffff8000u, 0x00000000u, 0x00000000u, 0x000000ffu, 0xf0f0f0f0u, 0xfffffffeu, 0xf0f0f0f0u, 0xffff8000u, 0xf0f0f0f0u, 0xffff8000u, 0xf0f0f0f0u, 0xffff8000u, 0xf0f0f0f0u, 0xfffffffeu, 0xf0f0f0f0u, 0xffff8000u, 0xffff8000u, 0xf0f08000u, 0x00000000u, 0x0000000fu, 0xffff8000u, 0xfffff0f0u, 0xffff8000u, 0x0f0f70f0u, 0xffff8000u, 0x000070f0u, 0xffff8000u, 0xf0f0ffffu, 0xffff8000u, 0x0f0f7fffu, 0xffff8000u, 0x00000f0fu, 0xffff8000u, 0xf0f08f0fu, 0x00000000u, 0x0000007fu, 0xffff8000u, 0x0f0f0000u, 0xffff8000u, 0xffff8f0fu, 0x0f0f0000u, 0x0f0f0000u, 0x000070f0u, 0x000070f0u, 0xf0f0f0f0u, 0xfffffffeu, 0xf0f0f0f0u, 0xffff8000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0xf0f0f0f0u, 0xfffffffeu, 0xf0f0f0f0u, 0xfffffffeu, 0xf0f0f0f0u, 0xfffffffeu, 0x00000000u, 0x00007ffeu, 0xf0f0f0f0u, 0xfffffffeu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000006u, 0xf0f0f0f0u, 0xfffffffeu, 0xffffffffu, 0xffff8001u, 0xf0f0f0f0u, 0xfffffffeu, 0x0f0f0f0fu, 0x00007fffu, 0xf0f0f0f0u, 0xfffffffeu, 0x0f0f0f0fu, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xf0f0f0f0u, 0xfffffffeu, 0xffffffffu, 0xffff8001u, 0xf0f0f0f0u, 0xfffffffeu, 0x00000000u, 0x00007ffeu, 0xf0f0f0f0u, 0xfffffffeu, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000000u, 0x00007ffeu, 0x00000000u, 0x00007ffeu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x52caca6eu, 0xffff8001u, 0xf0f070f0u, 0xf0f170efu, 0x2d2d2d30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x7afb2c68u, 0x80000000u, 0x00010003u, 0x00010003u, 0x7a86f7a2u, 0x8504d397u, 0x8504d397u, 0x7fffffffu, 0xffffffffu, 0xffffffffu, 0xf0000000u, 0x1634df5eu, 0x00000001u, 0x1634df5eu, 0x0000000eu, 0x0000027fu, 0x0000002du, 0x00000012u, 0x00000013u, 0x00000000u, 0x00000000u, 0x00000003u, 0x00000003u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000001u, 0x00000068u, 0x00002c68u, 0x00010003u, 0x0001000bu, 0x00007fffu, 0x00010003u, 0x7a86f7a2u, 0x0001000bu, 0x7a86f7a2u, 0x000000ffu, 0x000080ffu, 0x00000000u, 0x7afb2c68u, 0x0cf03cc0u, 0x3fccffcfu, 0x7a86f7a2u, 0x00010003u, 0x7a86f7a2u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00010003u, 0x7a86f7a2u, 0x7afb2c68u, 0x7afb2c68u, 0x00010003u, 0x7a86f7a2u, 0x7afb2c68u, 0x80000000u, 0x00010003u, 0x7a86f7a2u, 0x7afb2c68u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x0000000eu, 0x80000000u, 0xfafb2c68u, 0x80000000u, 0xfafb2c68u, 0x80000000u, 0x7afb2c68u, 0x80000000u, 0x7fffffffu, 0x80000000u, 0xffffffffu, 0x80000000u, 0x0504d397u, 0x80000000u, 0x0504d397u, 0x00000000u, 0x0000007fu, 0x80000000u, 0x80000000u, 0x80000000u, 0x8504d397u, 0x80000000u, 0x80000000u, 0x7afb2c68u, 0x7afb2c68u, 0x00010003u, 0x7a86f7a2u, 0x00010000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x00010003u, 0x7a86f7a2u, 0x7afb2c6bu, 0xfa86f7a2u, 0x00010003u, 0x7a86f7a2u, 0x7afa2c6bu, 0xfa86f7a2u, 0x00010003u, 0x7a86f7a2u, 0x7afa2c68u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00010003u, 0x7a86f7a2u, 0xfffffffcu, 0x8579085du, 0x00010003u, 0x7a86f7a2u, 0xfffeffffu, 0xffffffffu, 0x00010003u, 0x7a86f7a2u, 0x8504d394u, 0x0579085du, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00010003u, 0x7a86f7a2u, 0x8505d394u, 0x0579085du, 0x00010003u, 0x7a86f7a2u, 0x00000003u, 0x7a86f7a2u, 0x00010003u, 0x7a86f7a2u, 0x8505d397u, 0x7fffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000003u, 0x7a86f7a2u, 0x00000003u, 0x7a86f7a2u, 0x7afa2c68u, 0x80000000u, 0x7afa2c68u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6e38ab8eu, 0xffff8001u, 0x7afaac68u, 0x7afbac67u, 0x8f0e7ac8u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0xaaaaaaaau, 0x00000001u, 0xaaaaaaaau, 0xaaaaaaaau, 0xdd02de92u, 0x55555555u, 0x55555555u, 0xfffffffeu, 0xffffffffu, 0xffffffffu, 0x0000000fu, 0x55555555u, 0x80000000u, 0x55555555u, 0x00000010u, 0x0000027fu, 0x0000002fu, 0x00000010u, 0x00000011u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000001u, 0x00000000u, 0x0000001fu, 0x00000001u, 0x0000001fu, 0xffffffaau, 0xffffaaaau, 0xaaaaaaaau, 0xaaaaaeaau, 0x00007fffu, 0xaaaaaaaau, 0xdd02da92u, 0xaaaaaaaau, 0xdd02de92u, 0x000000ffu, 0x000001ffu, 0x00000000u, 0x55555556u, 0xccccccccu, 0xccccccccu, 0xaaaaaaaau, 0xaaaaaaaau, 0xdd02de92u, 0x00000000u, 0x00000000u, 0x000000ffu, 0xaaaaaaaau, 0xdd02de92u, 0xaaaaaaaau, 0x00000001u, 0xaaaaaaaau, 0xdd02de92u, 0xaaaaaaaau, 0x00000001u, 0xaaaaaaaau, 0xdd02de92u, 0xaaaaaaaau, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x0000000eu, 0x00000001u, 0xaaaaaaabu, 0x00000001u, 0xaaaaaaabu, 0x00000001u, 0xaaaaaaaau, 0x00000001u, 0xfffffffeu, 0x00000001u, 0xffffffffu, 0x00000001u, 0x55555554u, 0x00000001u, 0x55555554u, 0x00000000u, 0x0000007fu, 0x00000001u, 0x00000001u, 0x00000001u, 0x55555555u, 0x00000001u, 0x00000001u, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau, 0xdd02de92u, 0xaaaaaaaau, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0xaaaaaaaau, 0xdd02de92u, 0xaaaaaaaau, 0xdd02de93u, 0xaaaaaaaau, 0xdd02de92u, 0x00000000u, 0xdd02de93u, 0xaaaaaaaau, 0xdd02de92u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xaaaaaaaau, 0xdd02de92u, 0xffffffffu, 0x22fd216du, 0xaaaaaaaau, 0xdd02de92u, 0x55555555u, 0xffffffffu, 0xaaaaaaaau, 0xdd02de92u, 0x55555555u, 0x22fd216cu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xaaaaaaaau, 0xdd02de92u, 0xffffffffu, 0x22fd216cu, 0xaaaaaaaau, 0xdd02de92u, 0x00000000u, 0xdd02de92u, 0xaaaaaaaau, 0xdd02de92u, 0xffffffffu, 0xfffffffeu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000000u, 0xdd02de92u, 0x00000000u, 0xdd02de92u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x72cad26eu, 0xffff8001u, 0xaaaa2aaau, 0xaaab2aa9u, 0x00000002u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x00000021u, 0x00010000u, 0x55555555u, 0x55555555u, 0x00010000u, 0xffffffdeu, 0xffffffdeu, 0xfffeffffu, 0x000000ffu, 0x000000ffu, 0x000f0000u, 0x84000000u, 0x00008000u, 0x84000000u, 0x0000001eu, 0x0000027fu, 0x0000003du, 0x00000002u, 0x00000003u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x0000001au, 0x0000000fu, 0x0000001au, 0x0000000fu, 0x00000021u, 0x00000021u, 0x55555555u, 0x55755555u, 0x00007fffu, 0x55555555u, 0x00010000u, 0x55755555u, 0x00010000u, 0x00000003u, 0x00001003u, 0x00000000u, 0x00000021u, 0x00000c03u, 0x00000000u, 0x00010000u, 0x00000021u, 0x00010000u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x55555555u, 0x00000021u, 0x00000021u, 0x00010000u, 0x00000021u, 0x00010000u, 0x00000021u, 0x00010000u, 0x55555555u, 0x00010000u, 0x00000021u, 0x00010000u, 0x00010000u, 0x00000000u, 0x00000000u, 0x0000000eu, 0x00010000u, 0x00010021u, 0x00010000u, 0x00010021u, 0x00010000u, 0x00000021u, 0x00010000u, 0xfffeffffu, 0x00010000u, 0xffffffffu, 0x00010000u, 0xfffeffdeu, 0x00010000u, 0xfffeffdeu, 0x00000000u, 0x0000007fu, 0x00010000u, 0x00010000u, 0x00010000u, 0xffffffdeu, 0x00010000u, 0x00010000u, 0x00000021u, 0x00000021u, 0x55555555u, 0x00010000u, 0x00000001u, 0x00010000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x55555555u, 0x00010000u, 0x55555575u, 0x00010000u, 0x55555555u, 0x00010000u, 0x55555574u, 0x00000000u, 0x55555555u, 0x00010000u, 0x00000020u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x55555555u, 0x00010000u, 0xaaaaaaabu, 0xffffffffu, 0x55555555u, 0x00010000u, 0xfffffffeu, 0xfffeffffu, 0x55555555u, 0x00010000u, 0xaaaaaa8au, 0xfffeffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x55555555u, 0x00010000u, 0xaaaaaa8bu, 0xffffffffu, 0x55555555u, 0x00010000u, 0x55555554u, 0x00000000u, 0x55555555u, 0x00010000u, 0xffffffdfu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x55555554u, 0x00000000u, 0x55555554u, 0x00000000u, 0x00000020u, 0x00000000u, 0x00000020u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x69a6d389u, 0xffff8001u, 0xffff8021u, 0x00008020u, 0xffffff9du, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u}, + {0x31f51707u, 0xd17e4497u, 0x31f51707u, 0x31f51707u, 0xd17e4497u, 0xce0ae8f8u, 0xce0ae8f8u, 0x2e81bb68u, 0xffffff0fu, 0xffffff0fu, 0xffffffffu, 0xe0e8af8cu, 0xe9227e8bu, 0xe0e8af8cu, 0x00000010u, 0x000003ffu, 0x0000001fu, 0x00000010u, 0x00000021u, 0x00000003u, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000002u, 0x00000000u, 0x00000002u, 0x00000002u, 0x00000007u, 0x00001707u, 0x00010000u, 0x00010001u, 0x00007fffu, 0x00010000u, 0xd17e4497u, 0x00010001u, 0xd17e4497u, 0x000000fdu, 0x0000fffdu, 0x00000000u, 0x31f51707u, 0x033f003fu, 0x0f03ff33u, 0x31f51707u, 0x31f51707u, 0xd17e4497u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x31f51707u, 0xd17e4497u, 0x31f51707u, 0xd17e4497u, 0x31f51707u, 0xd17e4497u, 0x31f51707u, 0xd17e4497u, 0x00010000u, 0x31f51707u, 0x31f51707u, 0xd17e4497u, 0xd17e4497u, 0x11740407u, 0x00000000u, 0x0000000fu, 0xd17e4497u, 0xf1ff5797u, 0xd17e4497u, 0xe08b5390u, 0xd17e4497u, 0x20811300u, 0xd17e4497u, 0x3ff5bf6fu, 0xd17e4497u, 0xee8bfbf8u, 0xd17e4497u, 0x0e00a868u, 0xd17e4497u, 0x1f74ac6fu, 0x00000000u, 0x0000007fu, 0xd17e4497u, 0xc00a4090u, 0xd17e4497u, 0xdf7eecffu, 0xc00a4090u, 0xc00a4090u, 0x20811300u, 0x20811300u, 0x00010000u, 0xd17e4497u, 0x00010000u, 0xd17e4497u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x00010000u, 0xd17e4497u, 0x31f51707u, 0xd17e4497u, 0x00010000u, 0xd17e4497u, 0x31f41707u, 0x00000000u, 0x00010000u, 0xd17e4497u, 0x31f41707u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00010000u, 0xd17e4497u, 0xffffffffu, 0xffffffffu, 0x00010000u, 0xd17e4497u, 0xfffeffffu, 0x2e81bb68u, 0x00010000u, 0xd17e4497u, 0xce0ae8f8u, 0x2e81bb68u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00010000u, 0xd17e4497u, 0xce0be8f8u, 0xffffffffu, 0x00010000u, 0xd17e4497u, 0x00000000u, 0x00000000u, 0x00010000u, 0xd17e4497u, 0xce0be8f8u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000005u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x31f41707u, 0x00000000u, 0x31f41707u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x2e38ab8eu, 0xffff8001u, 0x31f49707u, 0x31f59706u, 0x6a20baebu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x00007fffu, 0x00000040u, 0x00007fffu, 0x00007fffu, 0x00000040u, 0xffff8000u, 0xffff8000u, 0xffffffbfu, 0x0000ffffu, 0x0000ffffu, 0x000000f0u, 0xfffe0000u, 0x02000000u, 0xfffe0000u, 0x00000011u, 0x000003ffu, 0x00000030u, 0x0000000fu, 0x00000010u, 0x0000000fu, 0x0000000fu, 0x00000000u, 0x00000000u, 0x00000011u, 0x00000019u, 0x00000011u, 0x00000019u, 0xffffffffu, 0x00007fffu, 0xf4de2c08u, 0xf4de2d08u, 0x00007fffu, 0xf4de2c08u, 0x00020001u, 0xf4de2d08u, 0x00020001u, 0x0000000fu, 0x0000020fu, 0x00000000u, 0x00007fffu, 0x3fffffffu, 0x00000000u, 0x00007fffu, 0x00007fffu, 0x00000040u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00007fffu, 0x00020001u, 0x00007fffu, 0x00000040u, 0x00007fffu, 0x00000040u, 0x00007fffu, 0x00000040u, 0xf4de2c08u, 0x00020001u, 0x00007fffu, 0x00000040u, 0x00000040u, 0x00000040u, 0x00000000u, 0x0000000fu, 0x00000040u, 0x00007fffu, 0x00000040u, 0x00007fbfu, 0x00000040u, 0x00007fbfu, 0x00000040u, 0xffffffffu, 0x00000040u, 0xffffffbfu, 0x00000040u, 0xffff8000u, 0x00000040u, 0xffff8040u, 0x00000000u, 0x0000007fu, 0x00000040u, 0x00000000u, 0x00000040u, 0xffff8040u, 0x00000000u, 0x00000000u, 0x00007fbfu, 0x00007fbfu, 0xf4de2c08u, 0x00020001u, 0x00002c08u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000000bu, 0xf4de2c08u, 0x00020001u, 0xf4de7fffu, 0x00020041u, 0xf4de2c08u, 0x00020001u, 0xf4de53f7u, 0x00020041u, 0xf4de2c08u, 0x00020001u, 0x000053f7u, 0x00000040u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xf4de2c08u, 0x00020001u, 0x0b21ffffu, 0xfffdfffeu, 0xf4de2c08u, 0x00020001u, 0xffffd3f7u, 0xffffffffu, 0xf4de2c08u, 0x00020001u, 0x0b218000u, 0xfffdffbeu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xf4de2c08u, 0x00020001u, 0x0b21ac08u, 0xfffdffbeu, 0xf4de2c08u, 0x00020001u, 0xf4de0000u, 0x00020001u, 0xf4de2c08u, 0x00020001u, 0xffffac08u, 0xffffffbfu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xf4de0000u, 0x00020001u, 0xf4de0000u, 0x00020001u, 0x000053f7u, 0x00000040u, 0x000053f7u, 0x00000040u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6e26a988u, 0xffff8001u, 0xffffffffu, 0x0000fffeu, 0xfffe8003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u}, + {0x00010000u, 0x00010003u, 0x00010000u, 0x00010000u, 0x00010003u, 0xfffeffffu, 0xfffeffffu, 0xfffefffcu, 0x000f0000u, 0x000f0000u, 0x000f000fu, 0x00008000u, 0xc0008000u, 0x00008000u, 0x0000001fu, 0x000003ffu, 0x0000003cu, 0x00000001u, 0x00000004u, 0x00000000u, 0x00000000u, 0x00000010u, 0x00000010u, 0x0000000fu, 0x0000000fu, 0x0000000fu, 0x0000000fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00007fffu, 0x00000000u, 0x00010003u, 0x00000001u, 0x00010003u, 0x00000010u, 0x00001110u, 0x00000000u, 0x00010000u, 0x00000000u, 0x00000003u, 0x00010000u, 0x00010000u, 0x00010003u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00010000u, 0x00010003u, 0x00010000u, 0x00010003u, 0x00010000u, 0x00010003u, 0x00010000u, 0x00010003u, 0x00010000u, 0x00010003u, 0x00010000u, 0x00010003u, 0x00010003u, 0x00010000u, 0x00000000u, 0x0000000fu, 0x00010003u, 0x00010003u, 0x00010003u, 0x00000003u, 0x00010003u, 0x00000000u, 0x00010003u, 0xfffffffcu, 0x00010003u, 0xfffeffffu, 0x00010003u, 0xfffefffcu, 0x00010003u, 0xfffffffcu, 0x00000000u, 0x0000006fu, 0x00010003u, 0x00000003u, 0x00010003u, 0xffffffffu, 0x00000003u, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00010003u, 0x00000000u, 0x00010003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001du, 0x00000000u, 0x00010003u, 0x00010000u, 0x00010003u, 0x00000000u, 0x00010003u, 0x00010000u, 0x00000000u, 0x00000000u, 0x00010003u, 0x00010000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000000u, 0x00010003u, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00010003u, 0xffffffffu, 0xfffefffcu, 0x00000000u, 0x00010003u, 0xfffeffffu, 0xfffefffcu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000000u, 0x00010003u, 0xfffeffffu, 0xffffffffu, 0x00000000u, 0x00010003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00010003u, 0xfffeffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000005u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00010000u, 0x00000000u, 0x00010000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x2e38d38eu, 0xffff8001u, 0x00008000u, 0x00017fffu, 0xfffd0000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0xe8e72789u, 0x00000040u, 0xe8e72789u, 0xe8e72789u, 0x00000040u, 0x1718d876u, 0x1718d876u, 0xffffffbfu, 0xffffffffu, 0xffffffffu, 0x000000f0u, 0x91e4e717u, 0x02000000u, 0x91e4e717u, 0x0000000fu, 0x000003ffu, 0x0000002eu, 0x00000011u, 0x00000012u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u, 0x00000003u, 0x00000019u, 0xffffff89u, 0x00002789u, 0xe8e72589u, 0xe8e72789u, 0x00007fffu, 0xe8e72589u, 0x00010003u, 0xe8e72789u, 0x00010003u, 0x000000ffu, 0x000002ffu, 0x00000000u, 0x1718d877u, 0x0c3fc0c3u, 0xfcc0fc3fu, 0x00000040u, 0xe8e72789u, 0x00000040u, 0x00000000u, 0x00000000u, 0x000000ffu, 0xe8e72789u, 0xe8e72789u, 0xe8e72789u, 0x00000040u, 0xe8e72789u, 0x00000040u, 0xe8e72789u, 0x00000040u, 0xe8e72789u, 0x00010003u, 0xe8e72789u, 0x00000040u, 0x00000040u, 0x00000000u, 0x00000000u, 0x0000000eu, 0x00000040u, 0xe8e727c9u, 0x00000040u, 0xe8e727c9u, 0x00000040u, 0xe8e72789u, 0x00000040u, 0xffffffbfu, 0x00000040u, 0xffffffffu, 0x00000040u, 0x1718d836u, 0x00000040u, 0x1718d836u, 0x00000000u, 0x0000007fu, 0x00000040u, 0x00000040u, 0x00000040u, 0x1718d876u, 0x00000040u, 0x00000040u, 0xe8e72789u, 0xe8e72789u, 0xe8e72789u, 0x00010003u, 0xe8e72789u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0xe8e72789u, 0x00010003u, 0xe8e72789u, 0x00010043u, 0xe8e72789u, 0x00010003u, 0x00000000u, 0x00010043u, 0xe8e72789u, 0x00010003u, 0x00000000u, 0x00000040u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xe8e72789u, 0x00010003u, 0xffffffffu, 0xfffefffcu, 0xe8e72789u, 0x00010003u, 0x1718d876u, 0xffffffffu, 0xe8e72789u, 0x00010003u, 0x1718d876u, 0xfffeffbcu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xe8e72789u, 0x00010003u, 0xffffffffu, 0xfffeffbcu, 0xe8e72789u, 0x00010003u, 0x00000000u, 0x00010003u, 0xe8e72789u, 0x00010003u, 0xffffffffu, 0xffffffbfu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000000u, 0x00010003u, 0x00000000u, 0x00010003u, 0x00000000u, 0x00000040u, 0x00000000u, 0x00000040u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x72caaa6eu, 0xffff8001u, 0xe8e6a789u, 0xe8e7a788u, 0x454a8965u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x7691b06fu, 0x80000000u, 0x7691b06fu, 0x7691b06fu, 0x80000000u, 0x896e4f90u, 0x896e4f90u, 0x7fffffffu, 0xfffff0ffu, 0xfffff0ffu, 0xf0000000u, 0xf60d896eu, 0x00000001u, 0xf60d896eu, 0x0000000fu, 0x000003ffu, 0x0000002eu, 0x00000011u, 0x00000012u, 0x00000004u, 0x00000004u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000001u, 0x0000006fu, 0xffffb06fu, 0x2b855c1fu, 0xab855c1fu, 0x00007fffu, 0x2b855c1fu, 0x070d7109u, 0xab855c1fu, 0x070d7109u, 0x000000fbu, 0x000080fbu, 0x00000000u, 0x7691b06fu, 0xcf003cffu, 0x3f3cc303u, 0x070d7109u, 0x2b855c1fu, 0x070d7109u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x2b855c1fu, 0x070d7109u, 0x7691b06fu, 0x7691b06fu, 0x2b855c1fu, 0x070d7109u, 0x7691b06fu, 0x80000000u, 0x2b855c1fu, 0x070d7109u, 0x7691b06fu, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x0000000eu, 0x80000000u, 0xf691b06fu, 0x80000000u, 0xf691b06fu, 0x80000000u, 0x7691b06fu, 0x80000000u, 0x7fffffffu, 0x80000000u, 0xffffffffu, 0x80000000u, 0x096e4f90u, 0x80000000u, 0x096e4f90u, 0x00000000u, 0x0000007fu, 0x80000000u, 0x80000000u, 0x80000000u, 0x896e4f90u, 0x80000000u, 0x80000000u, 0x7691b06fu, 0x7691b06fu, 0x2b855c1fu, 0x070d7109u, 0x2281100fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x2b855c1fu, 0x070d7109u, 0x7f95fc7fu, 0x870d7109u, 0x2b855c1fu, 0x070d7109u, 0x5d14ec70u, 0x870d7109u, 0x2b855c1fu, 0x070d7109u, 0x5410a060u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x2b855c1fu, 0x070d7109u, 0xf6fbb3efu, 0xf8f28ef6u, 0x2b855c1fu, 0x070d7109u, 0xdd7eeff0u, 0xffffffffu, 0x2b855c1fu, 0x070d7109u, 0x806a0380u, 0x78f28ef6u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x2b855c1fu, 0x070d7109u, 0xa2eb138fu, 0x78f28ef6u, 0x2b855c1fu, 0x070d7109u, 0x09044c10u, 0x070d7109u, 0x2b855c1fu, 0x070d7109u, 0xabef5f9fu, 0x7fffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x09044c10u, 0x070d7109u, 0x09044c10u, 0x070d7109u, 0x5410a060u, 0x80000000u, 0x5410a060u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6e38d38eu, 0xffff8001u, 0x7691306fu, 0x7692306eu, 0x9c4aeeb3u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x256badf9u, 0x988af3fbu, 0x256badf9u, 0x256badf9u, 0x988af3fbu, 0xda945206u, 0xda945206u, 0x67750c04u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x9fb5d6a4u, 0xdfcf5119u, 0x9fb5d6a4u, 0x0000000du, 0x000003ffu, 0x0000001au, 0x00000013u, 0x00000026u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000002u, 0x00000000u, 0x00000002u, 0x00000001u, 0xfffffff9u, 0xffffadf9u, 0xa842bc19u, 0xaa42bc19u, 0x00007fffu, 0xa842bc19u, 0x27e9e06fu, 0xaa42bc19u, 0x27e9e06fu, 0x000000ffu, 0x0000ffffu, 0x00000000u, 0x256badf9u, 0xccf3ffc3u, 0x0c333ccfu, 0x988af3fbu, 0x256badf9u, 0x988af3fbu, 0x00000000u, 0x00000000u, 0x000000ffu, 0xa842bc19u, 0x256badf9u, 0x256badf9u, 0x988af3fbu, 0x256badf9u, 0x988af3fbu, 0x256badf9u, 0x988af3fbu, 0xa842bc19u, 0x27e9e06fu, 0x988af3fbu, 0x988af3fbu, 0x988af3fbu, 0x000aa1f9u, 0x00000000u, 0x0000000fu, 0x988af3fbu, 0xbdebfffbu, 0x988af3fbu, 0xbde15e02u, 0x988af3fbu, 0x25610c00u, 0x988af3fbu, 0x677fadfdu, 0x988af3fbu, 0xfff55e06u, 0x988af3fbu, 0x42140004u, 0x988af3fbu, 0x421ea1fdu, 0x00000000u, 0x0000007fu, 0x988af3fbu, 0x98805202u, 0x988af3fbu, 0xda9ef3ffu, 0x98805202u, 0x98805202u, 0x25610c00u, 0x25610c00u, 0xa842bc19u, 0x27e9e06fu, 0x2042ac19u, 0x0088e06bu, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0xa842bc19u, 0x27e9e06fu, 0xad6bbdf9u, 0xbfebf3ffu, 0xa842bc19u, 0x27e9e06fu, 0x8d2911e0u, 0xbf631394u, 0xa842bc19u, 0x27e9e06fu, 0x052901e0u, 0x98021390u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xa842bc19u, 0x27e9e06fu, 0x77ffefffu, 0xd89efffbu, 0xa842bc19u, 0x27e9e06fu, 0xdfbd53e6u, 0xff771f94u, 0xa842bc19u, 0x27e9e06fu, 0x52944206u, 0x40140c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xa842bc19u, 0x27e9e06fu, 0x72d6ee1fu, 0x409cec6bu, 0xa842bc19u, 0x27e9e06fu, 0x88001000u, 0x27610004u, 0xa842bc19u, 0x27e9e06fu, 0xfad6fe1fu, 0x67fdec6fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x88001000u, 0x27610004u, 0x88001000u, 0x27610004u, 0x052901e0u, 0x98021390u, 0x052901e0u, 0x98021390u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6e26d38eu, 0xffff8001u, 0x256b2df9u, 0x256c2df8u, 0x8fbcf615u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u} +}; +constexpr const char* Names[176] = {"mov_b64 s12", "mov_b64 s13", "cmov_b32 s12", "cmov_b64 s12", "cmov_b64 s13", "not_b32 s12", "not_b64 s12", "not_b64 s13", "wqm_b32 s12", "wqm_b64 s12", "wqm_b64 s13", "brev_b32 s12", "brev_b64 s12", "brev_b64 s13", "bcnt0_i32_b32 s12", "scc mov_b64,cmov_b32,cmov_b64,not_b32,not_b64,wqm_b32,wqm_b64,brev_b32,brev_b64,bcnt0_i32_b32", "bcnt0_i32_b64 s12", "bcnt1_i32_b32 s12", "bcnt1_i32_b64 s12", "ff0_i32_b32 s12", "ff0_i32_b64 s12", "ff1_i32_b32 s12", "ff1_i32_b64 s12", "flbit_i32_b32 s12", "flbit_i32_b64 s12", "flbit_i32 s12", "flbit_i32_i64 s12", "sext_i32_i8 s12", "sext_i32_i16 s12", "bitset0_b32 s12", "bitset1_b32 s12", "scc bcnt0_i32_b64,bcnt1_i32_b32,bcnt1_i32_b64,ff0_i32_b32,ff0_i32_b64,ff1_i32_b32,ff1_i32_b64,flbit_i32_b32,flbit_i32_b64,flbit_i32,flbit_i32_i64,sext_i32_i8,sext_i32_i16,bitset0_b32,bitset1_b32", "bitset0_b64 s12", "bitset0_b64 s13", "bitset1_b64 s12", "bitset1_b64 s13", "quadmask_b32 s12", "quadmask_b64 s12", "quadmask_b64 s13", "abs_i32 s12", "bitreplicate_b64_b32 s12", "bitreplicate_b64_b32 s13", "movrels_b32 s12", "movrels_b64 s12", "movrels_b64 s13", nullptr, nullptr, "scc bitset0_b64,bitset1_b64,quadmask_b32,quadmask_b64,abs_i32,bitreplicate_b64_b32,movrels_b32,movrels_b64", "movreld_b32 s12", "movreld_b32 s13", "movreld_b32 s14", "movreld_b32 s15", "movreld_b64 s12", "movreld_b64 s13", "movreld_b64 s14", "movreld_b64 s15", "movrelsd_2_b32 s12", "movrelsd_2_b32 s13", "movrelsd_2_b32 s14", "movrelsd_2_b32 s15", "and_saveexec_b32 s12", "and_saveexec_b32 s13", nullptr, "scc movreld_b32,movreld_b64,movrelsd_2_b32,and_saveexec_b32", "or_saveexec_b32 s12", "or_saveexec_b32 s13", "xor_saveexec_b32 s12", "xor_saveexec_b32 s13", "andn2_saveexec_b32 s12", "andn2_saveexec_b32 s13", "orn2_saveexec_b32 s12", "orn2_saveexec_b32 s13", "nand_saveexec_b32 s12", "nand_saveexec_b32 s13", "nor_saveexec_b32 s12", "nor_saveexec_b32 s13", "xnor_saveexec_b32 s12", "xnor_saveexec_b32 s13", nullptr, "scc or_saveexec_b32,xor_saveexec_b32,andn2_saveexec_b32,orn2_saveexec_b32,nand_saveexec_b32,nor_saveexec_b32,xnor_saveexec_b32", "andn1_saveexec_b32 s12", "andn1_saveexec_b32 s13", "orn1_saveexec_b32 s12", "orn1_saveexec_b32 s13", "andn1_wrexec_b32 s12", "andn1_wrexec_b32 s13", "andn2_wrexec_b32 s12", "andn2_wrexec_b32 s13", "and_saveexec_b64 s12", "and_saveexec_b64 s13", "and_saveexec_b64 s14", "and_saveexec_b64 s15", nullptr, nullptr, nullptr, "scc andn1_saveexec_b32,orn1_saveexec_b32,andn1_wrexec_b32,andn2_wrexec_b32,and_saveexec_b64", "or_saveexec_b64 s12", "or_saveexec_b64 s13", "or_saveexec_b64 s14", "or_saveexec_b64 s15", "xor_saveexec_b64 s12", "xor_saveexec_b64 s13", "xor_saveexec_b64 s14", "xor_saveexec_b64 s15", "andn2_saveexec_b64 s12", "andn2_saveexec_b64 s13", "andn2_saveexec_b64 s14", "andn2_saveexec_b64 s15", nullptr, nullptr, nullptr, "scc or_saveexec_b64,xor_saveexec_b64,andn2_saveexec_b64", "orn2_saveexec_b64 s12", "orn2_saveexec_b64 s13", "orn2_saveexec_b64 s14", "orn2_saveexec_b64 s15", "nand_saveexec_b64 s12", "nand_saveexec_b64 s13", "nand_saveexec_b64 s14", "nand_saveexec_b64 s15", "nor_saveexec_b64 s12", "nor_saveexec_b64 s13", "nor_saveexec_b64 s14", "nor_saveexec_b64 s15", nullptr, nullptr, nullptr, "scc orn2_saveexec_b64,nand_saveexec_b64,nor_saveexec_b64", "xnor_saveexec_b64 s12", "xnor_saveexec_b64 s13", "xnor_saveexec_b64 s14", "xnor_saveexec_b64 s15", "andn1_saveexec_b64 s12", "andn1_saveexec_b64 s13", "andn1_saveexec_b64 s14", "andn1_saveexec_b64 s15", "orn1_saveexec_b64 s12", "orn1_saveexec_b64 s13", "orn1_saveexec_b64 s14", "orn1_saveexec_b64 s15", nullptr, nullptr, nullptr, "scc xnor_saveexec_b64,andn1_saveexec_b64,orn1_saveexec_b64", "andn1_wrexec_b64 s12", "andn1_wrexec_b64 s13", "andn1_wrexec_b64 s14", "andn1_wrexec_b64 s15", "andn2_wrexec_b64 s12", "andn2_wrexec_b64 s13", "andn2_wrexec_b64 s14", "andn2_wrexec_b64 s15", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "scc andn1_wrexec_b64,andn2_wrexec_b64,cmp_eq_i32,cmp_lg_i32,cmp_gt_i32,cmp_ge_i32,cmp_lt_i32,cmp_le_i32,cmp_eq_u32,cmp_lg_u32,cmp_gt_u32,cmp_ge_u32,cmp_lt_u32,cmp_le_u32,cmp_eq_u64,cmp_lg_u64,bitcmp0_b32,bitcmp1_b32,bitcmp0_b64,bitcmp1_b64,cmpk_eq_i32,cmpk_lg_i32,cmpk_gt_i32,cmpk_ge_i32,cmpk_lt_i32,cmpk_le_i32,cmpk_eq_u32,cmpk_lg_u32,cmpk_gt_u32,cmpk_ge_u32,cmpk_lt_u32", "movk_i32 s12", "addk_i32 0x8000 s12", "addk_i32 0x7fff s12", "mulk_i32 s12", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "scc cmpk_le_u32,movk_i32,addk_i32,addk_i32,mulk_i32"}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("scalar sop1: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < Results; ++i) { + if (Names[i] != nullptr) Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("scalar sop1 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index 4be3e2626..740f6ec07 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -230,7 +230,7 @@ private: bool vCndmaskB32(const RdnaInstruction& inst); bool packB16(const RdnaInstruction& inst, bool high0, bool high1); void sSubvectorLoop(const RdnaInstruction& inst, bool begin); - void sSaveexec(const RdnaInstruction& inst, IrOpcode operation, bool negateExec, bool negateSource, bool write64, bool negateResult = false, bool writeDestination = true); + void sSaveexec(const RdnaInstruction& inst, IrOpcode operation, bool negateExec, bool negateSource, bool write64, bool negateResult = false, bool writeResult = false); void addU32(const RdnaInstruction& inst, bool vector, bool useCarryIn); void subU32(const RdnaInstruction& inst, bool vector, bool reverse); void subbU32(const RdnaInstruction& inst, bool vector, bool reverse); diff --git a/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp b/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp index 48fedcf5c..346937e58 100644 --- a/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp +++ b/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp @@ -25,17 +25,17 @@ bool SaveexecWritesDestinationFirst() { return writeFirst; } -void TranslationContext::sSaveexec(const RdnaInstruction& inst, IrOpcode operation, bool negateExec, bool negateSource, bool write64, bool negateResult, bool writeDestination) { +void TranslationContext::sSaveexec(const RdnaInstruction& inst, IrOpcode operation, bool negateExec, bool negateSource, bool write64, bool negateResult, bool writeResult) { // Callers name the lane-wise operation; the exec mask words themselves combine bitwise. if (operation == IrOpcode::LogicalAnd) operation = IrOpcode::BitwiseAnd32; else if (operation == IrOpcode::LogicalOr) operation = IrOpcode::BitwiseOr32; else if (operation == IrOpcode::LogicalXor) operation = IrOpcode::BitwiseXor32; - const bool writeFirst = SaveexecWritesDestinationFirst() && writeDestination; + const bool writeFirst = SaveexecWritesDestinationFirst() && !writeResult; if (write64) { const std::array oldExec{IrU32(ir.GetExecLo()), IrU32(ir.GetExecHi())}; if (writeFirst) writeU32Pair(inst.destination, oldExec); const std::array source = readU32Pair(sourceAt(inst, 0u)); - if (!writeFirst && writeDestination) writeU32Pair(inst.destination, oldExec); + if (!writeFirst && !writeResult) writeU32Pair(inst.destination, oldExec); IrValue& lowExecOperand = negateExec ? ir.BitwiseNot(oldExec[0].Value()) : oldExec[0].Value(); IrValue& lowSourceOperand = negateSource ? ir.BitwiseNot(source[0].Value()) : source[0].Value(); IrValue& highExecOperand = negateExec ? ir.BitwiseNot(oldExec[1].Value()) : oldExec[1].Value(); @@ -46,6 +46,7 @@ void TranslationContext::sSaveexec(const RdnaInstruction& inst, IrOpcode operati const IrU32 newExecHi(negateResult ? ir.BitwiseNot(highCombined) : highCombined); ir.SetExecLo(newExecLo.Value()); ir.SetExecHi(newExecHi.Value()); + if (writeResult) writeU32Pair(inst.destination, {newExecLo, newExecHi}); const IrU1 nonZero(ir.LogicalOr(ir.INotEqual(newExecLo.Value(), ir.Constant(0u)), ir.INotEqual(newExecHi.Value(), ir.Constant(0u)))); ir.SetScc(nonZero.Value()); ir.SetExec(threadBit({newExecLo, newExecHi}).Value()); @@ -54,12 +55,13 @@ void TranslationContext::sSaveexec(const RdnaInstruction& inst, IrOpcode operati const IrU32 oldExec(ir.GetExecLo()); if (writeFirst) writeRawU32(inst.destination, oldExec); const IrU32 source = readU32(sourceAt(inst, 0u)); - if (!writeFirst && writeDestination) writeRawU32(inst.destination, oldExec); + if (!writeFirst && !writeResult) writeRawU32(inst.destination, oldExec); IrValue& execOperand = negateExec ? ir.BitwiseNot(oldExec.Value()) : oldExec.Value(); IrValue& sourceOperand = negateSource ? ir.BitwiseNot(source.Value()) : source.Value(); IrValue& combined = ir.Emit(operation, IrType::U32, {&execOperand, &sourceOperand}); const IrU32 newExec(negateResult ? ir.BitwiseNot(combined) : combined); ir.SetExecLo(newExec.Value()); + if (writeResult) writeRawU32(inst.destination, newExec); const IrU1 nonZero(ir.INotEqual(newExec.Value(), ir.Constant(0u))); ir.SetScc(nonZero.Value()); ir.SetExec(threadBit({newExec, IrU32(ir.GetExecHi())}).Value()); @@ -238,13 +240,17 @@ void TranslationContext::sWqm(const RdnaInstruction& inst, bool wide) { const std::array source = readU32Pair(sourceAt(inst, 0u)); const IrU64 wide64(ir.ConstructU64(source[0].Value(), source[1].Value())); const IrU64 result(ir.Emit(IrOpcode::WqmU64, IrType::U64, {&wide64.Value()})); - writeU32Pair(inst.destination, extractU64(result)); + const std::array words = extractU64(result); + writeU32Pair(inst.destination, words); + ir.SetScc(ir.LogicalOr(ir.INotEqual(words[0].Value(), ir.Constant(0u)), ir.INotEqual(words[1].Value(), ir.Constant(0u)))); return; } const IrU32 source = readU32(sourceAt(inst, 0u)); const IrU64 wide64(ir.ConstructU64(source.Value(), ir.Constant(0u))); const IrU64 result(ir.Emit(IrOpcode::WqmU64, IrType::U64, {&wide64.Value()})); - writeRawU32(inst.destination, extractU64(result)[0]); + const IrU32 low = extractU64(result)[0]; + writeRawU32(inst.destination, low); + ir.SetScc(ir.INotEqual(low.Value(), ir.Constant(0u))); } // M0 holds a uniform register offset. Vector registers are SSA values with fixed indices, so an indexed diff --git a/core/shader/recompiler/Translation/src/ScalarInstructions.cpp b/core/shader/recompiler/Translation/src/ScalarInstructions.cpp index 0c88d096b..a75747ed2 100644 --- a/core/shader/recompiler/Translation/src/ScalarInstructions.cpp +++ b/core/shader/recompiler/Translation/src/ScalarInstructions.cpp @@ -87,10 +87,10 @@ bool TranslationContext::emitScalar(const RdnaInstruction& inst) { sSaveexec(inst, IrOpcode::LogicalXor, false, false, false, true); return true; case RdnaOpcode::SAndn1WrexecB32: - sSaveexec(inst, IrOpcode::LogicalAnd, false, true, false, false, false); + sSaveexec(inst, IrOpcode::LogicalAnd, false, true, false, false, true); return true; case RdnaOpcode::SAndn2WrexecB32: - sSaveexec(inst, IrOpcode::LogicalAnd, true, false, false, false, false); + sSaveexec(inst, IrOpcode::LogicalAnd, true, false, false, false, true); return true; case RdnaOpcode::SAndSaveexecB64: sSaveexec(inst, IrOpcode::LogicalAnd, false, false, true); @@ -123,10 +123,10 @@ bool TranslationContext::emitScalar(const RdnaInstruction& inst) { sSaveexec(inst, IrOpcode::LogicalOr, false, true, true); return true; case RdnaOpcode::SAndn1WrexecB64: - sSaveexec(inst, IrOpcode::LogicalAnd, false, true, true, false, false); + sSaveexec(inst, IrOpcode::LogicalAnd, false, true, true, false, true); return true; case RdnaOpcode::SAndn2WrexecB64: - sSaveexec(inst, IrOpcode::LogicalAnd, true, false, true, false, false); + sSaveexec(inst, IrOpcode::LogicalAnd, true, false, true, false, true); return true; case RdnaOpcode::SAddU32: addU32(inst, false, false); From b90a9137154afa2c70350bd9113fdeb06f989cd1 Mon Sep 17 00:00:00 2001 From: Pacuka <69674426+Pacuka@users.noreply.github.com> Date: Sat, 3 Oct 2026 18:16:49 +0200 Subject: [PATCH 008/164] fix(agc): draw without a pixel program when SPI_SHADER_PGM_PS is unset Co-Authored-By: Claude Opus 5.5 --- .../include/Driver/Shaders/ShaderRegistry.hpp | 2 ++ .../Execution/src/Driver/Draw/DrawDecode.cpp | 5 +++- .../src/Driver/Shaders/ShaderRegistry.cpp | 23 +++++++++++++++++++ .../Graphics/src/ShaderInputState.cpp | 13 ++++++++--- .../libSceAgcDriver/Graphics/src/State.cpp | 8 ------- .../prx/libSceAgcDriver/tests/Graphics.cpp | 9 ++++++++ 6 files changed, 48 insertions(+), 12 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp index 7511f50e0..6189cbfec 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp @@ -39,6 +39,8 @@ using ShaderRegistry = std::map SourceHandleFor(const ShaderSnapshot& snapshot, std::size_t codeOffset, std::uint64_t deviceSerial, const ShaderRecompiler::RecompileRequest& request, bool bypass, const std::string** poisoned = nullptr); } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp index 56ae16a72..c913e0202 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" @@ -15,6 +16,8 @@ std::shared_ptr Driver::decodeDraw(const QueueState& queue, const Su return (static_cast(readRegister(queue.shader, base)) << 8u) | (static_cast(high) << 40u); }; const auto prepare = [&](std::uint64_t address, std::uint8_t type, Stage stage, std::uint32_t rsrc2, std::uint32_t userDataBase) { + const bool nullPixel = address == 0 && stage == Stage::Fragment; + if (nullPixel) address = NullPixelProgramAddress(); auto it = submission.shaders->upper_bound(address); require(it != submission.shaders->begin(), "graphics program does not belong to a registered shader"); --it; @@ -22,7 +25,7 @@ std::shared_ptr Driver::decodeDraw(const QueueState& queue, const Su require(address - snapshot.codeAddress < snapshot.code.size() * sizeof(std::uint32_t), "graphics program is outside registered shader code"); require(snapshot.type == type, "graphics program refers to an incompatible shader binary type"); Graphics::NoteRegisterRead(Graphics::RegisterBank::Shader, rsrc2); - const auto resources = readRegister(queue.shader, rsrc2); + const auto resources = nullPixel && !queue.shader.contains(rsrc2) ? 0u : readRegister(queue.shader, rsrc2); const auto userCount = ((resources >> 1u) & 0x1fu) | (((resources >> 27u) & 1u) << 5u); require(userCount <= 32, "graphics user SGPR count exceeds the register bank"); const auto codeOffset = static_cast((address - snapshot.codeAddress) / sizeof(std::uint32_t)); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp index 1c5e6123d..abd485168 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -77,6 +77,22 @@ std::shared_ptr SourceHandleFor(const Shad return handle; } +alignas(256) static const std::uint32_t NullPixelCode[64] = {0xbf810000u}; +static const Shader NullPixelShader = [] { + Shader shader{}; + shader.file_header = 0x34333231u; + shader.version = 0x18u; + shader.code = NullPixelCode; + shader.header_size = sizeof(Shader); + shader.shader_size = sizeof(NullPixelCode); + shader.type = 1; + return shader; +}(); + +std::uint64_t NullPixelProgramAddress() { + return reinterpret_cast(NullPixelCode); +} + void Driver::RegisterShader(const Shader* shader) { CheckFailure(); GuestMemory::CheckRange(shader, sizeof(Shader), alignof(Shader)); @@ -107,6 +123,13 @@ void Driver::RegisterShader(const Shader* shader) { if (shaders == nullptr) shaders = std::make_shared(); else if (shaders.use_count() != 1) shaders = std::make_shared(*shaders); shaders->insert_or_assign(address, std::make_shared(std::move(snapshot))); + if (shaders->find(NullPixelProgramAddress()) == shaders->end()) { + ShaderSnapshot null{NullPixelProgramAddress(), reinterpret_cast(&NullPixelShader), NullPixelShader.type, {}, {}}; + null.code.assign(std::begin(NullPixelCode), std::end(NullPixelCode)); + null.header.resize(sizeof(Shader)); + std::memcpy(null.header.data(), &NullPixelShader, sizeof(Shader)); + shaders->insert_or_assign(NullPixelProgramAddress(), std::make_shared(std::move(null))); + } } } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp index 788458ce7..09b5c58af 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp @@ -25,6 +25,7 @@ constexpr std::uint32_t spiPsInputAddr = 0x1B4; constexpr std::uint32_t spiPsInControl = 0x1B6; constexpr std::uint32_t dbShaderControl = 0x203; constexpr std::uint32_t spiShaderColFormat = 0x1C5; +constexpr std::uint32_t defaultPixelInputs = 0x2u; std::uint32_t read(const Registers& registers, std::uint32_t offset, RegisterBank bank) { NoteRegisterRead(bank, offset); @@ -37,6 +38,12 @@ std::uint32_t read(const Registers& registers, std::uint32_t offset, RegisterBan return it->second; } +std::uint32_t readOr(const Registers& registers, std::uint32_t offset, RegisterBank bank, std::uint32_t fallback) { + NoteRegisterRead(bank, offset); + const auto it = registers.find(offset); + return it == registers.end() ? fallback : it->second; +} + template T _readHeaderPod(std::span header, std::uint64_t headerAddress, const void* pointer) { if (pointer == nullptr) throw std::runtime_error("AGC graphics: null AGC header pointer"); const auto address = reinterpret_cast(pointer); @@ -90,8 +97,8 @@ ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& con if (inputNum > 32u) { throw std::runtime_error("AGC graphics: SPI_PS_IN_CONTROL input count exceeds 32"); } - const auto ena = read(context, spiPsInputEna, RegisterBank::Context); - const auto addr = read(context, spiPsInputAddr, RegisterBank::Context); + const auto ena = readOr(context, spiPsInputEna, RegisterBank::Context, defaultPixelInputs); + const auto addr = readOr(context, spiPsInputAddr, RegisterBank::Context, defaultPixelInputs); const auto activeInputs = ena & addr; using ShaderRecompiler::PixelInput; using ShaderRecompiler::PixelInputBit; @@ -114,7 +121,7 @@ ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& con if (((shaderControl >> 13u) & 0x3u) != 0) { throw std::runtime_error("AGC graphics: DB_SHADER_CONTROL.CONSERVATIVE_Z_EXPORT is unsupported"); } - const auto colFormat = read(context, spiShaderColFormat, RegisterBank::Context); + const auto colFormat = readOr(context, spiShaderColFormat, RegisterBank::Context, 0u); std::array targetOutputMode{}; for (std::uint32_t i = 0; i < 8u; ++i) { targetOutputMode[i] = static_cast((colFormat >> (4u * i)) & 0xFu); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index 22373ebce..4ee1ec8b3 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -754,14 +754,6 @@ std::string DrawRejection(const QueueState& queue, bool indexed) { std::uint32_t targetMask = 0, shaderMask = 0; if (value(cx, 0x8e, targetMask) && value(cx, 0x8f, shaderMask) && value(cx, 0x202, word) && !colorControlSupported(word, (targetMask & shaderMask) != 0)) return colorControlMessage(word); if (auto reason = nonzero(cx, 0x1c4, ~0u, "depth or sample-mask export"); !reason.empty()) return reason; - // The pixel stage decode (ShaderInputState.cpp) reads these after DecodeState and the program - // prepare; a bank without them fails there with this message. - for (const auto offset : {0x1b3u, 0x1b4u, 0x1c5u}) { - if (find(cx, offset) != cx.end()) continue; - char text[64]; - std::snprintf(text, sizeof(text), "AGC graphics: missing register at DWORD 0x%x", offset); - return text; - } return {}; } diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index b74c63b80..d4c052bc0 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -167,6 +167,15 @@ void stateTests() { log.clear(); static_cast(AgcDriver::Graphics::DecodeState(queue)); Require(log.empty(), "the register facade recorded without a log"); + queue = makeState(); + queue.context.erase(0x1b3); + queue.context.erase(0x1b4); + Require(AgcDriver::Graphics::DrawRejection(queue, true).empty(), "a bank without SPI_PS_INPUT_ENA/ADDR was rejected"); + state = AgcDriver::Graphics::DecodeState(queue); + queue.context.erase(0x1c5); + const auto unset = AgcDriver::Graphics::DecodePixelStageInfo(queue.context, AgcDriver::Graphics::ExportMappings(state)); + Require(unset.inputAddr == ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PerspectiveCenter), "unset pixel inputs did not read as PERSP_CENTER_ENA"); + for (const auto mode : unset.targetOutputMode) Require(mode == 0, "an unset SPI_SHADER_COL_FORMAT exported a color"); } void hardwareScreenOffsetTests() { From a48a9eabf7289a43fd962ee7ca0c2010a2cef757 Mon Sep 17 00:00:00 2001 From: Pacuka <69674426+Pacuka@users.noreply.github.com> Date: Sun, 4 Oct 2026 20:57:01 +0200 Subject: [PATCH 009/164] fix(agc): keep the pixel register fallbacks to the null program and refuse its color writes Co-Authored-By: Claude Opus 5.5 --- .../Execution/src/Driver/Draw/DrawDecode.cpp | 7 +++++- .../Graphics/include/ShaderInputState.hpp | 2 +- .../Graphics/include/State.hpp | 2 ++ .../Graphics/src/ShaderInputState.cpp | 8 +++---- .../libSceAgcDriver/Graphics/src/State.cpp | 23 ++++++++++++++++++- .../prx/libSceAgcDriver/tests/Graphics.cpp | 18 +++++++++++---- 6 files changed, 49 insertions(+), 11 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp index c913e0202..42a10f51d 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp @@ -89,9 +89,14 @@ std::shared_ptr Driver::decodeDraw(const QueueState& queue, const Su } else { append(0xc8, 2, Stage::Vertex, 0x8b, 0x8c, Role::Main); } + const bool nullPixel = Graphics::PixelProgramUnset(queue); + if (nullPixel) { + const auto rejection = Graphics::NullPixelProgramRejection(queue); + require(rejection.empty(), rejection.c_str()); + } append(0x008, 1, Stage::Fragment, 0x00b, 0x00c, Role::Fragment); programs.back().firstUserSgpr = 0; - product->pixel = Graphics::DecodePixelStageInfo(queue.context, Graphics::ExportMappings(graphics)); + product->pixel = Graphics::DecodePixelStageInfo(queue.context, Graphics::ExportMappings(graphics), nullPixel); return product; } } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp index df16329a9..d54fba676 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp @@ -10,7 +10,7 @@ namespace AgcDriver::Graphics { -ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& context, const std::array& exportMappings); +ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& context, const std::array& exportMappings, bool nullProgram = false); ShaderRecompiler::ShaderComputeStageInfo DecodeComputeStageInfo(const Registers& shader); // A guest range DecodeVertexStageInfo read (an attribute word, a vertex V#) with the bytes as read: // the draw cache validates them by value with the stage's capture (design_cpu_final rule RD). diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp index 9c49f888d..dc8a45223 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp @@ -108,6 +108,8 @@ std::optional DecodeColorMetadataPass(const QueueState& queue // this precheck covers, evaluated without exceptions before the draw is decoded; empty when they // pass (DecodeState still checks everything). A register a rule needs that is absent is no verdict. std::string DrawRejection(const QueueState& queue, bool indexed); +bool PixelProgramUnset(const QueueState& queue); +std::string NullPixelProgramRejection(const QueueState& queue); // The recording facade of the draw decoders (design_cpu_final M8, step 8a): every register read // of DecodeShaderStages, DecodeState, DrawRejection, DecodePixelStageInfo and Driver::draw's diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp index 09b5c58af..44ee5932f 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp @@ -91,14 +91,14 @@ ShaderRecompiler::ShaderComputeStageInfo DecodeComputeStageInfo(const Registers& }; } -ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& context, const std::array& exportMappings) { +ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& context, const std::array& exportMappings, bool nullProgram) { const auto inControl = read(context, spiPsInControl, RegisterBank::Context); const auto inputNum = inControl & 0x3Fu; if (inputNum > 32u) { throw std::runtime_error("AGC graphics: SPI_PS_IN_CONTROL input count exceeds 32"); } - const auto ena = readOr(context, spiPsInputEna, RegisterBank::Context, defaultPixelInputs); - const auto addr = readOr(context, spiPsInputAddr, RegisterBank::Context, defaultPixelInputs); + const auto ena = nullProgram ? readOr(context, spiPsInputEna, RegisterBank::Context, defaultPixelInputs) : read(context, spiPsInputEna, RegisterBank::Context); + const auto addr = nullProgram ? readOr(context, spiPsInputAddr, RegisterBank::Context, defaultPixelInputs) : read(context, spiPsInputAddr, RegisterBank::Context); const auto activeInputs = ena & addr; using ShaderRecompiler::PixelInput; using ShaderRecompiler::PixelInputBit; @@ -121,7 +121,7 @@ ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& con if (((shaderControl >> 13u) & 0x3u) != 0) { throw std::runtime_error("AGC graphics: DB_SHADER_CONTROL.CONSERVATIVE_Z_EXPORT is unsupported"); } - const auto colFormat = readOr(context, spiShaderColFormat, RegisterBank::Context, 0u); + const auto colFormat = nullProgram ? readOr(context, spiShaderColFormat, RegisterBank::Context, 0u) : read(context, spiShaderColFormat, RegisterBank::Context); std::array targetOutputMode{}; for (std::uint32_t i = 0; i < 8u; ++i) { targetOutputMode[i] = static_cast((colFormat >> (4u * i)) & 0xFu); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index 4ee1ec8b3..27de97ca1 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -506,7 +506,7 @@ State DecodeState(const QueueState& queue) { // CB_COLOR_CONTROL mode 0 disables color writes, which only matters when a target is written. if (const auto colorControl = read(cx, 0x202); !colorControlSupported(colorControl, result.hasColorTarget)) throw std::runtime_error(colorControlMessage(colorControl)); zero(cx, 0x1c4, ~0u, "depth or sample-mask export"); - const auto exportFormat = read(cx, 0x1c5); + const auto exportFormat = result.hasColorTarget ? read(cx, 0x1c5) : 0u; APS5_LOG_OUT_DEBUG("Export format=%u", exportFormat); // SPI_SHADER_POS_FORMAT: POS0 must be a 4-component position; later vectors carry the misc/clip // exports that PA_CL_VS_OUT_CNTL validation above already limits to ignored layer/viewport data. @@ -754,6 +754,27 @@ std::string DrawRejection(const QueueState& queue, bool indexed) { std::uint32_t targetMask = 0, shaderMask = 0; if (value(cx, 0x8e, targetMask) && value(cx, 0x8f, shaderMask) && value(cx, 0x202, word) && !colorControlSupported(word, (targetMask & shaderMask) != 0)) return colorControlMessage(word); if (auto reason = nonzero(cx, 0x1c4, ~0u, "depth or sample-mask export"); !reason.empty()) return reason; + if (PixelProgramUnset(queue)) return NullPixelProgramRejection(queue); + for (const auto offset : {0x1b3u, 0x1b4u, 0x1c5u}) { + if (find(cx, offset) != cx.end()) continue; + char text[64]; + std::snprintf(text, sizeof(text), "AGC graphics: missing register at DWORD 0x%x", offset); + return text; + } + return {}; +} + +bool PixelProgramUnset(const QueueState& queue) { + const auto low = find(queue.shader, 0x008, RegisterBank::Shader); + const auto high = find(queue.shader, 0x009, RegisterBank::Shader); + return low != queue.shader.end() && high != queue.shader.end() && low->second == 0 && high->second == 0; +} + +std::string NullPixelProgramRejection(const QueueState& queue) { + const auto targetMask = find(queue.context, 0x8e); + const auto shaderMask = find(queue.context, 0x8f); + if (targetMask == queue.context.end() || shaderMask == queue.context.end()) return "AGC graphics: a draw without a pixel program needs CB_TARGET_MASK and CB_SHADER_MASK"; + if ((targetMask->second & shaderMask->second) != 0) return "AGC graphics: a draw without a pixel program writes color"; return {}; } diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index d4c052bc0..2da119a57 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -170,11 +170,21 @@ void stateTests() { queue = makeState(); queue.context.erase(0x1b3); queue.context.erase(0x1b4); - Require(AgcDriver::Graphics::DrawRejection(queue, true).empty(), "a bank without SPI_PS_INPUT_ENA/ADDR was rejected"); - state = AgcDriver::Graphics::DecodeState(queue); queue.context.erase(0x1c5); - const auto unset = AgcDriver::Graphics::DecodePixelStageInfo(queue.context, AgcDriver::Graphics::ExportMappings(state)); - Require(unset.inputAddr == ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PerspectiveCenter), "unset pixel inputs did not read as PERSP_CENTER_ENA"); + queue.shader[0x008] = 0x100; + queue.shader[0x009] = 0; + Require(!AgcDriver::Graphics::PixelProgramUnset(queue), "a pixel program address was read as unset"); + Require(AgcDriver::Graphics::DrawRejection(queue, true).find("missing register at DWORD 0x1b3") != std::string::npos, "a real pixel program without SPI_PS_INPUT_ENA was accepted"); + expectFailure([&] { AgcDriver::Graphics::DecodePixelStageInfo(queue.context, std::array{0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u}); }, "missing register"); + queue.shader[0x008] = 0; + Require(AgcDriver::Graphics::PixelProgramUnset(queue), "a zero pixel program address was not read as unset"); + Require(AgcDriver::Graphics::DrawRejection(queue, true).find("writes color") != std::string::npos, "a draw without a pixel program that writes color was accepted"); + queue.context[0x8e] = 0; + Require(AgcDriver::Graphics::DrawRejection(queue, true).empty(), "a depth-only draw without a pixel program was rejected"); + state = AgcDriver::Graphics::DecodeState(queue); + Require(!state.hasColorTarget, "a depth-only draw without a pixel program decoded a color target"); + const auto unset = AgcDriver::Graphics::DecodePixelStageInfo(queue.context, AgcDriver::Graphics::ExportMappings(state), true); + Require(unset.inputAddr == ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PerspectiveCenter), "unset pixel inputs of the null program did not read as PERSP_CENTER_ENA"); for (const auto mode : unset.targetOutputMode) Require(mode == 0, "an unset SPI_SHADER_COL_FORMAT exported a color"); } From 89cd75e15fcef4b4872d3f51f25cef66eeb8df53 Mon Sep 17 00:00:00 2001 From: Pouare514 Date: Sun, 4 Oct 2026 21:02:15 +0200 Subject: [PATCH 010/164] fix(libkernel): idempotent fixed virtual reservation over registered range --- core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp | 1 + core/libs/tests/GuestMemory.cpp | 3 +++ 2 files changed, 4 insertions(+) diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index 6187e79d0..820c6737b 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -593,6 +593,7 @@ int DoReserveVirtual(void** addr, size_t len, int flags, size_t alignment) { if (len == 0 || (len & (PS5_PAGE_SIZE - 1)) != 0) return SCE_KERNEL_ERROR_EINVAL; GuestAllocations::Mutation mutation; const bool fixed = *addr != nullptr && (flags & GuestMapFixedFlag) != 0; + if (fixed && mutation.Covers(*addr, len)) return 0; if (fixed) mutation.RequireAvailable(*addr, len); constexpr int GuestMapNoCoalesce = 0x400000; void* mapped = MapAligned(fixed ? *addr : nullptr, len, PROT_NONE, fixed ? GuestMapFixedFlag | (flags & GuestMapNoCoalesce) : 0, alignment); diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp index 8727abded..7415ff4f8 100644 --- a/core/libs/tests/GuestMemory.cpp +++ b/core/libs/tests/GuestMemory.cpp @@ -172,6 +172,9 @@ static void CheckFixedVirtualReservation() { void* fixed = requested; Require(sceKernelReserveVirtualRange(&fixed, page * 2, 0x400010, 0) == 0); Require(fixed == requested); + void* again = requested; + Require(sceKernelReserveVirtualRange(&again, page * 2, 0x400010, 0) == 0); + Require(again == requested); Require(sceKernelMunmap(fixed, page * 2) == 0); void* pooled = nullptr; Require(sceKernelMemoryPoolReserve(requested, page * 2, 0, 0x10, &pooled) == 0); From 03760d937b8bdcecfd145d5886b3a3199bbc2ab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 16:49:11 +0200 Subject: [PATCH 011/164] fix(shader): s_lshlN_add SCC and s_bfe_[iu]32 fields past bit 31 s_lshl1..4_add_u32 never wrote SCC; hardware sets it when the shifted-out bits or the add carry are non-zero. s_bfe_i32/u32 went through OpBitField{S,U}Extract, which RADV gets wrong when the width runs past bit 31; build the field from shifts instead. Adds an execution test for 38 SOP2 opcodes with hardware-captured results. Co-Authored-By: Claude --- .../tests/execution/ScalarSop2.cpp | 198 ++++++++++++++++++ .../Translation/TranslationContext.hpp | 1 + .../src/ControlFlowInstructions.cpp | 2 + .../Translation/src/IntegerInstructions.cpp | 14 +- 4 files changed, 211 insertions(+), 4 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/ScalarSop2.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSop2.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSop2.cpp new file mode 100644 index 000000000..cec18794d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSop2.cpp @@ -0,0 +1,198 @@ +#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 +#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 = 64; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060088, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xbe940380, 0xd7600008, 0x02002904, + 0xd7600009, 0x02002905, 0xd760000a, 0x02002906, 0xd760000b, 0x02002907, 0xbe9e0380, 0x871f810b, + 0x828c0a08, 0x821e1e1e, 0xd761000a, 0x0200280c, 0x871f810b, 0x858c0a08, 0x821e1e1e, 0xd761000b, + 0x0200280c, 0xd761000c, 0x0200280d, 0x810c0a08, 0x821e1e1e, 0xd761000d, 0x0200280c, 0x818c0a08, + 0x821e1e1e, 0xd761000e, 0x0200280c, 0x830c0a08, 0x821e1e1e, 0xd761000f, 0x0200280c, 0x838c0a08, + 0x821e1e1e, 0xd7610010, 0x0200280c, 0x840c0a08, 0x821e1e1e, 0xd7610011, 0x0200280c, 0x848c0a08, + 0x821e1e1e, 0xd7610012, 0x0200280c, 0x890c0a08, 0x821e1e1e, 0xd7610013, 0x0200280c, 0x8a0c0a08, + 0x821e1e1e, 0xd7610014, 0x0200280c, 0x8b0c0a08, 0x821e1e1e, 0xd7610015, 0x0200280c, 0x8d0c0a08, + 0x821e1e1e, 0xd7610016, 0x0200280c, 0x8e0c0a08, 0x821e1e1e, 0xd7610017, 0x0200280c, 0xd7610019, + 0x0200281e, 0xbe9e0380, 0x888c0a08, 0x821e1e1e, 0xd761001a, 0x0200280c, 0xd761001b, 0x0200280d, + 0x898c0a08, 0x821e1e1e, 0xd761001c, 0x0200280c, 0xd761001d, 0x0200280d, 0x8a8c0a08, 0x821e1e1e, + 0xd761001e, 0x0200280c, 0xd761001f, 0x0200280d, 0x8b8c0a08, 0x821e1e1e, 0xd7610020, 0x0200280c, + 0xd7610021, 0x0200280d, 0x8c8c0a08, 0x821e1e1e, 0xd7610022, 0x0200280c, 0xd7610023, 0x0200280d, + 0x8d8c0a08, 0x821e1e1e, 0xd7610024, 0x0200280c, 0xd7610025, 0x0200280d, 0x8e8c0a08, 0x821e1e1e, + 0xd7610026, 0x0200280c, 0xd7610027, 0x0200280d, 0xd7610029, 0x0200281e, 0xbe9e0380, 0x8f8c0a08, + 0x821e1e1e, 0xd761002a, 0x0200280c, 0xd761002b, 0x0200280d, 0x900c0a08, 0x821e1e1e, 0xd761002c, + 0x0200280c, 0x908c0a08, 0x821e1e1e, 0xd761002d, 0x0200280c, 0xd761002e, 0x0200280d, 0x910c0a08, + 0x821e1e1e, 0xd761002f, 0x0200280c, 0x918c0a08, 0x821e1e1e, 0xd7610030, 0x0200280c, 0xd7610031, + 0x0200280d, 0xbf060b0b, 0x920c0a08, 0x821e1e1e, 0xd7610032, 0x0200280c, 0xbf060b0b, 0x928c0a08, + 0x821e1e1e, 0xd7610033, 0x0200280c, 0xd7610034, 0x0200280d, 0x940c0a08, 0x821e1e1e, 0xd7610035, + 0x0200280c, 0x938c0a08, 0x821e1e1e, 0xd7610036, 0x0200280c, 0x948c0a08, 0x821e1e1e, 0xd7610037, + 0x0200280c, 0xd7610038, 0x0200280d, 0xd7610039, 0x0200281e, 0xbe9e0380, 0x960c0a08, 0x821e1e1e, + 0xd761003a, 0x0200280c, 0x970c0a08, 0x821e1e1e, 0xd761003b, 0x0200280c, 0x978c0a08, 0x821e1e1e, + 0xd761003c, 0x0200280c, 0x980c0a08, 0x821e1e1e, 0xd761003d, 0x0200280c, 0x988c0a08, 0x821e1e1e, + 0xd761003e, 0x0200280c, 0xbf060b0b, 0x990c0a08, 0x821e1e1e, 0xd761003f, 0x0200280c, 0xbf060b0b, + 0x998c0a08, 0x821e1e1e, 0xd7610040, 0x0200280c, 0xbf060b0b, 0x9a0c0a08, 0x821e1e1e, 0xd7610041, + 0x0200280c, 0xbf060b0b, 0x9a8c0a08, 0x821e1e1e, 0xd7610042, 0x0200280c, 0xbf060b0b, 0x9b0c0a08, + 0x821e1e1e, 0xd7610043, 0x0200280c, 0xd7610049, 0x0200281e, 0x80148114, 0xbf0aa014, 0xbf85ff26, + 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, + 0xe0781040, 0x80011a03, 0xe0781050, 0x80011e03, 0xe0781060, 0x80012203, 0xe0781070, 0x80012603, + 0xe0781080, 0x80012a03, 0xe0781090, 0x80012e03, 0xe07810a0, 0x80013203, 0xe07810b0, 0x80013603, + 0xe07810c0, 0x80013a03, 0xe07810d0, 0x80013e03, 0xe07810e0, 0x80014203, 0xe07810f0, 0x80014603, + 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0xbde5c099u, 0x00000001u, 0xc6a53877u, 0xa6233255u}, + {0x00000000u, 0x4e5a4cafu, 0x00000000u, 0xd86f17ecu}, + {0x00410003u, 0x80000000u, 0xffffffffu, 0xffff8000u}, + {0x80000000u, 0x00000040u, 0x7fffffffu, 0x0010001cu}, + {0xe05e9899u, 0xb77701e2u, 0x007f0000u, 0x0000001fu}, + {0x7fffffffu, 0xb89c4e56u, 0xff297d0eu, 0xffffffffu}, + {0x4b78dc3du, 0x0000003fu, 0x741b324du, 0xb057c162u}, + {0x00008000u, 0x40000000u, 0x61394c10u, 0x0000003fu}, + {0xffffffffu, 0x40000000u, 0x7fffffffu, 0x66868695u}, + {0x0000003fu, 0x40000000u, 0x617959ceu, 0xffff8000u}, + {0x00010000u, 0x00000040u, 0xc4069545u, 0x80000000u}, + {0x12345678u, 0x00000000u, 0x015c33b2u, 0x00010000u}, + {0x2a759159u, 0x4a0fe75du, 0x0000ffffu, 0x00010000u}, + {0x0000ffffu, 0xe4cc4132u, 0x00000001u, 0x0000003fu}, + {0x43892dfcu, 0x00000005u, 0x960d5a8fu, 0x0021001fu}, + {0x0020003fu, 0x7b121dc5u, 0x2f4d4c86u, 0x00000040u}, + {0x00000001u, 0x5b8e8fb2u, 0x04a012e8u, 0x00410003u}, + {0x00000000u, 0x00000040u, 0xf453324eu, 0xffff8000u}, + {0xf129c8c6u, 0x10000000u, 0x00080004u, 0x1ba95a54u}, + {0x0001001fu, 0x0960afe9u, 0x7fffffffu, 0x10000000u}, + {0xeb07c30du, 0x0010001cu, 0x8b8e8f4eu, 0x00400000u}, + {0x007f0000u, 0x0020003fu, 0x74a677c6u, 0x2547f19cu}, + {0x00000002u, 0x558d2adbu, 0x216d27a2u, 0x90656c8fu}, + {0xa161d909u, 0x69f0441eu, 0x0040003fu, 0xffffffffu}, + {0x7aa286acu, 0x40000000u, 0x00000021u, 0x00000040u}, + {0x7fd39898u, 0x2e1c5d3au, 0x0010001cu, 0x10000000u}, + {0x0020003fu, 0xa9fd1ce9u, 0x00000005u, 0x0010001cu}, + {0x43cdc356u, 0xb0ddb48bu, 0xf4f6717cu, 0xa6523995u}, + {0x00000000u, 0x00080004u, 0x0010001cu, 0x10000000u}, + {0xcc3136d8u, 0x0010001cu, 0xbce240c6u, 0x68916d08u}, + {0x00000040u, 0xb036d9ddu, 0x0001001fu, 0x84a4c93fu}, + {0x0000003fu, 0x0001001fu, 0x7a2c54a0u, 0x7fffffffu} +}; +constexpr std::uint32_t Expected[32][64] = { + {0xf7408821u, 0xbde5c099u, 0x00000001u, 0x848af910u, 0xf7408822u, 0xbde5c099u, 0xbde5c099u, 0xc6a53877u, 0xc6a53877u, 0x7b40f8eeu, 0x3940c088u, 0xbdffc799u, 0x001a0700u, 0x84bf0711u, 0x00000000u, 0x0000199fu, 0xffe5f8ffu, 0xa6233255u, 0x7b40f8eeu, 0xa6233254u, 0x3940c088u, 0x00000000u, 0xbdffc799u, 0x59dccdabu, 0x7b5affeeu, 0xfffffffeu, 0x001a0700u, 0x59dccdaau, 0x84bf0711u, 0x59dccdabu, 0x00000000u, 0x0000007fu, 0x00000000u, 0x4c800000u, 0x0000017bu, 0x00000000u, 0x00000000u, 0xffffff7bu, 0x00000000u, 0x00000000u, 0xff800000u, 0x00000000u, 0xff800000u, 0xffffff7bu, 0x0000017bu, 0x00000000u, 0x00000000u, 0x0000035eu, 0x08bf77deu, 0x4270b9a9u, 0xbe3c3adbu, 0xb5d33d3fu, 0xa5014207u, 0x3877c099u, 0xc6a5c099u, 0xc6a5bde5u, 0x935a41eeu, 0x0ecf48deu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0x00000000u, 0x00000000u, 0xd86f17ecu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000007u, 0x00000000u, 0xde7f5fefu, 0x00000000u, 0x96355b43u, 0x00000000u, 0x06104803u, 0xffffffffu, 0x6fdaecbfu, 0xffffffffu, 0xb7b5fb53u, 0xffffffffu, 0x2180a010u, 0xffffffffu, 0x69caa4bcu, 0x00000000u, 0x0000007fu, 0x00000000u, 0x4e5a4cafu, 0x00000000u, 0x00000000u, 0x4e5a4cafu, 0x00000000u, 0x00000000u, 0x4e5a4cafu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000002b8u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu}, + {0x00410004u, 0xffffffffu, 0xffff8000u, 0x00410002u, 0x00410004u, 0xffffffffu, 0x00410003u, 0x00410003u, 0xffffffffu, 0xffbefffcu, 0x00000000u, 0x00410003u, 0x00000000u, 0x00410003u, 0x00000000u, 0x000010d5u, 0xffffffffu, 0xffff8000u, 0xffbefffcu, 0x7fff8000u, 0x00000000u, 0x00000000u, 0x00410003u, 0x80007fffu, 0xffbefffcu, 0x7fffffffu, 0x00000000u, 0x00007fffu, 0x00410003u, 0x80007fffu, 0x00000000u, 0x0000006fu, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x000002b9u, 0x00410004u, 0x00820005u, 0x0104000bu, 0x02080017u, 0x0410002fu, 0xffff0003u, 0xffff0003u, 0xffff0041u, 0x00410002u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0x00000001u, 0x7fffffffu, 0x0010001cu, 0xffffffffu, 0x00000001u, 0x80000000u, 0x7fffffffu, 0x7fffffffu, 0x80000000u, 0xffffffffu, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000033cu, 0xffffffffu, 0x0010005cu, 0xffffffffu, 0x0010005cu, 0x80000000u, 0x00000040u, 0x80000000u, 0xffefffe3u, 0xffffffffu, 0xffffffffu, 0x00000000u, 0xffefffa3u, 0x00000000u, 0xffefffa3u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x00000001u, 0x00000000u, 0x00000000u, 0x0000015eu, 0x00000001u, 0x7fffffffu, 0x7fffffffu, 0x7fffffffu, 0x7fffffffu, 0xffff0000u, 0x7fff0000u, 0x7fff8000u, 0x3fffffffu, 0xc0000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0xdfdf9898u, 0xe05e9899u, 0xb77701e2u, 0xe0dd9899u, 0xdfdf9899u, 0xe05e9899u, 0x007f0000u, 0x007f0000u, 0xe05e9899u, 0xe0219899u, 0xe0009899u, 0xffdeffffu, 0x1f806766u, 0x1fde6766u, 0x00000000u, 0x0000093fu, 0xe07f9899u, 0xb77701ffu, 0xe0219899u, 0xb77701fdu, 0xe0009899u, 0xb77701e0u, 0xffdeffffu, 0xffffffe2u, 0xffa1ffffu, 0xfffffffdu, 0x1f806766u, 0x4888fe00u, 0x1fde6766u, 0x4888fe02u, 0x00000000u, 0x0000007fu, 0xe05e9899u, 0xb77701e2u, 0xe05e9899u, 0xe05e9899u, 0xb77701e2u, 0xe05e9899u, 0xe05e9899u, 0xb77701e2u, 0x01ffffffu, 0x01ffffffu, 0x00000000u, 0xe05e9899u, 0xe05e9899u, 0xe05e9899u, 0xb77701e2u, 0x000003ffu, 0x20206767u, 0xc13c3132u, 0x81f96264u, 0x0373c4c8u, 0x06688990u, 0x00009899u, 0x007f9899u, 0x007fe05eu, 0x006f4eedu, 0xfff04eedu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0x80d682f0u, 0x7fffffffu, 0xb89c4e56u, 0x7f297d0du, 0x80d682f1u, 0xff297d0eu, 0x7fffffffu, 0x7fffffffu, 0xff297d0eu, 0x80d682f1u, 0x00d682f1u, 0x7fffffffu, 0x00000000u, 0x7f297d0eu, 0x00000000u, 0x00001addu, 0xffffffffu, 0xffffffffu, 0x80d682f1u, 0x4763b1a9u, 0x00d682f1u, 0x00000000u, 0x7fffffffu, 0xb89c4e56u, 0x80d682f1u, 0x4763b1a9u, 0x00000000u, 0x00000000u, 0x7f297d0eu, 0xb89c4e56u, 0x00000000u, 0x0000007du, 0xffffc000u, 0x13959fffu, 0x0001ffffu, 0x3959ffffu, 0x0002e271u, 0x0001ffffu, 0x3959ffffu, 0xfffee271u, 0xffffc000u, 0xffffc000u, 0xffffffffu, 0x0001ffffu, 0x0001ffffu, 0x3959ffffu, 0x00000071u, 0x000003ffu, 0x7f297d0fu, 0xff297d0cu, 0xff297d0au, 0xff297d06u, 0xff297cfeu, 0x7d0effffu, 0xff29ffffu, 0xff297fffu, 0x7f94be86u, 0xff94be87u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0xd75da9f0u, 0x741b324du, 0xb057c162u, 0xbf940e8au, 0xd75da9f0u, 0x4b78dc3du, 0x4b78dc3du, 0x741b324du, 0x741b324du, 0x3f63ee70u, 0x0b60cc30u, 0xcbfcddbfu, 0x80840182u, 0xc09c118fu, 0x00000000u, 0x0000159fu, 0x7f7bfe7du, 0xb057c17fu, 0x3f63ee70u, 0xb057c15du, 0x0b60cc30u, 0x0000001du, 0xcbfcddbfu, 0x4fa83ebfu, 0xbfe7eff2u, 0xffffffddu, 0x80840182u, 0x4fa83e80u, 0xc09c118fu, 0x4fa83ea2u, 0x00000000u, 0x0000007fu, 0x1b87a000u, 0x0007e96fu, 0x00025bc6u, 0x01fa5bc6u, 0x00000000u, 0x00025bc6u, 0x01fa5bc6u, 0x00000000u, 0xffffe000u, 0xffffe000u, 0xffffffffu, 0x00025bc6u, 0x00025bc6u, 0x01fa5bc6u, 0x00000000u, 0x000003ffu, 0x28a25610u, 0x0b0ceac7u, 0xa1fea341u, 0xcfe21435u, 0x2ba8f61du, 0x324ddc3du, 0x741bdc3du, 0x741b4b78u, 0x223ac85fu, 0x223ac85fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0x9ec733efu, 0x00008000u, 0x40000000u, 0x6139cc10u, 0x9ec733f0u, 0x00008000u, 0x00008000u, 0x61394c10u, 0x61394c10u, 0x6139cc10u, 0x00008000u, 0x9ec6b3efu, 0x9ec633efu, 0x9ec633efu, 0x00000000u, 0x0000199fu, 0x6139cc10u, 0x4000003fu, 0x6139cc10u, 0x4000003fu, 0x00008000u, 0x40000000u, 0x9ec6b3efu, 0xffffffc0u, 0xffffffffu, 0xffffffffu, 0x9ec633efu, 0xbfffffc0u, 0x9ec633efu, 0xbfffffc0u, 0x00000000u, 0x0000007fu, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00004000u, 0x00000000u, 0x00000000u, 0x00004000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00004000u, 0x000002b9u, 0x6138cc10u, 0x613a4c10u, 0x613b4c10u, 0x613d4c10u, 0x61414c10u, 0x4c108000u, 0x61398000u, 0x61390000u, 0x0000309cu, 0x0000309cu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0x7fffffffu, 0xffffffffu, 0x40000000u, 0x7ffffffeu, 0x80000000u, 0xffffffffu, 0x7fffffffu, 0x7fffffffu, 0xffffffffu, 0x80000000u, 0x80000000u, 0xffffffffu, 0x00000000u, 0x7fffffffu, 0x00000000u, 0x0000093du, 0xffffffffu, 0x66868695u, 0x80000000u, 0x26868695u, 0x80000000u, 0x00000000u, 0xffffffffu, 0xd979796au, 0x80000000u, 0xbfffffffu, 0x00000000u, 0x9979796au, 0x7fffffffu, 0xd979796au, 0x00000000u, 0x0000007fu, 0x00000000u, 0x80000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0xffffffffu, 0x00000001u, 0x00000000u, 0x00000000u, 0x0000035eu, 0x80000000u, 0x7ffffffdu, 0x7ffffffbu, 0x7ffffff7u, 0x7fffffefu, 0xffffffffu, 0x7fffffffu, 0x7fffffffu, 0x7ffffffeu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0x9e86a671u, 0x617959ceu, 0xffff8000u, 0x61795a0du, 0x9e86a671u, 0x0000003fu, 0x0000003fu, 0x617959ceu, 0x617959ceu, 0x617959f1u, 0x00000031u, 0x9e86a63fu, 0x9e86a600u, 0x9e86a60eu, 0x00000000u, 0x0000119fu, 0x617959ffu, 0xffff8000u, 0x617959f1u, 0xbfff8000u, 0x00000031u, 0x00000000u, 0x9e86a63fu, 0x40007fffu, 0xfffffff1u, 0xbfffffffu, 0x9e86a600u, 0x00007fffu, 0x9e86a60eu, 0x40007fffu, 0x00000000u, 0x0000007fu, 0x000fc000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00010000u, 0x00000000u, 0x00000000u, 0x00010000u, 0xffffc000u, 0xffffc000u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00010000u, 0x000002b9u, 0x6179598fu, 0x61795a4cu, 0x61795acau, 0x61795bc6u, 0x61795dbeu, 0x59ce003fu, 0x6179003fu, 0x61790000u, 0x00000017u, 0x00000017u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0x3bfa6abbu, 0xc4069545u, 0x80000000u, 0xc4079545u, 0x3bfa6abbu, 0xc4069545u, 0x00010000u, 0x00010000u, 0xc4069545u, 0xc4079545u, 0x00010000u, 0x3bf96abau, 0x3bf86abau, 0x3bf86abau, 0x00000000u, 0x000010dfu, 0xc4079545u, 0x80000040u, 0xc4079545u, 0x80000040u, 0x00010000u, 0x00000040u, 0x3bf96abau, 0x7fffffffu, 0xffffffffu, 0xffffffffu, 0x3bf86abau, 0x7fffffbfu, 0x3bf86abau, 0x7fffffbfu, 0x00000000u, 0x0000007fu, 0x00200000u, 0x00000800u, 0x00000800u, 0x00000800u, 0x00000002u, 0x00000800u, 0x00000800u, 0x00000002u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003f8u, 0x3bfa6abbu, 0xc4089545u, 0xc40a9545u, 0xc40e9545u, 0xc4169545u, 0x95450000u, 0xc4060000u, 0xc4060001u, 0x0000c406u, 0xffffc406u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0x10d822c6u, 0x015c33b2u, 0x00010000u, 0x13908a2au, 0x10d822c6u, 0x015c33b2u, 0x015c33b2u, 0x12345678u, 0x12345678u, 0x136865cau, 0x12204448u, 0xfeb7de7du, 0xec838805u, 0xec979a35u, 0x00000000u, 0x0000007fu, 0x137c77fau, 0x00010000u, 0x136865cau, 0x00010000u, 0x12204448u, 0x00000000u, 0xfeb7de7du, 0xfffeffffu, 0xffebedcfu, 0xffffffffu, 0xec838805u, 0xfffeffffu, 0xec979a35u, 0xfffeffffu, 0x00000000u, 0x0000007fu, 0x00000000u, 0x59e00000u, 0x0000048du, 0x00000000u, 0x00000000u, 0x0000048du, 0x00000000u, 0x00000000u, 0xfffc0000u, 0x00000000u, 0xfffc0000u, 0x0000048du, 0x0000048du, 0x00000000u, 0x00000000u, 0x0000035eu, 0x10d822c6u, 0x25c4e0a2u, 0x4a2d8d92u, 0x92fee772u, 0x24a19b32u, 0x33b25678u, 0x015c5678u, 0x015c1234u, 0x0018c2d2u, 0x0018c2d2u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000023fu}, + {0x2a74915au, 0x0000ffffu, 0x00010000u, 0x2a769158u, 0x2a74915au, 0x0000ffffu, 0x0000ffffu, 0x2a759159u, 0x2a759159u, 0x2a756ea6u, 0x2a750000u, 0xffff9159u, 0xd58a0000u, 0xd58a9159u, 0x00000000u, 0x0000007fu, 0x2a75ffffu, 0x4a0fe75du, 0x2a756ea6u, 0x4a0ee75du, 0x2a750000u, 0x4a0ee75du, 0xffff9159u, 0xffffffffu, 0xffff6ea6u, 0xfffeffffu, 0xd58a0000u, 0xb5f018a2u, 0xd58a9159u, 0xb5f118a2u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000218u, 0x2a74915au, 0x54ec22b1u, 0xa9d74563u, 0x53ad8ac7u, 0xa75a158fu, 0xffff9159u, 0x00009159u, 0x00002a75u, 0x00002a75u, 0x00002a75u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000027fu}, + {0x0000fffdu, 0x0000ffffu, 0xe4cc4132u, 0x00010000u, 0x0000fffeu, 0x00000001u, 0x00000001u, 0x0000ffffu, 0x0000ffffu, 0x0000fffeu, 0x0000fffeu, 0xffffffffu, 0xffff0000u, 0xffff0001u, 0x00000000u, 0x0000087fu, 0x0000ffffu, 0xe4cc413fu, 0x0000fffeu, 0xe4cc410du, 0x0000fffeu, 0xe4cc4100u, 0xffffffffu, 0xfffffff2u, 0xfffffffeu, 0xffffffcdu, 0xffff0000u, 0x1b33bec0u, 0xffff0001u, 0x1b33bef2u, 0x00000000u, 0x0000007fu, 0x0001fffeu, 0xc9988264u, 0x00007fffu, 0x00007fffu, 0x72662099u, 0x00007fffu, 0x00007fffu, 0xf2662099u, 0xfffffffeu, 0xfffffffeu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003f8u, 0x0000fffeu, 0x0001ffffu, 0x0003fffdu, 0x0007fff9u, 0x000ffff1u, 0x0001ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0xad7bd36cu, 0x43892dfcu, 0x00000005u, 0xd996888bu, 0xad7bd36du, 0x960d5a8fu, 0x43892dfcu, 0x43892dfcu, 0x960d5a8fu, 0xd5847773u, 0x41802570u, 0x6bfbadfcu, 0x28728000u, 0x2a7b888cu, 0x00000000u, 0x00001adfu, 0xd78d7fffu, 0x0021001fu, 0xd5847773u, 0x0021001au, 0x41802570u, 0x00000000u, 0x6bfbadfcu, 0xffdeffe5u, 0xfdf6f773u, 0xfffffffau, 0x28728000u, 0xffdeffe0u, 0x2a7b888cu, 0xffdeffe5u, 0x00000000u, 0x0000007fu, 0x96fe0000u, 0x0002a1c4u, 0x00008712u, 0x000a8712u, 0x00000000u, 0x00008712u, 0x000a8712u, 0x00000000u, 0xffff8000u, 0xffff8000u, 0xffffffffu, 0x00000712u, 0x00000712u, 0x00000712u, 0x00000000u, 0x000003ffu, 0x52842c93u, 0x1d1fb687u, 0xa432127fu, 0xb256ca6fu, 0xcea03a4fu, 0x5a8f2dfcu, 0x960d2dfcu, 0x960d4389u, 0x2795e6ccu, 0xe40cb8d0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0xd0d2b3b9u, 0x2f4d4c86u, 0x00000040u, 0x2f6d4cc5u, 0xd0d2b3b9u, 0x0020003fu, 0x0020003fu, 0x2f4d4c86u, 0x2f4d4c86u, 0x2f6d4cb9u, 0x00200039u, 0xd0b2b37fu, 0xd092b340u, 0xd092b346u, 0x00000000u, 0x0000119fu, 0x2f6d4cbfu, 0x7b121dc5u, 0x2f6d4cb9u, 0x7b121d85u, 0x00200039u, 0x7b121d85u, 0xd0b2b37fu, 0xffffffffu, 0xfffffff9u, 0xffffffbfu, 0xd092b340u, 0x84ede23au, 0xd092b346u, 0x84ede27au, 0x00000000u, 0x0000007fu, 0x08000fc0u, 0xc4877140u, 0x00008000u, 0x14008000u, 0x01ec4877u, 0x00008000u, 0x14008000u, 0x01ec4877u, 0xffffffc0u, 0xffffffc0u, 0xffffffffu, 0x00008000u, 0x00008000u, 0x14008000u, 0x01ec4877u, 0x000003ffu, 0x2f2d4c47u, 0x2f8d4d04u, 0x2fcd4d82u, 0x304d4e7eu, 0x314d5076u, 0x4c86003fu, 0x2f4d003fu, 0x2f4d0020u, 0x0005e9b5u, 0x0005e9b5u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0xfb5fed18u, 0x00000001u, 0x5b8e8fb2u, 0x04a012e9u, 0xfb5fed19u, 0x00000001u, 0x00000001u, 0x04a012e8u, 0x04a012e8u, 0x04a012e9u, 0x00000001u, 0xfb5fed17u, 0xfb5fed16u, 0xfb5fed16u, 0x00000000u, 0x0000199fu, 0x04a012e9u, 0x5bcf8fb3u, 0x04a012e9u, 0x5bcf8fb1u, 0x00000001u, 0x5b8e8fb0u, 0xfb5fed17u, 0xffbefffeu, 0xffffffffu, 0xfffffffdu, 0xfb5fed16u, 0xa430704cu, 0xfb5fed16u, 0xa430704eu, 0x00000000u, 0x0000007fu, 0x00000000u, 0x00000100u, 0x00000000u, 0x005b8e8fu, 0x00000000u, 0x00000000u, 0x005b8e8fu, 0x00000000u, 0x00000100u, 0x00000000u, 0x00000100u, 0x00000000u, 0x00000000u, 0x005b8e8fu, 0x00000000u, 0x000002b9u, 0x04a012e7u, 0x04a012eau, 0x04a012ecu, 0x04a012f0u, 0x04a012f8u, 0x12e80001u, 0x04a00001u, 0x04a00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0x0baccdb2u, 0xf453324eu, 0xffff8000u, 0xf453324eu, 0x0baccdb2u, 0xf453324eu, 0x00000000u, 0x00000000u, 0xf453324eu, 0xf453324eu, 0x00000000u, 0x0baccdb1u, 0x0baccdb1u, 0x0baccdb1u, 0x00000000u, 0x000010d7u, 0xf453324eu, 0xffff8040u, 0xf453324eu, 0xffff8040u, 0x00000000u, 0x00000040u, 0x0baccdb1u, 0x00007fffu, 0xffffffffu, 0xffffffffu, 0x0baccdb1u, 0x00007fbfu, 0x0baccdb1u, 0x00007fbfu, 0x00000000u, 0x0000007fu, 0x00000000u, 0x00100000u, 0x00000000u, 0x01000000u, 0x00000000u, 0x00000000u, 0x01000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x01000000u, 0x00000000u, 0x000002b9u, 0x0baccdb2u, 0xf453324eu, 0xf453324eu, 0xf453324eu, 0xf453324eu, 0x324e0000u, 0xf4530000u, 0xf4530000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0xf121c8c2u, 0x00080004u, 0x1ba95a54u, 0xf131c8cau, 0xf121c8c2u, 0xf129c8c6u, 0x00080004u, 0x00080004u, 0xf129c8c6u, 0xf121c8c2u, 0xf121c8c2u, 0xffffffffu, 0x0ed63739u, 0x0ede373du, 0x00000000u, 0x0000013fu, 0xf129c8c6u, 0x1ba95a54u, 0xf121c8c2u, 0x0ba95a54u, 0xf121c8c2u, 0x00000000u, 0xffffffffu, 0xf456a5abu, 0xfff7fffbu, 0xefffffffu, 0x0ed63739u, 0xe456a5abu, 0x0ede373du, 0xf456a5abu, 0x00000000u, 0x0000007fu, 0x129c8c60u, 0x0000000fu, 0x0f129c8cu, 0x0f129c8cu, 0x01000000u, 0xff129c8cu, 0x0f129c8cu, 0x01000000u, 0x000003f0u, 0x000003f0u, 0x00000000u, 0xffffff8cu, 0x0000008cu, 0x0000008cu, 0x00000000u, 0x000003ffu, 0x0ede373eu, 0xe25b9190u, 0xc4af231cu, 0x89564634u, 0x12a48c64u, 0x0004c8c6u, 0x0008c8c6u, 0x0008f129u, 0x00078952u, 0xffff894eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0x80010020u, 0x7fffffffu, 0x10000000u, 0x8001001eu, 0x80010020u, 0x0001001fu, 0x0001001fu, 0x7fffffffu, 0x7fffffffu, 0x7ffeffe0u, 0x00000000u, 0x8001001fu, 0x80000000u, 0x8001001fu, 0x00000000u, 0x00001597u, 0x7fffffffu, 0x1960afe9u, 0x7ffeffe0u, 0x1960afe9u, 0x00000000u, 0x0960afe9u, 0x8001001fu, 0xefffffffu, 0xfffeffe0u, 0xffffffffu, 0x80000000u, 0xe69f5016u, 0x8001001fu, 0xe69f5016u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000218u, 0x7ffeffe0u, 0x8002003du, 0x8004007bu, 0x800800f7u, 0x801001efu, 0xffff001fu, 0x7fff001fu, 0x7fff0001u, 0x0000800fu, 0x0000800fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0x5f7933bfu, 0x8b8e8f4eu, 0x00400000u, 0x7696525bu, 0x5f7933bfu, 0x8b8e8f4eu, 0x8b8e8f4eu, 0xeb07c30du, 0xeb07c30du, 0x60894c43u, 0x60014001u, 0xff77f3bdu, 0x147030b0u, 0x9f76b3bcu, 0x00000000u, 0x0000047fu, 0xeb8fcf4fu, 0x0050001cu, 0x60894c43u, 0x0050001cu, 0x60014001u, 0x0010001cu, 0xff77f3bdu, 0xffbfffffu, 0x74f97cf3u, 0xffffffffu, 0x147030b0u, 0xffafffe3u, 0x9f76b3bcu, 0xffafffe3u, 0x00000000u, 0x0000007fu, 0xf0c34000u, 0x00073ac1u, 0x0003ac1fu, 0x0073ac1fu, 0x00000040u, 0xffffac1fu, 0x0073ac1fu, 0x00000040u, 0x07ffc000u, 0x07ffc000u, 0x00000000u, 0xffffec1fu, 0x00002c1fu, 0x00002c1fu, 0x00000000u, 0x000003ffu, 0x5f7933bfu, 0x619e1568u, 0x37ad9b82u, 0xe3cca7b6u, 0x3c0ac01eu, 0x8f4ec30du, 0x8b8ec30du, 0x8b8eeb07u, 0x802018c7u, 0x0989c66cu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0x8bd8883au, 0x74a677c6u, 0x2547f19cu, 0x752577c6u, 0x8bd8883au, 0x007f0000u, 0x007f0000u, 0x74a677c6u, 0x74a677c6u, 0x74d977c6u, 0x00590000u, 0x8b7f8839u, 0x8b008839u, 0x8b268839u, 0x00000000u, 0x0000119fu, 0x74ff77c6u, 0x2567f1bfu, 0x74d977c6u, 0x2567f1a3u, 0x00590000u, 0x00200023u, 0x8b7f8839u, 0xdab80e7fu, 0xffd9ffffu, 0xffffffe3u, 0x8b008839u, 0xda980e40u, 0x8b268839u, 0xda980e5cu, 0x00000000u, 0x0000007fu, 0x1fc00000u, 0x08000fc0u, 0x0001fc00u, 0xfc01fc00u, 0x00008000u, 0x0001fc00u, 0xfc01fc00u, 0x00008000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0001fc00u, 0x0001fc00u, 0xfc01fc00u, 0x00000000u, 0x000003ffu, 0x742777c6u, 0x75a477c6u, 0x76a277c6u, 0x789e77c6u, 0x7c9677c6u, 0x77c60000u, 0x74a60000u, 0x74a6007fu, 0x0039de95u, 0x0039de95u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0xde92d85fu, 0x00000002u, 0x558d2adbu, 0x216d27a4u, 0xde92d860u, 0x00000002u, 0x00000002u, 0x216d27a2u, 0x216d27a2u, 0x216d27a0u, 0x00000000u, 0xde92d85fu, 0xde92d85du, 0xde92d85fu, 0x00000000u, 0x00001997u, 0x216d27a2u, 0xd5ed6edfu, 0x216d27a0u, 0xc5e84654u, 0x00000000u, 0x45880250u, 0xde92d85fu, 0x7f9fbbfbu, 0xfffffffdu, 0xeffad774u, 0xde92d85du, 0x2a129120u, 0xde92d85fu, 0x3a17b9abu, 0x00000000u, 0x0000007fu, 0x00000000u, 0x00000008u, 0x00000000u, 0x15634ab6u, 0x00000000u, 0x00000000u, 0x15634ab6u, 0x00000000u, 0x0000000cu, 0x00000000u, 0x0000000cu, 0x00000000u, 0x00000000u, 0x15634ab6u, 0x00000000u, 0x000002b9u, 0x216d27a0u, 0x216d27a6u, 0x216d27aau, 0x216d27b2u, 0x216d27c2u, 0x27a20002u, 0x216d0002u, 0x216d0000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0xa121d8c9u, 0xa161d909u, 0x69f0441eu, 0xa1a1d948u, 0xa121d8cau, 0xa161d909u, 0x0040003fu, 0x0040003fu, 0xa161d909u, 0xa121d936u, 0xa121d900u, 0xffffffc9u, 0x5e9e26c0u, 0x5ede26c9u, 0x00000000u, 0x0000093fu, 0xa161d93fu, 0xffffffffu, 0xa121d936u, 0x960fbbe1u, 0xa121d900u, 0x00000000u, 0xffffffc9u, 0x69f0441eu, 0xffbffff6u, 0x960fbbe1u, 0x5e9e26c0u, 0x00000000u, 0x5ede26c9u, 0x69f0441eu, 0x00000000u, 0x0000007fu, 0x00000000u, 0x80000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0xffffffffu, 0x00000001u, 0x00000000u, 0x00000000u, 0x0000035eu, 0x5ede2736u, 0x4303b251u, 0x85c76463u, 0x0b4ec887u, 0x165d90cfu, 0x003fd909u, 0x0040d909u, 0x0040a161u, 0x0028589du, 0xffe8585eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0x7aa2868bu, 0x00000021u, 0x00000040u, 0x7aa286cdu, 0x7aa2868bu, 0x00000021u, 0x00000021u, 0x7aa286acu, 0x7aa286acu, 0x7aa2868du, 0x7aa2868cu, 0xfffffffeu, 0x855d7952u, 0x855d7972u, 0x00000000u, 0x0000007fu, 0x7aa286adu, 0x40000040u, 0x7aa2868du, 0x40000040u, 0x7aa2868cu, 0x40000000u, 0xfffffffeu, 0xffffffbfu, 0xffffffdfu, 0xffffffffu, 0x855d7952u, 0xbfffffbfu, 0x855d7972u, 0xbfffffbfu, 0x00000000u, 0x0000007fu, 0x00000000u, 0xf5450d58u, 0x3d514356u, 0x20000000u, 0x00000000u, 0x3d514356u, 0x20000000u, 0x00000000u, 0x00001ffeu, 0x00000000u, 0xfffffffeu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003f8u, 0x7aa2868bu, 0xf5450d79u, 0xea8a1ad1u, 0xd5143581u, 0xaa286ae1u, 0x002186acu, 0x000086acu, 0x00007aa2u, 0x0000000fu, 0x0000000fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000002ffu}, + {0x7fc3987cu, 0x0010001cu, 0x10000000u, 0x7fe398b4u, 0x7fc3987cu, 0x0010001cu, 0x0010001cu, 0x7fd39898u, 0x7fd39898u, 0x7fc39884u, 0x7fc39880u, 0xfffffffbu, 0x802c6763u, 0x803c677bu, 0x00000000u, 0x0000007fu, 0x7fd3989cu, 0x3e1c5d3au, 0x7fc39884u, 0x3e1c5d3au, 0x7fc39880u, 0x2e1c5d3au, 0xfffffffbu, 0xefffffffu, 0xffefffe7u, 0xffffffffu, 0x802c6763u, 0xc1e3a2c5u, 0x803c677bu, 0xc1e3a2c5u, 0x00000000u, 0x0000007fu, 0x80000000u, 0xa7fd3989u, 0x00000007u, 0xe1c5d3a7u, 0x00000002u, 0x00000007u, 0xe1c5d3a7u, 0x00000002u, 0xf0000000u, 0xf0000000u, 0x000fffffu, 0x00000007u, 0x00000007u, 0x0000d3a7u, 0x00000000u, 0x000003ffu, 0x7fc3987cu, 0xffb7314cu, 0xff5e627cu, 0xfeacc4dcu, 0xfd49899cu, 0x001c9898u, 0x00109898u, 0x00107fd3u, 0x0007fd47u, 0x0007fd47u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000002ffu}, + {0x0020003au, 0x00000005u, 0x0010001cu, 0x00200044u, 0x0020003au, 0x00000005u, 0x00000005u, 0x0020003fu, 0x0020003fu, 0x0020003au, 0x0020003au, 0xffffffffu, 0xffdfffc0u, 0xffdfffc5u, 0x00000000u, 0x0000007fu, 0x0020003fu, 0xa9fd1cfdu, 0x0020003au, 0xa9ed1cf5u, 0x0020003au, 0xa9ed1ce1u, 0xffffffffu, 0xffffffebu, 0xfffffffau, 0xffeffff7u, 0xffdfffc0u, 0x5602e302u, 0xffdfffc5u, 0x5612e30au, 0x00000000u, 0x0000007fu, 0x040007e0u, 0x3fa39d20u, 0x00010001u, 0x48010001u, 0x054fe8e7u, 0x00010001u, 0x48010001u, 0xfd4fe8e7u, 0xffffffe0u, 0xffffffe0u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003f8u, 0x0020003au, 0x00400083u, 0x00800101u, 0x010001fdu, 0x020003f5u, 0x0005003fu, 0x0000003fu, 0x00000020u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0x4ed751d9u, 0x43cdc356u, 0xb0ddb48bu, 0x38c434d2u, 0x4ed751dau, 0xf4f6717cu, 0x43cdc356u, 0x43cdc356u, 0xf4f6717cu, 0xb73bb22au, 0x03098202u, 0x4bcdcfd7u, 0x08000c81u, 0x48c44dd5u, 0x00000000u, 0x000018dfu, 0xf7fff37eu, 0xb6dfbd9fu, 0xb73bb22au, 0x168f8d1eu, 0x03098202u, 0x108d840au, 0x4bcdcfd7u, 0xf9fdf6ebu, 0xbf3bbeabu, 0x5fafcf7eu, 0x08000c81u, 0x49204260u, 0x48c44dd5u, 0xe97072e1u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x60000000u, 0x00000004u, 0x0000000bu, 0x00000000u, 0x00000004u, 0xfffffffbu, 0xffffffffu, 0xf0000000u, 0x00000000u, 0xf0000000u, 0x00000004u, 0x00000004u, 0x0000000bu, 0x00000000u, 0x000003ffu, 0x4ed751dau, 0x7c91f828u, 0x042d7ed4u, 0x13648c2cu, 0x31d2a6dcu, 0x717cc356u, 0xf4f6c356u, 0xf4f643cdu, 0x40e163f6u, 0xfd13a0a0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0xffefffe4u, 0x0010001cu, 0x10000000u, 0x0010001cu, 0xffefffe4u, 0x00000000u, 0x00000000u, 0x0010001cu, 0x0010001cu, 0x0010001cu, 0x00000000u, 0xffefffe3u, 0xffefffe3u, 0xffefffe3u, 0x00000000u, 0x00001197u, 0x0010001cu, 0x10080004u, 0x0010001cu, 0x10080004u, 0x00000000u, 0x00080004u, 0xffefffe3u, 0xefffffffu, 0xffffffffu, 0xffffffffu, 0xffefffe3u, 0xeff7fffbu, 0xffefffe3u, 0xeff7fffbu, 0x00000000u, 0x0000007fu, 0x00000000u, 0x40000000u, 0x00000000u, 0x00800040u, 0x00000000u, 0x00000000u, 0x00800040u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000040u, 0x00000000u, 0x000002b9u, 0x0010001cu, 0x0010001cu, 0x0010001cu, 0x0010001cu, 0x0010001cu, 0x001c0000u, 0x00100000u, 0x00100000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0x0f4ef612u, 0xbce240c6u, 0x68916d08u, 0x8913779eu, 0x0f4ef612u, 0xbce240c6u, 0xbce240c6u, 0xcc3136d8u, 0xcc3136d8u, 0x70d3761eu, 0x40113618u, 0xcf3dbff9u, 0x030c8921u, 0x8f2c89e1u, 0x00000000u, 0x0000007fu, 0xfcf376deu, 0x68916d1cu, 0x70d3761eu, 0x68816d14u, 0x40113618u, 0x00000014u, 0xcf3dbff9u, 0x977e92ffu, 0x73dfff3fu, 0xffeffff7u, 0x030c8921u, 0x976e92e3u, 0x8f2c89e1u, 0x977e92ebu, 0x00000000u, 0x0000007fu, 0x0c4db600u, 0x04000733u, 0x0330c4dbu, 0x7330c4dbu, 0x00004000u, 0xff30c4dbu, 0x7330c4dbu, 0x00004000u, 0x3fffffc0u, 0x3fffffc0u, 0x00000000u, 0xff30c4dbu, 0x0330c4dbu, 0x7330c4dbu, 0x00004000u, 0x000003ffu, 0x0f4ef612u, 0x5544ae76u, 0xeda71c26u, 0x1e6bf786u, 0x7ff5ae46u, 0x40c636d8u, 0xbce236d8u, 0xbce2cc31u, 0x96a89b63u, 0x0d9523c5u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0xffff0020u, 0x00000040u, 0xb036d9ddu, 0x0001005fu, 0xffff0021u, 0x00000040u, 0x00000040u, 0x0001001fu, 0x0001001fu, 0x0001005fu, 0x00000040u, 0xfffeffe0u, 0xfffeffa0u, 0xfffeffa0u, 0x00000000u, 0x0000199fu, 0x0001005fu, 0xb4b6d9ffu, 0x0001005fu, 0x349210e2u, 0x00000040u, 0x301210c0u, 0xfffeffe0u, 0xfb7fffddu, 0xffffffffu, 0x7fdb36e2u, 0xfffeffa0u, 0x4b492600u, 0xfffeffa0u, 0xcb6def1du, 0x00000000u, 0x0000007fu, 0x00000000u, 0x80000020u, 0x00000000u, 0x606db3bau, 0x00000001u, 0x00000000u, 0x606db3bau, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000002b8u, 0x0000ffdfu, 0x0001009fu, 0x0001011fu, 0x0001021fu, 0x0001041fu, 0x001f0040u, 0x00010040u, 0x00010000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0x85d3ab9eu, 0x0000003fu, 0x0001001fu, 0x7a2c54dfu, 0x85d3ab9fu, 0x0000003fu, 0x0000003fu, 0x7a2c54a0u, 0x7a2c54a0u, 0x7a2c549fu, 0x0000001fu, 0x85d3ab7fu, 0x85d3ab40u, 0x85d3ab60u, 0x00000000u, 0x0000199fu, 0x7a2c54bfu, 0x7fffffffu, 0x7a2c549fu, 0x7ffeffe0u, 0x0000001fu, 0x00000000u, 0x85d3ab7fu, 0x8001001fu, 0xffffffdfu, 0xfffeffe0u, 0x85d3ab40u, 0x80000000u, 0x85d3ab60u, 0x8001001fu, 0x00000000u, 0x0000007fu, 0x00000000u, 0x0000003fu, 0x0000003fu, 0x0001001fu, 0x00000000u, 0x0000003fu, 0x0001001fu, 0x00000000u, 0x7fffffffu, 0x00000000u, 0xffffffffu, 0x0000003fu, 0x0000003fu, 0x0001001fu, 0x00000000u, 0x000003ffu, 0x7a2c5461u, 0x7a2c551eu, 0x7a2c559cu, 0x7a2c5698u, 0x7a2c5890u, 0x54a0003fu, 0x7a2c003fu, 0x7a2c0000u, 0x0000001eu, 0x0000001eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu} +}; +constexpr const char* Names[64] = {"subb_u32", "cselect_b64 lo", "cselect_b64 hi", "add_i32", "sub_i32", "min_i32", "min_u32", "max_i32", "max_u32", "xor_b32", "andn2_b32", "orn2_b32", "nor_b32", "xnor_b32", nullptr, "scc subb_u32,cselect_b64,add_i32,sub_i32,min_i32,min_u32,max_i32,max_u32,xor_b32,andn2_b32,orn2_b32,nor_b32,xnor_b32", "or_b64 lo", "or_b64 hi", "xor_b64 lo", "xor_b64 hi", "andn2_b64 lo", "andn2_b64 hi", "orn2_b64 lo", "orn2_b64 hi", "nand_b64 lo", "nand_b64 hi", "nor_b64 lo", "nor_b64 hi", "xnor_b64 lo", "xnor_b64 hi", nullptr, "scc or_b64,xor_b64,andn2_b64,orn2_b64,nand_b64,nor_b64,xnor_b64", "lshl_b64 lo", "lshl_b64 hi", "lshr_b32", "lshr_b64 lo", "lshr_b64 hi", "ashr_i32", "ashr_i64 lo", "ashr_i64 hi", "bfm_b32", "bfm_b64 lo", "bfm_b64 hi", "bfe_i32", "bfe_u32", "bfe_u64 lo", "bfe_u64 hi", "scc lshl_b64,lshr_b32,lshr_b64,ashr_i32,ashr_i64,bfm_b32,bfm_b64,bfe_i32,bfe_u32,bfe_u64", "absdiff_i32", "lshl1_add_u32", "lshl2_add_u32", "lshl3_add_u32", "lshl4_add_u32", "pack_ll_b32_b16", "pack_lh_b32_b16", "pack_hh_b32_b16", "mul_hi_u32", "mul_hi_i32", nullptr, nullptr, nullptr, nullptr, nullptr, "scc absdiff_i32,lshl1_add_u32,lshl2_add_u32,lshl3_add_u32,lshl4_add_u32,pack_ll_b32_b16,pack_lh_b32_b16,pack_hh_b32_b16,mul_hi_u32,mul_hi_i32"}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("scalar sop2: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 64; ++i) { + if (Names[i] != nullptr) Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("scalar sop2 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index 4be3e2626..a7e575017 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -211,6 +211,7 @@ private: bool bfmB32(const RdnaInstruction& inst); IrU32 rightMask32(IrU32 count); IrU64 rightMask64(IrU32 count); + IrU32 extractBits32(IrU32 source, IrU32 offset, IrU32 rawCount, bool sign); bool sBfmB64(const RdnaInstruction& inst); bool sBfeU32(const RdnaInstruction& inst, bool sign); bool sBfeU64(const RdnaInstruction& inst); diff --git a/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp b/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp index 48fedcf5c..a2a15bbe2 100644 --- a/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp +++ b/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp @@ -154,6 +154,8 @@ void TranslationContext::sLshlAddU32(const RdnaInstruction& inst, std::uint32_t const IrU32 rhs = readU32(sourceAt(inst, 1u)); const IrU32 shifted(ir.ShiftLeftLogical(lhs.Value(), ir.Constant(shiftAmount))); const IrU32 result(ir.IAdd(shifted.Value(), rhs.Value())); + const IrU1 shiftedOut(ir.INotEqual(ir.ShiftRightLogical(lhs.Value(), ir.Constant(32u - shiftAmount)), ir.Constant(0u))); + ir.SetScc(ir.LogicalOr(shiftedOut.Value(), ir.ULessThan(result.Value(), shifted.Value()))); writeRawU32(inst.destination, result); } diff --git a/core/shader/recompiler/Translation/src/IntegerInstructions.cpp b/core/shader/recompiler/Translation/src/IntegerInstructions.cpp index 7e895e5f3..e0e565607 100644 --- a/core/shader/recompiler/Translation/src/IntegerInstructions.cpp +++ b/core/shader/recompiler/Translation/src/IntegerInstructions.cpp @@ -589,15 +589,21 @@ bool TranslationContext::sBfmB64(const RdnaInstruction& inst) { return true; } +IrU32 TranslationContext::extractBits32(IrU32 source, IrU32 offset, IrU32 rawCount, bool sign) { + const IrU32 count(ir.Emit(IrOpcode::UMin32, IrType::U32, {&rawCount.Value(), &ir.Constant(32u)})); + const IrU32 discard(ir.BitwiseAnd(ir.ISub(ir.Constant(32u), count.Value()), ir.Constant(31u))); + const IrU32 shifted(sign ? ir.ShiftRightArithmetic(source.Value(), offset.Value()) : ir.ShiftRightLogical(source.Value(), offset.Value())); + const IrU32 aligned(ir.ShiftLeftLogical(shifted.Value(), discard.Value())); + const IrU32 field(sign ? ir.ShiftRightArithmetic(aligned.Value(), discard.Value()) : ir.ShiftRightLogical(aligned.Value(), discard.Value())); + return IrU32(ir.Select(ir.INotEqual(count.Value(), ir.Constant(0u)), field.Value(), ir.Constant(0u))); +} + bool TranslationContext::sBfeU32(const RdnaInstruction& inst, bool sign) { const IrU32 source = readU32(sourceAt(inst, 0u)); const IrU32 field = readU32(sourceAt(inst, 1u)); const IrU32 offset(ir.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&field.Value(), &ir.Constant(0u), &ir.Constant(5u)})); const IrU32 rawCount(ir.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&field.Value(), &ir.Constant(16u), &ir.Constant(7u)})); - const IrU32 available(ir.ISub(ir.Constant(32u), offset.Value())); - const IrU32 count(ir.Emit(IrOpcode::UMin32, IrType::U32, {&rawCount.Value(), &available.Value()})); - const IrOpcode opcode = sign ? IrOpcode::BitFieldSExtract : IrOpcode::BitFieldUExtract; - const IrU32 result(ir.Emit(opcode, IrType::U32, {&source.Value(), &offset.Value(), &count.Value()})); + const IrU32 result = extractBits32(source, offset, rawCount, sign); writeOperand(inst.destination, &result.Value()); ir.SetScc(ir.INotEqual(result.Value(), ir.Constant(0u))); return true; From cebc1e82892b128d9b5ffe522a3c1b57895576bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 16:24:32 +0200 Subject: [PATCH 012/164] fix(shader): DPP16 operands of v_pk_fmac_f16 and the relative moves v_pk_fmac_f16 read its accumulator through the permuted destination and wrote without the DPP write mask. v_movrels/movrelsd/movrelsd_2 dropped the source permute, and the relative writes dropped the DPP write mask. Co-Authored-By: Claude --- .../tests/execution/Dpp16Operands.cpp | 182 ++++++++++++++++++ .../src/ControlFlowInstructions.cpp | 4 +- .../Translation/src/FloatInstructions.cpp | 5 +- 3 files changed, 186 insertions(+), 5 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Dpp16Operands.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16Operands.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16Operands.cpp new file mode 100644 index 000000000..7c144a245 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16Operands.cpp @@ -0,0 +1,182 @@ +#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 +#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, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e500304, 0x7e520305, 0x7e140306, + 0x7e1602ff, 0xdeaddead, 0x7e5802ff, 0xdeaddead, 0x7e5a02ff, 0xdeaddead, 0x7e5c02ff, 0xdeaddead, + 0x7e5e02ff, 0xdeaddead, 0x7e6002ff, 0xdeaddead, 0x7e6202ff, 0xdeaddead, 0xbefe03c1, 0xbefc0381, + 0x78140afa, 0xff004b04, 0x7e1686fa, 0xff004b28, 0x7e5888fa, 0xff004b28, 0x7e5c84fa, 0xff004b28, + 0xbefc03ff, 0x00010001, 0x7e6090fa, 0xff004b28, 0xbefe03c1, 0x7e18032d, 0x7e1a032f, 0x7e1c0331, + 0x7e500304, 0x7e520305, 0x7e1e0306, 0x7e2002ff, 0xdeaddead, 0x7e5802ff, 0xdeaddead, 0x7e5a02ff, + 0xdeaddead, 0x7e5c02ff, 0xdeaddead, 0x7e5e02ff, 0xdeaddead, 0x7e6002ff, 0xdeaddead, 0x7e6202ff, + 0xdeaddead, 0xbefe03c1, 0xbefc0381, 0x781e0afa, 0xff011104, 0x7e2086fa, 0xff011128, 0x7e5888fa, + 0xff011128, 0x7e5c84fa, 0xff011128, 0xbefc03ff, 0x00010001, 0x7e6090fa, 0xff011128, 0xbefe03c1, + 0x7e22032d, 0x7e24032f, 0x7e260331, 0x7e500304, 0x7e520305, 0x7e280306, 0x7e2a02ff, 0xdeaddead, + 0x7e5802ff, 0xdeaddead, 0x7e5a02ff, 0xdeaddead, 0x7e5c02ff, 0xdeaddead, 0x7e5e02ff, 0xdeaddead, + 0x7e6002ff, 0xdeaddead, 0x7e6202ff, 0xdeaddead, 0xbefe03ff, 0x33cc0ff3, 0xbefc0381, 0x78280afa, + 0x5a00b104, 0x7e2a86fa, 0x5a00b128, 0x7e5888fa, 0x5a00b128, 0x7e5c84fa, 0x5a00b128, 0xbefc03ff, + 0x00010001, 0x7e6090fa, 0x5a00b128, 0xbefe03c1, 0x7e2c032d, 0x7e2e032f, 0x7e300331, 0xd5780019, + 0x04c25d2c, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, + 0x80011603, 0xbf810000, +}; + +constexpr const char* Names[] = {"quad_perm_pk_fmac_f16", "quad_perm_movrels", "quad_perm_movrelsd", "quad_perm_movreld", "quad_perm_movrelsd_2", "row_shr_pk_fmac_f16", "row_shr_movrels", "row_shr_movrelsd", "row_shr_movreld", "row_shr_movrelsd_2", "masks_exec_pk_fmac_f16", "masks_exec_movrels", "masks_exec_movrelsd", "masks_exec_movreld", "masks_exec_movrelsd_2", "relative_bases"}; +constexpr std::uint32_t Rows[32][4] = { + {0x4000c4abu, 0x00000000u, 0x4a3d4000u, 0xe255accbu}, + {0x35558000u, 0x80000000u, 0x2e668000u, 0x8c3d5f16u}, + {0xb1c73f01u, 0x80003f01u, 0x35553c00u, 0x9d3c7decu}, + {0x40004000u, 0x42484000u, 0x3f013c00u, 0xd0944996u}, + {0x4a3d3f01u, 0x4a3dc4abu, 0x35552e66u, 0x406329bcu}, + {0xc4abc4abu, 0x2e66b1c7u, 0x00004a3du, 0x1389b372u}, + {0xb1c7c4abu, 0x40000000u, 0x00004248u, 0x49a047dcu}, + {0x3f01b800u, 0x4a3db1c7u, 0xb1c78000u, 0x4f1a8a1du}, + {0x3c00b1c7u, 0xc4abc4abu, 0x3f012e66u, 0x17371472u}, + {0x2e668000u, 0x00003f01u, 0x40002e66u, 0x81a0ba05u}, + {0x80003f01u, 0x2e660000u, 0x42482e66u, 0x90624fe3u}, + {0x3f010000u, 0x00003c00u, 0x80003555u, 0xc40d31b5u}, + {0xb8003c00u, 0xb1c70000u, 0x4a3d4000u, 0xb7a1774fu}, + {0x80002e66u, 0x3f01b800u, 0x3f013555u, 0xd9814559u}, + {0x40004a3du, 0xb1c73f01u, 0x4a3d3f01u, 0xb6eafff5u}, + {0x00004248u, 0x00002e66u, 0x4a3d4248u, 0x8c53765fu}, + {0x4a3d0000u, 0xb1c73f01u, 0x2e662e66u, 0x33d91808u}, + {0x42484248u, 0x3c003c00u, 0xb8004a3du, 0xe1cd7be8u}, + {0x80003f01u, 0x00003f01u, 0xc4ab3555u, 0x42969a50u}, + {0xb1c73f01u, 0x3f010000u, 0x3f012e66u, 0x8ece78b0u}, + {0x4a3d3c00u, 0x4248c4abu, 0x3f014000u, 0x703e53cfu}, + {0xb1c7b1c7u, 0x00004248u, 0x3f01b800u, 0xf79edc82u}, + {0x2e66b1c7u, 0x3f014000u, 0x4a3d4248u, 0xe73c49eau}, + {0x3f01c4abu, 0x40003555u, 0xb1c72e66u, 0x2577c324u}, + {0x3f013c00u, 0x40003c00u, 0x3f018000u, 0x8bed5b65u}, + {0x40008000u, 0x35553555u, 0x40008000u, 0x0b6ec6f3u}, + {0x00004248u, 0x4a3d4a3du, 0xc4abb800u, 0x01504585u}, + {0xc4ab4a3du, 0x80008000u, 0x3f01b1c7u, 0xb870e4e1u}, + {0xb8000000u, 0x35553f01u, 0x8000b800u, 0xf551abfdu}, + {0x8000b1c7u, 0x00004248u, 0x40002e66u, 0xab266981u}, + {0xb800b800u, 0x80004248u, 0x00002e66u, 0x63f1bbbcu}, + {0xb8000000u, 0x35552e66u, 0x3c004000u, 0x63ac4f14u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x4a3d4000u, 0x42484000u, 0x42484000u, 0x40004000u, 0x42484000u, 0x4a3d4000u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0x4a3d4000u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x2e660000u, 0x80003f01u, 0x80003f01u, 0xb1c73f01u, 0x80003f01u, 0x2e668000u, 0x00000000u, 0x00000000u, 0x4000c4abu, 0x00000000u, 0x2e668000u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x3555c72cu, 0x00000000u, 0x00000000u, 0x4000c4abu, 0x00000000u, 0x35553c00u, 0x80000000u, 0x80000000u, 0x35558000u, 0x80000000u, 0x35553c00u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x41983c00u, 0x80000000u, 0x80000000u, 0x35558000u, 0x80000000u, 0x3cbc4480u, 0x80003f01u, 0x80003f01u, 0xb1c73f01u, 0x80003f01u, 0x3f013c00u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x4d8b40deu, 0x4a3db1c7u, 0x4a3db1c7u, 0x3f01b800u, 0x4a3db1c7u, 0x4e52c89eu, 0x42484000u, 0x42484000u, 0x40004000u, 0x42484000u, 0xd33d4d79u, 0x2e66b1c7u, 0x2e66b1c7u, 0xc4abc4abu, 0x2e66b1c7u, 0xdeaddeadu}, + {0xa49f4aa9u, 0x40000000u, 0x40000000u, 0xb1c7c4abu, 0x40000000u, 0x3cfd4a15u, 0x4a3dc4abu, 0x4a3dc4abu, 0x4a3d3f01u, 0x4a3dc4abu, 0x3cfd4a15u, 0x4a3dc4abu, 0x4a3dc4abu, 0x4a3d3f01u, 0x4a3dc4abu, 0xdeaddeadu}, + {0x4e3d4248u, 0x4a3dc4abu, 0x4a3dc4abu, 0x4a3d3f01u, 0x4a3dc4abu, 0xc8ab4248u, 0x2e66b1c7u, 0x2e66b1c7u, 0xc4abc4abu, 0x2e66b1c7u, 0x43014248u, 0x4a3db1c7u, 0x4a3db1c7u, 0x3f01b800u, 0x4a3db1c7u, 0xdeaddeadu}, + {0xd34d3abeu, 0x2e66b1c7u, 0x2e66b1c7u, 0xc4abc4abu, 0x2e66b1c7u, 0xc0de3abeu, 0x40000000u, 0x40000000u, 0xb1c7c4abu, 0x40000000u, 0xc0de3abeu, 0x40000000u, 0x40000000u, 0xb1c7c4abu, 0x40000000u, 0xdeaddeadu}, + {0xc66c2e66u, 0x00003c00u, 0x00003c00u, 0x3f010000u, 0x00003c00u, 0xc66c40deu, 0x4a3db1c7u, 0x4a3db1c7u, 0x3f01b800u, 0x4a3db1c7u, 0x3f012e66u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x40004255u, 0x2e660000u, 0x2e660000u, 0x80003f01u, 0x2e660000u, 0x4000b2ebu, 0xc4abc4abu, 0xc4abc4abu, 0x3c00b1c7u, 0xc4abc4abu, 0x40002e66u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x427b2e66u, 0xc4abc4abu, 0xc4abc4abu, 0x3c00b1c7u, 0xc4abc4abu, 0x424d2e66u, 0x00003f01u, 0x00003f01u, 0x2e668000u, 0x00003f01u, 0x42482e66u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x00003555u, 0x00003f01u, 0x00003f01u, 0x2e668000u, 0x00003f01u, 0x8000402bu, 0x2e660000u, 0x2e660000u, 0x80003f01u, 0x2e660000u, 0x80003555u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x4a3d4000u, 0x00002e66u, 0x00002e66u, 0x00004248u, 0x00002e66u, 0x4a154000u, 0x00003c00u, 0x00003c00u, 0x3f010000u, 0x00003c00u, 0x4a3d4000u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x4541c5e8u, 0xb1c73f01u, 0xb1c73f01u, 0x40004a3du, 0xb1c73f01u, 0x3b01b156u, 0xb1c70000u, 0xb1c70000u, 0xb8003c00u, 0xb1c70000u, 0x3f013555u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x4a494301u, 0xb1c70000u, 0xb1c70000u, 0xb8003c00u, 0xb1c70000u, 0x4a3d3fb4u, 0x3f01b800u, 0x3f01b800u, 0x80002e66u, 0x3f01b800u, 0x4a3d3f01u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x4a3d424du, 0x3f01b800u, 0x3f01b800u, 0x80002e66u, 0x3f01b800u, 0x4a3d4463u, 0xb1c73f01u, 0xb1c73f01u, 0x40004a3du, 0xb1c73f01u, 0x4a3d4248u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x303e4255u, 0x3f010000u, 0x3f010000u, 0xb1c73f01u, 0x3f010000u, 0x2e662e66u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0x2e662e66u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb8004b1du, 0x00003f01u, 0x00003f01u, 0x80003f01u, 0x00003f01u, 0x49fd4a3du, 0xb1c73f01u, 0xb1c73f01u, 0x4a3d0000u, 0xb1c73f01u, 0xb8004a3du, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc4ab3555u, 0xb1c73f01u, 0xb1c73f01u, 0x4a3d0000u, 0xb1c73f01u, 0xc4ab45d5u, 0x3c003c00u, 0x3c003c00u, 0x42484248u, 0x3c003c00u, 0xc4ab3555u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x47402e66u, 0x3c003c00u, 0x3c003c00u, 0x42484248u, 0x3c003c00u, 0x3f012e66u, 0x00003f01u, 0x00003f01u, 0x80003f01u, 0x00003f01u, 0x3f012e66u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x47404df3u, 0x40003555u, 0x40003555u, 0x3f01c4abu, 0x40003555u, 0x3cbcc62cu, 0x3f010000u, 0x3f010000u, 0xb1c73f01u, 0x3f010000u, 0x3f014000u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x3f01bc45u, 0x3f014000u, 0x3f014000u, 0x2e66b1c7u, 0x3f014000u, 0x3f014148u, 0x4248c4abu, 0x4248c4abu, 0x4a3d3c00u, 0x4248c4abu, 0x3f01b800u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x504a4524u, 0x4248c4abu, 0x4248c4abu, 0x4a3d3c00u, 0x4248c4abu, 0x4a15418fu, 0x00004248u, 0x00004248u, 0xb1c7b1c7u, 0x00004248u, 0x4a3d4248u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb8552918u, 0x00004248u, 0x00004248u, 0xb1c7b1c7u, 0x00004248u, 0x24f82918u, 0x3f014000u, 0x3f014000u, 0x2e66b1c7u, 0x3f014000u, 0xb1c72e66u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc7964a3du, 0x80008000u, 0x80008000u, 0xc4ab4a3du, 0x80008000u, 0x4541c4abu, 0x40003555u, 0x40003555u, 0x3f01c4abu, 0x40003555u, 0x3f018000u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x40003c30u, 0x4a3d4a3du, 0x4a3d4a3du, 0x00004248u, 0x4a3d4a3du, 0x412b3555u, 0x40003c00u, 0x40003c00u, 0x3f013c00u, 0x40003c00u, 0x40008000u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x4c4b49fdu, 0x40003c00u, 0x40003c00u, 0x3f013c00u, 0x40003c00u, 0x4d12b800u, 0x35553555u, 0x35553555u, 0x40008000u, 0x35553555u, 0xc4abb800u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x3f01b1c7u, 0x35553555u, 0x35553555u, 0x40008000u, 0x35553555u, 0x3f01b1c7u, 0x4a3d4a3du, 0x4a3d4a3du, 0x00004248u, 0x4a3d4a3du, 0x3f01b1c7u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb155b800u, 0x35552e66u, 0x35552e66u, 0xb8000000u, 0x35552e66u, 0xbe394d56u, 0x80008000u, 0x80008000u, 0xc4ab4a3du, 0x80008000u, 0x8000b800u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x4000bde2u, 0x80004248u, 0x80004248u, 0xb800b800u, 0x80004248u, 0x40002e66u, 0x35553f01u, 0x35553f01u, 0xb8000000u, 0x35553f01u, 0x40002e66u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x00002e66u, 0x35553f01u, 0x35553f01u, 0xb8000000u, 0x35553f01u, 0x0000b779u, 0x00004248u, 0x00004248u, 0x8000b1c7u, 0x00004248u, 0x00002e66u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x3c003feeu, 0x00004248u, 0x00004248u, 0x8000b1c7u, 0x00004248u, 0x3aab3fcdu, 0x80004248u, 0x80004248u, 0xb800b800u, 0x80004248u, 0x3c004000u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu} +}; +void Fill(std::uint32_t tid, std::uint32_t* words) { + for (std::uint32_t i = 0; i < 4; ++i) words[i] = Rows[tid][i]; +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("dpp16 operands: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) Expect(tid, out[i], Expected[tid][i], Names[i]); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < Threads) { + std::printf("skipped, the device's subgroups are narrower than a wave (%u lanes)\n", device->Target().subgroupSize); + return VulkanTestSkipped; + } + Run(*device); + Check(); + std::puts("dpp16 operands tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp b/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp index 48fedcf5c..7da5b29c8 100644 --- a/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp +++ b/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp @@ -258,11 +258,11 @@ IrU32 TranslationContext::readRelativeVector(const RdnaOperand& base, IrValue& o IrValue& hit = ir.IEqual(offset, ir.Constant(reg - base.reg)); result = IrU32(ir.Select(hit, ir.GetVectorReg(static_cast(reg)), result.Value())); } - return result; + return applyBitSourceModifiers(base, result); } void TranslationContext::writeRelativeVector(const RdnaOperand& base, IrValue& offset, IrU32 value, const char* name) { - const RdnaOperand destination = plainOperand(base); + const RdnaOperand& destination = base; if (destination.kind != RdnaOperandKind::VectorRegister) { throw std::runtime_error(std::string(name) + " destination is not a vector register"); } diff --git a/core/shader/recompiler/Translation/src/FloatInstructions.cpp b/core/shader/recompiler/Translation/src/FloatInstructions.cpp index 4ad4de1d6..d1748db1c 100644 --- a/core/shader/recompiler/Translation/src/FloatInstructions.cpp +++ b/core/shader/recompiler/Translation/src/FloatInstructions.cpp @@ -16,7 +16,7 @@ bool TranslationContext::packedFloat16(const RdnaInstruction& inst, IrOpcode opc const IrF32 lhs = readF16LaneAsF32(sourceAt(inst, 0u), high, true); const IrF32 rhs = readF16LaneAsF32(sourceAt(inst, 1u), high, true); if (accumulator) { - const IrF32 acc = readF16LaneAsF32(inst.destination, high, true); + const IrF32 acc = readF16LaneAsF32(accumulatorOperand(inst), high, true); return applyF32ResultModifiers(inst.destination, IrF32(ir.Emit(opcode, IrType::F32, {&lhs.Value(), &rhs.Value(), &acc.Value()}))); } if (inst.sourceCount == 3u) { @@ -25,7 +25,6 @@ bool TranslationContext::packedFloat16(const RdnaInstruction& inst, IrOpcode opc } return applyF32ResultModifiers(inst.destination, IrF32(ir.Emit(opcode, IrType::F32, {&lhs.Value(), &rhs.Value()}))); }; - const RdnaOperand raw = plainOperand(inst.destination); IrU32 result = packHalf2x16(translateLane(false), translateLane(true)); if (quietSnan) { const auto quietSnanLane = [&](const RdnaOperand& operand, bool high) { @@ -44,7 +43,7 @@ bool TranslationContext::packedFloat16(const RdnaInstruction& inst, IrOpcode opc }; result = packU16Lanes(overrideLane(false), overrideLane(true)); } - writeOperand(raw, &result.Value()); + writeRawU32(inst.destination, result); return true; } From b02c80254e485a3991840d33cfe496b01cd2f450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 17:03:09 +0200 Subject: [PATCH 013/164] fix(shader): v_cvt_rpi/flr_i32_f32 NaN and exact round-half-up Co-Authored-By: Claude --- .../tests/execution/Vop1Vop2Integer.cpp | 173 ++++++++++++++++++ .../Translation/TranslationContext.hpp | 1 + .../Translation/src/ConvertInstructions.cpp | 19 +- 3 files changed, 189 insertions(+), 4 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Vop1Vop2Integer.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop1Vop2Integer.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop1Vop2Integer.cpp new file mode 100644 index 000000000..773fd3e38 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop1Vop2Integer.cpp @@ -0,0 +1,173 @@ +#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 +#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 = 64; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060088, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e146f04, 0x7e167104, 0x7e187304, + 0x7e1a7504, 0x7e1c7704, 0x7e1e0d04, 0x7e200b04, 0x7e220f04, 0x7e241104, 0x7e261904, 0x7e281b04, + 0x7e2a1d04, 0x7e2ca304, 0x7e2ea104, 0x7e30a504, 0x7e32a704, 0x12340b04, 0x14360b04, 0x16380b04, + 0x183a0b04, 0x223c0b04, 0x243e0b04, 0x26400b04, 0x28420b04, 0x2c440b04, 0x30460b04, 0x34480b04, + 0x364a0b04, 0x384c0b04, 0x3a4e0b04, 0x3c500b04, 0x4a520b04, 0x4c540b04, 0x4e560b04, 0x36100c81, + 0x7d8a1080, 0x02580b04, 0x36100c81, 0x7d8a1080, 0x505a0b04, 0xd501002e, 0x01a90280, 0x36100c81, + 0x7d8a1080, 0x525e0b04, 0xd5010030, 0x01a90280, 0x36100c81, 0x7d8a1080, 0x54620b04, 0xd5010032, + 0x01a90280, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, + 0x80011603, 0xe0781040, 0x80011a03, 0xe0781050, 0x80011e03, 0xe0781060, 0x80012203, 0xe0781070, + 0x80012603, 0xe0781080, 0x80012a03, 0xe0781090, 0x80012e03, 0xe07810a0, 0x80013203, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x3effffffu, 0x00000005u, 0x00000001u, 0x00000000u}, + {0xbeffffffu, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x4b000001u, 0x7fffffffu, 0x00000001u, 0x00000000u}, + {0xcb000001u, 0x00000021u, 0x00000000u, 0x00000000u}, + {0x4affffffu, 0x00ffffffu, 0x00000001u, 0x00000000u}, + {0xbf000000u, 0xff800000u, 0x00000000u, 0x00000000u}, + {0x7fc00000u, 0x00800000u, 0x00000001u, 0x00000000u}, + {0xffc00000u, 0x00000020u, 0x00000000u, 0x00000000u}, + {0x7f800001u, 0xffffffffu, 0x00000001u, 0x00000000u}, + {0xff800001u, 0x00000000u, 0x00000001u, 0x00000000u}, + {0x4effffffu, 0x00000001u, 0x00000000u, 0x00000000u}, + {0xcf000000u, 0x80000001u, 0x00000001u, 0x00000000u}, + {0xbf000001u, 0x00800000u, 0x00000000u, 0x00000000u}, + {0xaf000000u, 0x00007fffu, 0x00000001u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xbf000000u, 0x00000003u}, + {0x58457b3au, 0xa7913051u, 0xfffffffeu, 0xcf000000u}, + {0x80000000u, 0x00000000u, 0xad489bceu, 0x96fc31a0u}, + {0xfffffffeu, 0x00000001u, 0x80000000u, 0xffffffffu}, + {0x39a77c00u, 0xc0200000u, 0x0000fc00u, 0x4f800000u}, + {0x5c90a958u, 0x00ffffffu, 0x3e7d1bfbu, 0x4f000000u}, + {0xffff890bu, 0x72158370u, 0x00003c00u, 0x58d5563du}, + {0x14a0f9e7u, 0x00003800u, 0x00008000u, 0xdd2e1609u}, + {0x83f73f16u, 0xa7abe1c2u, 0x0dd27a65u, 0x0000bc00u}, + {0x2607679du, 0xf4998d7cu, 0x7fc00000u, 0x80000001u}, + {0xdd02de92u, 0xbfc00000u, 0x007fffffu, 0x3f000000u}, + {0x3d932289u, 0x31dec4f4u, 0x3606defcu, 0x9abcdef0u}, + {0x5dbe3023u, 0x00008000u, 0x3836e865u, 0x18189af4u}, + {0xffff5884u, 0x12345678u, 0x4effffffu, 0x6eb4fff8u}, + {0x33061fbcu, 0xd1cee715u, 0x0000001fu, 0xc17a4f81u}, + {0x75b00b15u, 0x00008001u, 0xc5acb068u, 0x0000bc00u}, + {0xffffffffu, 0x0000001fu, 0x0000fbffu, 0xfffffffeu}, + {0x953f48f1u, 0x93bd04cfu, 0x0000000fu, 0x0becd7b0u} +}; +constexpr std::uint32_t Expected[32][41] = { + {0xc1000000u, 0xffffff7cu, 0x00000002u, 0x00000000u, 0x00000002u, 0x4e7c0000u, 0x4e7c0000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbd800000u, 0x0000bc00u, 0x00007c00u, 0x00000000u, 0x00000000u, 0xfffffffbu, 0xffffffffu, 0x04fffffbu, 0x00000000u, 0x00000005u, 0x3effffffu, 0x00000005u, 0x3effffffu, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000005u, 0x3effffffu, 0x3efffffau, 0xc1000005u, 0x3f000004u, 0x3efffffau, 0xc1000006u, 0x00000005u, 0x3f000005u, 0x00000000u, 0x3efffff9u, 0x00000000u, 0xc1000005u, 0x00000001u}, + {0x41000000u, 0xffffff7du, 0x00000000u, 0x00000000u, 0x00000001u, 0x4f3f0000u, 0xce820000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xbd800000u, 0x0000bc00u, 0x00007c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0xbeffffffu, 0x80000000u, 0xbeffffffu, 0x00000001u, 0xffffffffu, 0x00000000u, 0x80000000u, 0xbeffffffu, 0x3effffffu, 0xc1000000u, 0x3effffffu, 0x3effffffu, 0xc1000001u, 0xbeffffffu, 0x3effffffu, 0x00000001u, 0x3effffffu, 0x00000000u, 0xc1000001u, 0x00000001u}, + {0xb4fffffeu, 0x800000d2u, 0x00000001u, 0x00000000u, 0x00000001u, 0x4e960000u, 0x4e960000u, 0x00800001u, 0x00800001u, 0x00800001u, 0x00800001u, 0x3d800000u, 0x00003c00u, 0x00003c00u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x00ffffffu, 0x00000000u, 0x4b000001u, 0x7fffffffu, 0x4b000001u, 0x7fffffffu, 0x3fffffffu, 0x3fffffffu, 0xfffffffeu, 0x4b000001u, 0x7fffffffu, 0x34fffffeu, 0xcb000001u, 0xcb000000u, 0xcb000002u, 0x34fffffeu, 0x7fffffffu, 0xcb000001u, 0x00000000u, 0xcb000001u, 0x00000001u, 0x34fffffdu, 0x00000000u}, + {0x34fffffeu, 0x800000d3u, 0x00000000u, 0x00000000u, 0x00000002u, 0x4f4b0000u, 0xce540000u, 0x00000000u, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x3d800000u, 0x00003c00u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000021u, 0x00000000u, 0x00000021u, 0x00000000u, 0xcb000001u, 0x00000021u, 0x00000021u, 0xcb000001u, 0x00000010u, 0x00000010u, 0x00000042u, 0x00000001u, 0xcb000021u, 0xcb000020u, 0x34ffffdfu, 0xcb000022u, 0xcaffffe0u, 0x35000020u, 0xcb000001u, 0xcb000022u, 0x00000000u, 0xcaffffe0u, 0x00000000u, 0x35000020u, 0x00000001u}, + {0xb5000000u, 0xffffff52u, 0x00000001u, 0x00000000u, 0x00000001u, 0x4e960000u, 0x4e960000u, 0x007fffffu, 0x007fffffu, 0x00800000u, 0x007fffffu, 0xbd800000u, 0x0000bc00u, 0x00007c00u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0xfe000001u, 0x0000ffffu, 0x00ffffffu, 0x4affffffu, 0x00ffffffu, 0x4affffffu, 0x00000000u, 0x00000000u, 0x80000000u, 0x00ffffffu, 0x4affffffu, 0x4a000000u, 0xb5ffffffu, 0x4bfffffeu, 0x4a000000u, 0xb6000000u, 0x00ffffffu, 0x4bffffffu, 0x00000000u, 0x49ffffffu, 0x00000000u, 0xb5ffffffu, 0x00000001u}, + {0x40ffffffu, 0x000000fdu, 0x00000000u, 0x00000018u, 0x00000001u, 0x4f3f0000u, 0xce820000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbf000000u, 0xff800000u, 0xbf000000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xbf000000u, 0xff800000u, 0x40800000u, 0xbf7fffffu, 0xbe800000u, 0xbf800000u, 0x40800000u, 0xbf000000u, 0xbe800000u, 0x00000001u, 0xbf800000u, 0x00000001u, 0x40800000u, 0x00000000u}, + {0x803fffffu, 0x000003feu, 0x00000001u, 0x00000016u, 0x00000001u, 0x4eff8000u, 0x4eff8000u, 0x00000000u, 0x00000000u, 0x7fffffffu, 0x7fffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00002000u, 0x00000000u, 0x00006000u, 0x00800000u, 0x7fc00000u, 0x00800000u, 0x7fc00000u, 0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u, 0x7fc00000u, 0x7f400000u, 0x80bfffffu, 0x80400000u, 0x7f400000u, 0x80c00000u, 0x00800000u, 0x80400001u, 0x00000000u, 0x7f3fffffu, 0x00000000u, 0x80bfffffu, 0x00000001u}, + {0x003fffffu, 0x000003ffu, 0x00000000u, 0x00000016u, 0x0000000au, 0x4f7fc000u, 0xca800000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xf8000000u, 0xffffffffu, 0x18000000u, 0x00000000u, 0xffc00000u, 0x00000020u, 0x00000020u, 0xffc00000u, 0x00000020u, 0x00000020u, 0x00000020u, 0x00000000u, 0xffc00020u, 0xffc00020u, 0x003fffdfu, 0xffc00020u, 0xffbfffe0u, 0x00400020u, 0xffc00000u, 0xffc00020u, 0x00000000u, 0xffbfffe0u, 0x00000000u, 0x00400020u, 0x00000001u}, + {0x807ffffeu, 0x800001feu, 0x00000001u, 0x00000000u, 0x00000001u, 0x4eff0000u, 0x4eff0000u, 0x00000000u, 0x00000000u, 0x7fffffffu, 0x7fffffffu, 0x3d800000u, 0x00003c00u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x007fffffu, 0x00000000u, 0x007fffffu, 0x00008000u, 0xffffffffu, 0x7f800001u, 0x7f800001u, 0xffffffffu, 0x7fffffffu, 0xffffffffu, 0xfffffffeu, 0x7f800001u, 0xffffffffu, 0x807ffffeu, 0x7f800001u, 0x7f800000u, 0x7f800002u, 0x807ffffeu, 0xffffffffu, 0x7f800001u, 0x00000001u, 0x7f800001u, 0x00000001u, 0x807ffffdu, 0x00000000u}, + {0x007ffffeu, 0x800001ffu, 0x00000000u, 0x00000000u, 0x00000009u, 0x4f7f8000u, 0xcafffffeu, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x3d800000u, 0x00003c00u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff800001u, 0x00000000u, 0x00000000u, 0xff800001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff800001u, 0xff800001u, 0x007ffffeu, 0xff800001u, 0xff800001u, 0x007fffffu, 0x00000000u, 0xff800002u, 0x00000000u, 0xff800000u, 0x00000000u, 0x007ffffeu, 0x00000001u}, + {0xb1000000u, 0xffffff72u, 0x00000001u, 0x00000000u, 0x00000001u, 0x4e9e0000u, 0x4e9e0000u, 0x7fffff80u, 0x7fffff80u, 0x7fffff80u, 0x7fffff80u, 0xbd800000u, 0x0000bc00u, 0x00007c00u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x00ffffffu, 0x00000000u, 0x00000001u, 0x4effffffu, 0x00000001u, 0x4effffffu, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000001u, 0x4effffffu, 0x4efffffeu, 0xb1000001u, 0x4f000000u, 0x4efffffeu, 0xb1000002u, 0x4effffffu, 0x4f000000u, 0x00000000u, 0x4efffffeu, 0x00000000u, 0xb1000002u, 0x00000001u}, + {0x30ffffffu, 0x000000f3u, 0x00000000u, 0x00000018u, 0x00000002u, 0x4f4f0000u, 0xce440000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000001u, 0xcf000000u, 0x80000001u, 0xcf000000u, 0x80000001u, 0x80000001u, 0x80000001u, 0x80000000u, 0xcf000001u, 0x4f000001u, 0xb0fffffeu, 0x4f000001u, 0x4effffffu, 0xb1000001u, 0x80000001u, 0x4f000002u, 0x00000001u, 0x4efffffeu, 0x00000000u, 0xb1000000u, 0x00000001u}, + {0x40fffffeu, 0x800000fdu, 0x00000000u, 0x00000000u, 0x00000001u, 0x4f3f0000u, 0xce820000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x3d800000u, 0x00003c00u, 0x00003c00u, 0x00000000u, 0x00000000u, 0xff800000u, 0xffffffffu, 0x00800000u, 0x00000000u, 0xbf000001u, 0x00800000u, 0x00800000u, 0xbf000001u, 0x00400000u, 0x00400000u, 0x01000000u, 0x00000000u, 0xbf800001u, 0xbf800001u, 0x407ffffeu, 0xbf800001u, 0xbe800001u, 0x417fffffu, 0xbf000001u, 0xbf800001u, 0x00000000u, 0xbe800001u, 0x00000000u, 0x417fffffu, 0x00000001u}, + {0x50ffffffu, 0x000000f5u, 0x00000000u, 0x00000018u, 0x00000001u, 0x4f2f0000u, 0xcea20000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xaf000000u, 0x00007fffu, 0x00007fffu, 0xaf000000u, 0x00007fffu, 0x00007fffu, 0x00007fffu, 0x00000000u, 0xaf007fffu, 0xaf007fffu, 0x50ff8000u, 0xaf007fffu, 0xaeff8001u, 0x51007fffu, 0x00007fffu, 0xaf008000u, 0x00000000u, 0xaeff8000u, 0x00000000u, 0x51007ffeu, 0x00000001u}, + {0xffffffffu, 0x00000000u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xa7ba84c5u, 0x5cdea21au, 0x00000001u, 0x00000001u, 0x00000001u, 0x4eb08af6u, 0x4eb08af6u, 0xffffffffu, 0x7fffffffu, 0x7fffffffu, 0x7fffffffu, 0xbec00000u, 0x000077b4u, 0x000077b4u, 0x0000e740u, 0x00007fffu, 0xaef0dd5au, 0xffffe1ecu, 0xe8f0dd5au, 0x00002767u, 0xa7913051u, 0x58457b3au, 0x58457b3au, 0xa7913051u, 0x00000029u, 0xffffffe9u, 0x44000000u, 0x00013010u, 0xffd57b7bu, 0xffd44b6bu, 0x002bb494u, 0xffd6ab8bu, 0xb0b44ae9u, 0x4f4bb517u, 0x58457b3au, 0xffd6ab8bu, 0x00000000u, 0xb0b44ae9u, 0x00000001u, 0x4f4bb517u, 0x00000000u}, + {0x7fffffffu, 0x00000001u, 0x00000000u, 0x0000001fu, 0x00000001u, 0x4f000000u, 0xcf000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x7fffffffu, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000001u}, + {0x00000001u, 0x7fffffffu, 0x00000000u, 0x00000001u, 0x0000001fu, 0x4f800000u, 0xc0000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0xbe000000u, 0x0000c000u, 0x00007c00u, 0x00000000u, 0x00000000u, 0xfffffffeu, 0xffffffffu, 0x00fffffeu, 0x00000000u, 0xfffffffeu, 0x00000001u, 0x00000001u, 0xfffffffeu, 0x00000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x00000000u, 0xffffffffu, 0xfffffffdu, 0x00000003u, 0xfffffffeu, 0xffffffffu, 0x00000000u, 0xfffffffdu, 0x00000000u, 0x00000003u, 0x00000001u}, + {0xc65883ffu, 0x003ee59cu, 0x00000002u, 0x0000000au, 0x00000002u, 0x4e669df0u, 0x4e669df0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000077c0u, 0x000077c0u, 0x0000ffffu, 0x00007fffu, 0x80000000u, 0xfffff4efu, 0x80000000u, 0x000014efu, 0xc0200000u, 0x39a77c00u, 0x39a77c00u, 0xc0200000u, 0xc0200000u, 0xc0200000u, 0xc0200000u, 0x00200000u, 0xf9a77c00u, 0xf9877c00u, 0x067883ffu, 0xf9c77c00u, 0x79877c00u, 0x86788400u, 0x39a77c00u, 0xf9c77c00u, 0x00000000u, 0x79877c00u, 0x00000001u, 0x86788400u, 0x00000000u}, + {0xa36f56a7u, 0x1a95093au, 0x00000001u, 0x00000003u, 0x00000001u, 0x4eb92153u, 0x4eb92153u, 0xffffffffu, 0x7fffffffu, 0x7fffffffu, 0x7fffffffu, 0xbf000000u, 0x0000f56au, 0x0000794bu, 0x00000000u, 0x00000000u, 0x006f56a8u, 0x00000000u, 0x576f56a8u, 0x000090a9u, 0x00ffffffu, 0x5c90a958u, 0x00ffffffu, 0x5c90a958u, 0x00000000u, 0x00000000u, 0xff000000u, 0x0090a958u, 0x5cffffffu, 0x5c6f56a7u, 0xa390a958u, 0x5d90a957u, 0x5b90a959u, 0xa46f56a7u, 0x00ffffffu, 0x5d90a958u, 0x00000000u, 0x5b90a958u, 0x00000000u, 0xa46f56a6u, 0x00000001u}, + {0x000076f4u, 0xd091ffffu, 0x00000000u, 0x00000000u, 0x00000011u, 0x4f7fff89u, 0xc6edea00u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0xbea00000u, 0x0000f76fu, 0x00007848u, 0x00000000u, 0x00000000u, 0x00d395d0u, 0xfffffff6u, 0x70d395d0u, 0x00001579u, 0xffff890bu, 0x72158370u, 0x72158370u, 0xffff890bu, 0x000e42b0u, 0x000e42b0u, 0xac1b8000u, 0x72158100u, 0xffff8b7bu, 0x8dea0a7bu, 0x7215f584u, 0x72150c7bu, 0x8dea059bu, 0x7215fa65u, 0xffff890bu, 0x72150c7bu, 0x00000001u, 0x8dea059bu, 0x00000000u, 0x7215fa65u, 0x00000001u}, + {0xeb5f0618u, 0xe79f0528u, 0x00000003u, 0x00000000u, 0x00000003u, 0x4da507cfu, 0x4da507cfu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ee00000u, 0x0000e619u, 0x00007bcfu, 0x00000000u, 0x00008000u, 0x36aa8800u, 0xffffffebu, 0x36aa8800u, 0x00000023u, 0x00003800u, 0x14a0f9e7u, 0x00003800u, 0x14a0f9e7u, 0x00000070u, 0x00000070u, 0x001c0000u, 0x00003800u, 0x14a0f9e7u, 0x14a0c1e7u, 0xeb5f3e18u, 0x14a131e7u, 0x14a0c1e7u, 0xeb5f3e19u, 0x14a0f9e7u, 0x14a131e7u, 0x00000000u, 0x14a0c1e7u, 0x00000000u, 0xeb5f3e19u, 0x00000001u}, + {0x7c08c0e9u, 0x68fcefc1u, 0x00000000u, 0x00000001u, 0x00000001u, 0x4f03f73fu, 0xcef81182u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x3ec00000u, 0x000073e3u, 0x000073e3u, 0x00000001u, 0x00000001u, 0x558224acu, 0x000002e0u, 0x2d8224acu, 0x0000a601u, 0x83f73f16u, 0xa7abe1c2u, 0x83f73f16u, 0xa7abe1c2u, 0x0000029eu, 0xfffffe9eu, 0x70800000u, 0x83a32102u, 0xa7ffffd6u, 0x245cded4u, 0xdba3212bu, 0x2ba320d8u, 0xdc4b5d54u, 0x23b4a2acu, 0xa7abe1c2u, 0x2ba320d9u, 0x00000001u, 0xdc4b5d53u, 0x00000001u, 0x23b4a2abu, 0x00000000u}, + {0xd9f89862u, 0xb9e6e064u, 0x00000002u, 0x00000000u, 0x00000002u, 0x4e181d9eu, 0x4e181d9eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbe400000u, 0x0000767au, 0x0000767au, 0x0000079du, 0x0000079du, 0x677ca90cu, 0xfffffd09u, 0x047ca90cu, 0x00000471u, 0xf4998d7cu, 0x2607679du, 0x2607679du, 0xf4998d7cu, 0x00000007u, 0xffffffffu, 0x80000000u, 0x2401051cu, 0xf69feffdu, 0xd29eeae1u, 0x2d61151eu, 0x1aa0f519u, 0x316dda21u, 0xce9225dfu, 0x2607679du, 0x1aa0f519u, 0x00000001u, 0x316dda21u, 0x00000001u, 0xce9225dfu, 0x00000000u}, + {0x22fd216du, 0x497b40bbu, 0x00000000u, 0x00000001u, 0x00000002u, 0x4f5d02dfu, 0xce0bf486u, 0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x3e000000u, 0x0000f02eu, 0x00007af5u, 0x00000000u, 0x0000fe5cu, 0x5b800000u, 0xffffff48u, 0xed800000u, 0x00000226u, 0xbfc00000u, 0xdd02de92u, 0xbfc00000u, 0xdd02de92u, 0x00002ff0u, 0xffffeff0u, 0x00000000u, 0x9d000000u, 0xffc2de92u, 0x62c2de92u, 0x9d3d216du, 0x9cc2de92u, 0x1d42de92u, 0xe2bd216eu, 0xbfc00000u, 0x9cc2de93u, 0x00000001u, 0x1d42de91u, 0x00000000u, 0xe2bd216du, 0x00000001u}, + {0xc26cdd76u, 0x9144c9bcu, 0x00000002u, 0x00000000u, 0x00000002u, 0x4e764c8au, 0x4e764c8au, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbee00000u, 0x00007051u, 0x00007051u, 0x00000000u, 0x00000000u, 0xa87bce94u, 0x00000e21u, 0x257bce94u, 0x00008009u, 0x31dec4f4u, 0x3d932289u, 0x31dec4f4u, 0x3d932289u, 0x0018ef62u, 0x0018ef62u, 0xbd89e800u, 0x31920080u, 0x3ddfe6fdu, 0x0c4de67du, 0xf3b21982u, 0x6f71e77du, 0x0bb45d95u, 0xf44ba26bu, 0x3d932289u, 0x6f71e77du, 0x00000000u, 0x0bb45d95u, 0x00000000u, 0xf44ba26bu, 0x00000001u}, + {0xa241cfdcu, 0xc40c7dbau, 0x00000001u, 0x00000000u, 0x00000001u, 0x4ebb7c60u, 0x4ebb7c60u, 0xffffffffu, 0x7fffffffu, 0x7fffffffu, 0x7fffffffu, 0x3e400000u, 0x00007204u, 0x00007204u, 0x00000000u, 0x00000000u, 0x18118000u, 0xffffffdfu, 0x18118000u, 0x0000005fu, 0x00008000u, 0x5dbe3023u, 0x00008000u, 0x5dbe3023u, 0x00001000u, 0x00001000u, 0x00040000u, 0x00000000u, 0x5dbeb023u, 0x5dbeb023u, 0xa2414fdcu, 0x5dbeb023u, 0x5dbdb023u, 0xa2424fddu, 0x00008000u, 0x5dbeb024u, 0x00000000u, 0x5dbdb022u, 0x00000000u, 0xa2424fdcu, 0x00000001u}, + {0x0000a77bu, 0x211affffu, 0x00000000u, 0x00000002u, 0x00000010u, 0x4f7fff59u, 0xc7277c00u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x3e800000u, 0x00007588u, 0x00007588u, 0x00000090u, 0x00000090u, 0xc23dd5e0u, 0xffffffddu, 0x3a3dd5e0u, 0x00003434u, 0xffff5884u, 0x12345678u, 0x12345678u, 0xffff5884u, 0x01234567u, 0x01234567u, 0x23456780u, 0x12345000u, 0xffff5efcu, 0xedcb0efcu, 0x1234f103u, 0x1233aefcu, 0xedcb020cu, 0x1234fdf4u, 0x12345678u, 0x1233aefdu, 0x00000001u, 0xedcb020bu, 0x00000000u, 0x1234fdf3u, 0x00000001u}, + {0xccf9e043u, 0x3df860ccu, 0x00000002u, 0x00000002u, 0x00000002u, 0x4e4c187fu, 0x4e4c187fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbe800000u, 0x00006fefu, 0x00006fefu, 0x00000000u, 0x00000000u, 0x546b3e6cu, 0xfffffed3u, 0x106b3e6cu, 0x000004f3u, 0xd1cee715u, 0x33061fbcu, 0x33061fbcu, 0xd1cee715u, 0x0000000du, 0xfffffffdu, 0x50000000u, 0x11060714u, 0xf3ceffbdu, 0xe2c8f8a9u, 0x1d370756u, 0x04d506d1u, 0x613738a7u, 0x9ec8c759u, 0xd1cee715u, 0x04d506d2u, 0x00000001u, 0x613738a6u, 0x00000001u, 0x9ec8c758u, 0x00000000u}, + {0x8a4ff4eau, 0xa8d00daeu, 0x00000001u, 0x00000000u, 0x00000001u, 0x4eeb6016u, 0x4eeb6016u, 0xffffffffu, 0x7fffffffu, 0x7fffffffu, 0x7fffffffu, 0x3ea00000u, 0x0000698au, 0x0000698au, 0x00000000u, 0x00000000u, 0x053a8b15u, 0xffffffd8u, 0x063a8b15u, 0x00000058u, 0x00008001u, 0x75b00b15u, 0x00008001u, 0x75b00b15u, 0x00000000u, 0x00000000u, 0x00200000u, 0x00000001u, 0x75b08b15u, 0x75b08b14u, 0x8a4f74ebu, 0x75b08b16u, 0x75af8b14u, 0x8a5074ecu, 0x75b00b15u, 0x75b08b16u, 0x00000000u, 0x75af8b14u, 0x00000000u, 0x8a5074ecu, 0x00000001u}, + {0x00000000u, 0xffffffffu, 0x00000000u, 0x00000000u, 0xffffffffu, 0x4f800000u, 0xbf800000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0xbd800000u, 0x0000bc00u, 0x00007c00u, 0x00000000u, 0x00000000u, 0xffffffe1u, 0xffffffffu, 0x1effffe1u, 0x00000000u, 0xffffffffu, 0x0000001fu, 0x0000001fu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x80000000u, 0x0000001fu, 0xffffffffu, 0xffffffe0u, 0x0000001fu, 0x0000001eu, 0xffffffe0u, 0x00000020u, 0x0000001fu, 0x0000001fu, 0x00000001u, 0xffffffdfu, 0x00000000u, 0x0000001fu, 0x00000001u}, + {0x6ac0b70eu, 0x8f12fca9u, 0x00000000u, 0x00000000u, 0x00000001u, 0x4f153f49u, 0xced5816eu, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x3d800000u, 0x0000748fu, 0x0000748fu, 0x00000009u, 0x00000009u, 0x193cbedfu, 0xffffef71u, 0x0a3cbedfu, 0x00002ebau, 0x93bd04cfu, 0x953f48f1u, 0x93bd04cfu, 0x953f48f1u, 0x000049deu, 0xffffc9deu, 0x099e0000u, 0x913d00c1u, 0x97bf4cffu, 0x06824c3eu, 0xf97db3c1u, 0x28fc4dc0u, 0x01824422u, 0xfe7dbbdeu, 0x93bd04cfu, 0x28fc4dc1u, 0x00000001u, 0x01824421u, 0x00000000u, 0xfe7dbbddu, 0x00000001u} +}; +constexpr const char* Names[41] = {"not_b32", "bfrev_b32", "ffbh_u32", "ffbl_b32", "ffbh_i32", "cvt_f32_u32", "cvt_f32_i32", "cvt_u32_f32", "cvt_i32_f32", "cvt_rpi_i32_f32", "cvt_flr_i32_f32", "cvt_off_f32_i4", "cvt_f16_i16", "cvt_f16_u16", "cvt_u16_f16", "cvt_i16_f16", "mul_i32_i24", "mul_hi_i32_i24", "mul_u32_u24", "mul_hi_u32_u24", "min_i32", "max_i32", "min_u32", "max_u32", "lshrrev_b32", "ashrrev_i32", "lshlrev_b32", "and_b32", "or_b32", "xor_b32", "xnor_b32", "add_nc_u32", "sub_nc_u32", "subrev_nc_u32", "cndmask_b32", "add_co_ci_u32", "add_co_ci_u32 carry", "sub_co_ci_u32", "sub_co_ci_u32 carry", "subrev_co_ci_u32", "subrev_co_ci_u32 carry"}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop1 vop2 integer: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 41; ++i) Expect(tid, out[i], Expected[tid][i], Names[i]); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop1 vop2 integer tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index 4be3e2626..3187613c7 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -116,6 +116,7 @@ private: IrF32 selectF32(IrU1 condition, IrF32 trueValue, IrF32 falseValue); IrU32 convertF32ToU32Saturated(IrF32 value, float upperBound, float safeUpper, std::uint32_t highResult); IrU32 convertF32ToI32Saturated(IrF32 value, float lowerBound, float upperBound, float safeUpper, std::uint32_t lowerResult, std::uint32_t upperResult); + IrU32 convertFlooredF32ToI32(IrF32 source, IrF32 floored); IrU32 packU16Lanes(IrU32 low, IrU32 high); void emitCompareResult(const RdnaInstruction& inst, IrU1 value, bool scalar, bool cmpx); void emitCompareConstant(const RdnaInstruction& inst, bool value, bool scalar, bool cmpx); diff --git a/core/shader/recompiler/Translation/src/ConvertInstructions.cpp b/core/shader/recompiler/Translation/src/ConvertInstructions.cpp index 0a03104de..f560ca699 100644 --- a/core/shader/recompiler/Translation/src/ConvertInstructions.cpp +++ b/core/shader/recompiler/Translation/src/ConvertInstructions.cpp @@ -104,18 +104,29 @@ void TranslationContext::vCvt16F16(const RdnaInstruction& inst, bool signedValue write16Bits(inst.destination, convertF32ToU32Saturated(value, 65536.0f, 65535.0f, 0xffffu)); } +IrU32 TranslationContext::convertFlooredF32ToI32(IrF32 source, IrF32 floored) { + const IrU32 converted = convertF32ToI32Saturated(floored, -2147483648.0f, 2147483648.0f, 2147483520.0f, 0x80000000u, 0x7fffffffu); + const IrU1 nan(ir.Emit(IrOpcode::FPIsNan32, IrType::U1, {&source.Value()})); + const IrU32 sign(ir.ShiftRightLogical(ir.BitCastU32(source.Value()), ir.Constant(31u))); + const IrU32 nanResult(ir.IAdd(ir.Constant(0x7fffffffu), sign.Value())); + return IrU32(ir.Select(nan.Value(), nanResult.Value(), converted.Value())); +} + void TranslationContext::vCvtRpiI32F32(const RdnaInstruction& inst) { const IrF32 source(*readOperand(sourceAt(inst, 0u), IrType::F32)); - const IrF32 biased(ir.Emit(IrOpcode::FPAdd32, IrType::F32, {&source.Value(), &ir.ConstantF32(0.5f)})); - const IrF32 rounded(ir.Emit(IrOpcode::FPFloor32, IrType::F32, {&biased.Value()})); - const IrU32 result = convertF32ToI32Saturated(rounded, -2147483648.0f, 2147483648.0f, 2147483520.0f, 0x80000000u, 0x7fffffffu); + const IrF32 floored(ir.Emit(IrOpcode::FPFloor32, IrType::F32, {&source.Value()})); + const IrF32 fraction(ir.Emit(IrOpcode::FPSub32, IrType::F32, {&source.Value(), &floored.Value()})); + const IrU1 roundUp(ir.Emit(IrOpcode::FPOrdGreaterThanEqual32, IrType::U1, {&fraction.Value(), &ir.ConstantF32(0.5f)})); + const IrF32 increment = selectF32(roundUp, IrF32(ir.ConstantF32(1.0f)), IrF32(ir.ConstantF32(0.0f))); + const IrF32 rounded(ir.Emit(IrOpcode::FPAdd32, IrType::F32, {&floored.Value(), &increment.Value()})); + const IrU32 result = convertFlooredF32ToI32(source, rounded); writeOperand(inst.destination, &result.Value()); } void TranslationContext::vCvtFlrI32F32(const RdnaInstruction& inst) { const IrF32 source(*readOperand(sourceAt(inst, 0u), IrType::F32)); const IrF32 rounded(ir.Emit(IrOpcode::FPFloor32, IrType::F32, {&source.Value()})); - const IrU32 result = convertF32ToI32Saturated(rounded, -2147483648.0f, 2147483648.0f, 2147483520.0f, 0x80000000u, 0x7fffffffu); + const IrU32 result = convertFlooredF32ToI32(source, rounded); writeOperand(inst.destination, &result.Value()); } From 1f5d84a6190d21c8fa7fbe1c19ba857b424ecd50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 00:00:40 +0200 Subject: [PATCH 014/164] fix(shader): return src0 when both f32 min/max sources are NaN Co-Authored-By: Claude --- .../tests/execution/F32MinMaxNan.cpp | 372 ++++++++++++++++++ .../SpirvBackend/src/SpirvModuleEmitter.cpp | 4 +- 2 files changed, 374 insertions(+), 2 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/F32MinMaxNan.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F32MinMaxNan.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F32MinMaxNan.cpp new file mode 100644 index 000000000..b9e7244d8 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F32MinMaxNan.cpp @@ -0,0 +1,372 @@ +#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 +#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, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x1e140b04, 0x20160b04, 0xd551000c, + 0x041a0b04, 0xd554000d, 0x041a0b04, 0xd557000e, 0x041a0b04, 0xd50f000f, 0x02020b04, 0xd5100010, + 0x02020b04, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xbf810000, +}; + +constexpr std::uint32_t Rows[128][4] = { + {0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x7f800001u, 0x00000000u}, + {0x00000000u, 0x3f800000u, 0xff800000u, 0x00000000u}, + {0x00000000u, 0xc0400000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x7f800000u, 0xff812345u, 0x00000000u}, + {0x00000000u, 0xff800000u, 0x7fc12345u, 0x00000000u}, + {0x00000000u, 0x7fc12345u, 0xc0400000u, 0x00000000u}, + {0x00000000u, 0xffd54321u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x7f800001u, 0xffd54321u, 0x00000000u}, + {0x00000000u, 0xff812345u, 0x7f800000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x7f800000u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x3f800000u, 0x7f800001u, 0x00000000u}, + {0x80000000u, 0xc0400000u, 0xff800000u, 0x00000000u}, + {0x80000000u, 0x7f800000u, 0x3f800000u, 0x00000000u}, + {0x80000000u, 0xff800000u, 0xff812345u, 0x00000000u}, + {0x80000000u, 0x7fc12345u, 0x7fc12345u, 0x00000000u}, + {0x80000000u, 0xffd54321u, 0xc0400000u, 0x00000000u}, + {0x80000000u, 0x7f800001u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0xff812345u, 0xffd54321u, 0x00000000u}, + {0x3f800000u, 0x00000000u, 0xffd54321u, 0x00000000u}, + {0x3f800000u, 0x80000000u, 0x7f800000u, 0x00000000u}, + {0x3f800000u, 0x3f800000u, 0x80000000u, 0x00000000u}, + {0x3f800000u, 0xc0400000u, 0x7f800001u, 0x00000000u}, + {0x3f800000u, 0x7f800000u, 0xff800000u, 0x00000000u}, + {0x3f800000u, 0xff800000u, 0x3f800000u, 0x00000000u}, + {0x3f800000u, 0x7fc12345u, 0xff812345u, 0x00000000u}, + {0x3f800000u, 0xffd54321u, 0x7fc12345u, 0x00000000u}, + {0x3f800000u, 0x7f800001u, 0xc0400000u, 0x00000000u}, + {0x3f800000u, 0xff812345u, 0x00000000u, 0x00000000u}, + {0xc0400000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc0400000u, 0x80000000u, 0xffd54321u, 0x00000000u}, + {0xc0400000u, 0x3f800000u, 0x7f800000u, 0x00000000u}, + {0xc0400000u, 0xc0400000u, 0x80000000u, 0x00000000u}, + {0xc0400000u, 0x7f800000u, 0x7f800001u, 0x00000000u}, + {0xc0400000u, 0xff800000u, 0xff800000u, 0x00000000u}, + {0xc0400000u, 0x7fc12345u, 0x3f800000u, 0x00000000u}, + {0xc0400000u, 0xffd54321u, 0xff812345u, 0x00000000u}, + {0xc0400000u, 0x7f800001u, 0x7fc12345u, 0x00000000u}, + {0xc0400000u, 0xff812345u, 0xc0400000u, 0x00000000u}, + {0x7f800000u, 0x00000000u, 0xc0400000u, 0x00000000u}, + {0x7f800000u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x7f800000u, 0x3f800000u, 0xffd54321u, 0x00000000u}, + {0x7f800000u, 0xc0400000u, 0x7f800000u, 0x00000000u}, + {0x7f800000u, 0x7f800000u, 0x80000000u, 0x00000000u}, + {0x7f800000u, 0xff800000u, 0x7f800001u, 0x00000000u}, + {0x7f800000u, 0x7fc12345u, 0xff800000u, 0x00000000u}, + {0x7f800000u, 0xffd54321u, 0x3f800000u, 0x00000000u}, + {0x7f800000u, 0x7f800001u, 0xff812345u, 0x00000000u}, + {0x7f800000u, 0xff812345u, 0x7fc12345u, 0x00000000u}, + {0xff800000u, 0x00000000u, 0x7fc12345u, 0x00000000u}, + {0xff800000u, 0x80000000u, 0xc0400000u, 0x00000000u}, + {0xff800000u, 0x3f800000u, 0x00000000u, 0x00000000u}, + {0xff800000u, 0xc0400000u, 0xffd54321u, 0x00000000u}, + {0xff800000u, 0x7f800000u, 0x7f800000u, 0x00000000u}, + {0xff800000u, 0xff800000u, 0x80000000u, 0x00000000u}, + {0xff800000u, 0x7fc12345u, 0x7f800001u, 0x00000000u}, + {0xff800000u, 0xffd54321u, 0xff800000u, 0x00000000u}, + {0xff800000u, 0x7f800001u, 0x3f800000u, 0x00000000u}, + {0xff800000u, 0xff812345u, 0xff812345u, 0x00000000u}, + {0x7fc12345u, 0x00000000u, 0xff812345u, 0x00000000u}, + {0x7fc12345u, 0x80000000u, 0x7fc12345u, 0x00000000u}, + {0x7fc12345u, 0x3f800000u, 0xc0400000u, 0x00000000u}, + {0x7fc12345u, 0xc0400000u, 0x00000000u, 0x00000000u}, + {0x7fc12345u, 0x7f800000u, 0xffd54321u, 0x00000000u}, + {0x7fc12345u, 0xff800000u, 0x7f800000u, 0x00000000u}, + {0x7fc12345u, 0x7fc12345u, 0x80000000u, 0x00000000u}, + {0x7fc12345u, 0xffd54321u, 0x7f800001u, 0x00000000u}, + {0x7fc12345u, 0x7f800001u, 0xff800000u, 0x00000000u}, + {0x7fc12345u, 0xff812345u, 0x3f800000u, 0x00000000u}, + {0xffd54321u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0xffd54321u, 0x80000000u, 0xff812345u, 0x00000000u}, + {0xffd54321u, 0x3f800000u, 0x7fc12345u, 0x00000000u}, + {0xffd54321u, 0xc0400000u, 0xc0400000u, 0x00000000u}, + {0xffd54321u, 0x7f800000u, 0x00000000u, 0x00000000u}, + {0xffd54321u, 0xff800000u, 0xffd54321u, 0x00000000u}, + {0xffd54321u, 0x7fc12345u, 0x7f800000u, 0x00000000u}, + {0xffd54321u, 0xffd54321u, 0x80000000u, 0x00000000u}, + {0xffd54321u, 0x7f800001u, 0x7f800001u, 0x00000000u}, + {0xffd54321u, 0xff812345u, 0xff800000u, 0x00000000u}, + {0x7f800001u, 0x00000000u, 0xff800000u, 0x00000000u}, + {0x7f800001u, 0x80000000u, 0x3f800000u, 0x00000000u}, + {0x7f800001u, 0x3f800000u, 0xff812345u, 0x00000000u}, + {0x7f800001u, 0xc0400000u, 0x7fc12345u, 0x00000000u}, + {0x7f800001u, 0x7f800000u, 0xc0400000u, 0x00000000u}, + {0x7f800001u, 0xff800000u, 0x00000000u, 0x00000000u}, + {0x7f800001u, 0x7fc12345u, 0xffd54321u, 0x00000000u}, + {0x7f800001u, 0xffd54321u, 0x7f800000u, 0x00000000u}, + {0x7f800001u, 0x7f800001u, 0x80000000u, 0x00000000u}, + {0x7f800001u, 0xff812345u, 0x7f800001u, 0x00000000u}, + {0xff812345u, 0x00000000u, 0x7f800001u, 0x00000000u}, + {0xff812345u, 0x80000000u, 0xff800000u, 0x00000000u}, + {0xff812345u, 0x3f800000u, 0x3f800000u, 0x00000000u}, + {0xff812345u, 0xc0400000u, 0xff812345u, 0x00000000u}, + {0xff812345u, 0x7f800000u, 0x7fc12345u, 0x00000000u}, + {0xff812345u, 0xff800000u, 0xc0400000u, 0x00000000u}, + {0xff812345u, 0x7fc12345u, 0x00000000u, 0x00000000u}, + {0xff812345u, 0xffd54321u, 0xffd54321u, 0x00000000u}, + {0xff812345u, 0x7f800001u, 0x7f800000u, 0x00000000u}, + {0xff812345u, 0xff812345u, 0x80000000u, 0x00000000u}, + {0x3f800000u, 0x40200000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x00800000u, 0x3f800000u, 0x00000000u}, + {0xbf800000u, 0xc0400000u, 0x00000000u, 0x00000000u}, + {0xc0400000u, 0xbf800000u, 0x3f800000u, 0x00000000u}, + {0x7fc00000u, 0x7fc12345u, 0x00000000u, 0x00000000u}, + {0xffc00000u, 0xffd54321u, 0x00000000u, 0x00000000u}, + {0x7f800001u, 0x7fa00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xbf800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xff800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7fc00000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xffc00000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7f800001u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xff812345u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0xbf800000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x7f800000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0xff800000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x7fc00000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0xffc00000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0xff812345u, 0x00000000u}, + {0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x3f800000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u} +}; +constexpr std::uint32_t Expected[128][7] = { + {0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x3f800000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0xc0400000u, 0x00000000u, 0xc0400000u, 0x3f800000u, 0x00000000u, 0xc0400000u, 0x00000000u}, + {0x00000000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0x00000000u, 0x7f800000u}, + {0xff800000u, 0x00000000u, 0xff800000u, 0x00000000u, 0xff800000u, 0xff800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xc0400000u, 0x00000000u, 0xc0400000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x7f800000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x80000000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x80000000u, 0x80000000u, 0x3f800000u}, + {0xc0400000u, 0x80000000u, 0xff800000u, 0x80000000u, 0xc0400000u, 0xc0400000u, 0x80000000u}, + {0x80000000u, 0x7f800000u, 0x80000000u, 0x7f800000u, 0x3f800000u, 0x80000000u, 0x7f800000u}, + {0xff800000u, 0x80000000u, 0xff800000u, 0x80000000u, 0xff800000u, 0xff800000u, 0x80000000u}, + {0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x80000000u, 0x80000000u, 0xc0400000u, 0x80000000u, 0xc0400000u, 0x80000000u, 0x80000000u}, + {0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x80000000u, 0x3f800000u, 0x80000000u, 0x7f800000u, 0x3f800000u, 0x80000000u, 0x3f800000u}, + {0x3f800000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u}, + {0xc0400000u, 0x3f800000u, 0xc0400000u, 0x3f800000u, 0xc0400000u, 0xc0400000u, 0x3f800000u}, + {0x3f800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x3f800000u, 0x3f800000u, 0x7f800000u}, + {0xff800000u, 0x3f800000u, 0xff800000u, 0x3f800000u, 0x3f800000u, 0xff800000u, 0x3f800000u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u}, + {0x3f800000u, 0x3f800000u, 0xc0400000u, 0x3f800000u, 0xc0400000u, 0x3f800000u, 0x3f800000u}, + {0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u}, + {0xc0400000u, 0x00000000u, 0xc0400000u, 0x00000000u, 0x00000000u, 0xc0400000u, 0x00000000u}, + {0xc0400000u, 0x80000000u, 0xc0400000u, 0x80000000u, 0xc0400000u, 0xc0400000u, 0x80000000u}, + {0xc0400000u, 0x3f800000u, 0xc0400000u, 0x7f800000u, 0x3f800000u, 0xc0400000u, 0x3f800000u}, + {0xc0400000u, 0xc0400000u, 0xc0400000u, 0x80000000u, 0xc0400000u, 0xc0400000u, 0xc0400000u}, + {0xc0400000u, 0x7f800000u, 0xc0400000u, 0x7f800000u, 0xc0400000u, 0xc0400000u, 0x7f800000u}, + {0xff800000u, 0xc0400000u, 0xff800000u, 0xc0400000u, 0xff800000u, 0xff800000u, 0xc0400000u}, + {0xc0400000u, 0xc0400000u, 0xc0400000u, 0x3f800000u, 0xc0400000u, 0xc0400000u, 0xc0400000u}, + {0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u}, + {0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u}, + {0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u}, + {0x00000000u, 0x7f800000u, 0xc0400000u, 0x7f800000u, 0x00000000u, 0x00000000u, 0x7f800000u}, + {0x80000000u, 0x7f800000u, 0x80000000u, 0x7f800000u, 0x00000000u, 0x80000000u, 0x7f800000u}, + {0x3f800000u, 0x7f800000u, 0x3f800000u, 0x7f800000u, 0x3f800000u, 0x3f800000u, 0x7f800000u}, + {0xc0400000u, 0x7f800000u, 0xc0400000u, 0x7f800000u, 0x7f800000u, 0xc0400000u, 0x7f800000u}, + {0x7f800000u, 0x7f800000u, 0x80000000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u}, + {0xff800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0xff800000u, 0xff800000u, 0x7f800000u}, + {0x7f800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x7f800000u}, + {0x7f800000u, 0x7f800000u, 0x3f800000u, 0x7f800000u, 0x3f800000u, 0x7f800000u, 0x7f800000u}, + {0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u}, + {0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u}, + {0xff800000u, 0x00000000u, 0xff800000u, 0x00000000u, 0xff800000u, 0xff800000u, 0x00000000u}, + {0xff800000u, 0x80000000u, 0xff800000u, 0x80000000u, 0xc0400000u, 0xff800000u, 0x80000000u}, + {0xff800000u, 0x3f800000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0xff800000u, 0x3f800000u}, + {0xff800000u, 0xc0400000u, 0xff800000u, 0xc0400000u, 0xff800000u, 0xff800000u, 0xc0400000u}, + {0xff800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x7f800000u, 0xff800000u, 0x7f800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0x80000000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0x3f800000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x3f800000u, 0x3f800000u, 0xc0400000u, 0x3f800000u, 0xc0400000u, 0x3f800000u, 0x3f800000u}, + {0xc0400000u, 0xc0400000u, 0xc0400000u, 0x00000000u, 0xc0400000u, 0xc0400000u, 0xc0400000u}, + {0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0x7f800000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0x7fc12345u, 0x7fc12345u, 0x80000000u, 0x80000000u, 0x80000000u, 0x7fc12345u, 0x7fc12345u}, + {0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u}, + {0x7fc12345u, 0x7fc12345u, 0xff800000u, 0xff800000u, 0xff800000u, 0x7fc12345u, 0x7fc12345u}, + {0x7fc12345u, 0x7fc12345u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x7fc12345u, 0x7fc12345u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u}, + {0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u}, + {0x7f800000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x7f800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0xffd54321u, 0xffd54321u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0xffd54321u, 0xffd54321u}, + {0xffd54321u, 0xffd54321u, 0x80000000u, 0x80000000u, 0x80000000u, 0xffd54321u, 0xffd54321u}, + {0xffd54321u, 0xffd54321u, 0xffd54321u, 0xffd54321u, 0xffd54321u, 0xffd54321u, 0xffd54321u}, + {0xffd54321u, 0xffd54321u, 0xff800000u, 0xff800000u, 0xff800000u, 0xffd54321u, 0xffd54321u}, + {0x00000000u, 0x00000000u, 0xff800000u, 0x00000000u, 0xff800000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0x80000000u, 0x3f800000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u}, + {0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u}, + {0x7f800000u, 0x7f800000u, 0xc0400000u, 0x7f800000u, 0xc0400000u, 0x7f800000u, 0x7f800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0x00000000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u}, + {0x7f800001u, 0x7f800001u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800001u, 0x7f800001u}, + {0x7f800001u, 0x7f800001u, 0x80000000u, 0x80000000u, 0x80000000u, 0x7f800001u, 0x7f800001u}, + {0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0xff800000u, 0x80000000u, 0xff800000u, 0x80000000u, 0x80000000u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u}, + {0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u}, + {0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0xc0400000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0xff812345u, 0xff812345u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff812345u, 0xff812345u}, + {0xff812345u, 0xff812345u, 0xff812345u, 0xff812345u, 0xff812345u, 0xff812345u, 0xff812345u}, + {0xff812345u, 0xff812345u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0xff812345u, 0xff812345u}, + {0xff812345u, 0xff812345u, 0x80000000u, 0x80000000u, 0x80000000u, 0xff812345u, 0xff812345u}, + {0x3f800000u, 0x40200000u, 0x00000000u, 0x40200000u, 0x3f800000u, 0x3f800000u, 0x40200000u}, + {0x00800000u, 0x3f800000u, 0x00800000u, 0x3f800000u, 0x3f800000u, 0x00800000u, 0x3f800000u}, + {0xc0400000u, 0xbf800000u, 0xc0400000u, 0x00000000u, 0xbf800000u, 0xc0400000u, 0xbf800000u}, + {0xc0400000u, 0xbf800000u, 0xc0400000u, 0x3f800000u, 0xbf800000u, 0xc0400000u, 0xbf800000u}, + {0x7fc00000u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc00000u, 0x7fc00000u}, + {0xffc00000u, 0xffc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffc00000u, 0xffc00000u}, + {0x7f800001u, 0x7f800001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7f800001u, 0x7f800001u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xff800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x3f800000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x7f800000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0xff800000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x00000000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u} +}; +constexpr std::uint32_t Chunks = 4; +std::uint32_t Chunk = 0; +constexpr const char* Names[7] = { + "v_min_f32 v10, v4, v5", + "v_max_f32 v11, v4, v5", + "v_min3_f32 v12, v4, v5, v6", + "v_max3_f32 v13, v4, v5, v6", + "v_med3_f32 v14, v4, v5, v6", + "v_min_f32_e64 v15, v4, v5", + "v_max_f32_e64 v16, v4, v5", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[Chunk * Threads + tid]), std::end(Rows[Chunk * Threads + tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("f32 min max nan: row ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 7; ++i) { + Expect(Chunk * Threads + tid, out[i], Expected[Chunk * Threads + tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + for (Chunk = 0; Chunk < Chunks; ++Chunk) { + Run(*device); + Check(); + } + std::puts("f32 min max nan tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp index b85d7ee98..5b3c83ce3 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp @@ -809,8 +809,8 @@ std::uint32_t EmitMinMaxF32Value(SpirvEmitterState& state, std::uint32_t lhs, st const auto zeroBits = Binary(state, maxValue ? spv::OpBitwiseAnd : spv::OpBitwiseOr, TypeU32(state), lhsClass.bits, rhsClass.bits); const auto numericBits = Select(state, TypeU32(state), bothZero, zeroBits, orderedBits); - const auto rhsNanBits = Select(state, TypeU32(state), rhsClass.nan, lhsClass.bits, numericBits); - const auto resultBits = Select(state, TypeU32(state), lhsClass.nan, rhsClass.bits, rhsNanBits); + const auto lhsNanBits = Select(state, TypeU32(state), lhsClass.nan, rhsClass.bits, numericBits); + const auto resultBits = Select(state, TypeU32(state), rhsClass.nan, lhsClass.bits, lhsNanBits); return Unary(state, spv::OpBitcast, TypeF32(state), resultBits); } From c227c6264958f3e1209b734f610ab54a3f4782ff Mon Sep 17 00:00:00 2001 From: Chad Date: Sun, 4 Oct 2026 18:05:13 -0500 Subject: [PATCH 015/164] fix(shader): splat the bindless table hit into a bool vector for SPIR-V 1.3 TableResult selected a uvec4 with a scalar bool condition, which needs SPIR-V 1.4. Targets at 1.3 failed validation (agc_shader_memory with ANYPS5_ENABLE_SPIRV_TOOLS). Co-Authored-By: Claude Opus 5.5 --- core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp index 251b1a045..7ea6a8d40 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp @@ -245,7 +245,9 @@ std::uint32_t SampledComponentZero(SpirvEmitterState& state, IrTextureNumericCla std::uint32_t TableResult(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, std::uint32_t result) { if (access.table.mapped == 0) return result; auto& state = ctx.state; - return Select(state, TypeU32Vector(state, 4), access.table.mapped, result, ConstantU32CompositeZero(state, 4)); + const auto mapped = state.module.AllocateId(); + state.module.AddFunction(spv::OpCompositeConstruct, TypeBoolVector(state, 4), mapped, access.table.mapped, access.table.mapped, access.table.mapped, access.table.mapped); + return Select(state, TypeU32Vector(state, 4), mapped, result, ConstantU32CompositeZero(state, 4)); } std::uint32_t ResultVector(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, std::uint32_t value, IrTextureNumericClass numericClass, bool dref, bool gather) { From dd9a62f2a3c6c56510c0cd8f7df8f48925f4ce22 Mon Sep 17 00:00:00 2001 From: DotDebian Date: Mon, 5 Oct 2026 01:07:40 +0200 Subject: [PATCH 016/164] feat(agc): implement the Sh and Uc RegistersDirect size queries sceAgcCbSetShRegistersDirectGetSize and sceAgcCbSetUcRegistersDirectGetSize return the bytes their writers need when no two registers are adjacent: one three-word packet per register. Adjacent registers are merged into ranges and take less. A count whose size does not fit 32 bits throws. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgc/CMakeLists.txt | 1 + core/libs/prx/libSceAgc/Cb/src/Registers.cpp | 18 ++++--- .../prx/libSceAgc/tests/RegisterCommands.cpp | 52 +++++++++++++++++++ 3 files changed, 65 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libSceAgc/CMakeLists.txt b/core/libs/prx/libSceAgc/CMakeLists.txt index 1ac5e1b6b..e3cf070b9 100644 --- a/core/libs/prx/libSceAgc/CMakeLists.txt +++ b/core/libs/prx/libSceAgc/CMakeLists.txt @@ -72,6 +72,7 @@ configure_windows_unwind(libSceAgc) add_test_executable(agc_register_command_tests EXCLUDE_FROM_ALL tests/RegisterCommands.cpp Command/src/Packet.cpp + Cb/src/Registers.cpp DcbState/src/Registers.cpp Patch/src/IndirectRegisters.cpp ) diff --git a/core/libs/prx/libSceAgc/Cb/src/Registers.cpp b/core/libs/prx/libSceAgc/Cb/src/Registers.cpp index 29b68fe01..8e4d72acd 100644 --- a/core/libs/prx/libSceAgc/Cb/src/Registers.cpp +++ b/core/libs/prx/libSceAgc/Cb/src/Registers.cpp @@ -6,6 +6,16 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +std::uint32_t RegisterListSize(std::uint32_t numRegs, const char* function) { + constexpr std::uint32_t bytesPerRegister = 3u * sizeof(std::uint32_t); + Agc::Command::Require(numRegs <= 0xffffffffu / bytesPerRegister, function, "register list size overflow"); + return numRegs * bytesPerRegister; +} + +} + extern "C" { std::uint32_t* APS5_VABI sceAgcCbSetUcRegisterRangeDirect(CommandBuffer* buf, std::uint32_t offset, const std::uint32_t* values, std::uint32_t numValues) { @@ -29,9 +39,7 @@ std::uint32_t* APS5_VABI sceAgcCbSetShRegistersDirect(CommandBuffer* buf, const } std::uint32_t APS5_VABI sceAgcCbSetShRegistersDirectGetSize(std::uint32_t numRegs) { - (void)numRegs; - NotImplemented_nid_no_patch(__func__); - return 0; + return RegisterListSize(numRegs, __func__); } std::uint32_t* APS5_VABI sceAgcCbSetUcRegistersDirect(CommandBuffer* buf, const volatile ShaderRegister* regs, std::uint32_t numRegs) { @@ -39,9 +47,7 @@ std::uint32_t* APS5_VABI sceAgcCbSetUcRegistersDirect(CommandBuffer* buf, const } std::uint32_t APS5_VABI sceAgcCbSetUcRegistersDirectGetSize(std::uint32_t numRegs) { - (void)numRegs; - NotImplemented_nid_no_patch(__func__); - return 0; + return RegisterListSize(numRegs, __func__); } } diff --git a/core/libs/prx/libSceAgc/tests/RegisterCommands.cpp b/core/libs/prx/libSceAgc/tests/RegisterCommands.cpp index e40249ef6..ffe1e8690 100644 --- a/core/libs/prx/libSceAgc/tests/RegisterCommands.cpp +++ b/core/libs/prx/libSceAgc/tests/RegisterCommands.cpp @@ -21,6 +21,10 @@ std::uint32_t APS5_VABI sceAgcDcbSetUcRegistersIndirectGetSize(std::uint32_t); int APS5_VABI sceAgcSetCxRegIndirectPatchSetNumRegisters(std::uint32_t*, std::uint32_t); int APS5_VABI sceAgcSetShRegIndirectPatchSetNumRegisters(std::uint32_t*, std::uint32_t); int APS5_VABI sceAgcSetUcRegIndirectPatchSetNumRegisters(std::uint32_t*, std::uint32_t); +std::uint32_t* APS5_VABI sceAgcCbSetShRegistersDirect(CommandBuffer*, const volatile ShaderRegister*, std::uint32_t); +std::uint32_t* APS5_VABI sceAgcCbSetUcRegistersDirect(CommandBuffer*, const volatile ShaderRegister*, std::uint32_t); +std::uint32_t APS5_VABI sceAgcCbSetShRegistersDirectGetSize(std::uint32_t); +std::uint32_t APS5_VABI sceAgcCbSetUcRegistersDirectGetSize(std::uint32_t); } namespace { @@ -101,12 +105,60 @@ void testIndirect() { } } +void testDirectList() { + constexpr std::uint32_t sentinel = 0xabcdef01u; + constexpr std::uint32_t wordSize = sizeof(std::uint32_t); + const std::array writers{sceAgcCbSetShRegistersDirect, sceAgcCbSetUcRegistersDirect}; + const std::array sizes{sceAgcCbSetShRegistersDirectGetSize, sceAgcCbSetUcRegistersDirectGetSize}; + const std::array opcodes{0x7600u, 0x7900u}; + const std::array scattered{{{0x12u, 1}, {0x11u, 2}, {0x20u, 3}, {0xffffu, 4}}}; + const std::array adjacent{{{0x10u, 5}, {0x11u, 6}, {0x12u, 7}, {0x13u, 8}}}; + const std::array paired{{{0x10u, 5}, {0x11u, 6}, {0x20u, 7}, {0x21u, 8}}}; + for (std::size_t i = 0; i < writers.size(); ++i) { + const auto single = 0xc0010000u | opcodes[i]; + for (std::uint32_t count = 1; count <= scattered.size(); ++count) { + const auto words = sizes[i](count) / wordSize; + Storage storage; + check(sizes[i](count) % wordSize == 0 && words != 0 && words <= storage.words.size(), "register list size is not a usable word count"); + storage.words.fill(sentinel); + storage.buffer.cursor_down = storage.buffer.cursor_up + words; + auto* packet = writers[i](&storage.buffer, scattered.data(), count); + check(packet == storage.words.data() && storage.buffer.cursor_up == storage.buffer.cursor_down, "separate registers do not fill the queried size"); + for (std::uint32_t reg = 0; reg < count; ++reg) { + const std::array expected{single, scattered[reg].offset, scattered[reg].value}; + check(std::equal(expected.begin(), expected.end(), packet + reg * expected.size()), "incorrect register list packet"); + } + check(std::all_of(storage.words.begin() + words, storage.words.end(), [](std::uint32_t word) { return word == sentinel; }), "register list overwrote following words"); + Storage shortStorage; + shortStorage.buffer.cursor_down = shortStorage.buffer.cursor_up + words - 1u; + expectFailure([&] { writers[i](&shortStorage.buffer, scattered.data(), count); }); + } + const auto limit = sizes[i](4) / wordSize; + Storage merged; + merged.buffer.cursor_down = merged.buffer.cursor_up + limit; + const std::array expectedMerged{0xc0040000u | opcodes[i], 0x10u, 5, 6, 7, 8}; + auto* packet = writers[i](&merged.buffer, adjacent.data(), 4); + check(std::equal(expectedMerged.begin(), expectedMerged.end(), packet) && merged.buffer.cursor_up == packet + expectedMerged.size(), "adjacent registers were not merged into one range"); + Storage split; + split.buffer.cursor_down = split.buffer.cursor_up + limit; + const std::array expectedSplit{0xc0020000u | opcodes[i], 0x10u, 5, 6, 0xc0020000u | opcodes[i], 0x20u, 7, 8}; + packet = writers[i](&split.buffer, paired.data(), 4); + check(std::equal(expectedSplit.begin(), expectedSplit.end(), packet) && split.buffer.cursor_up == packet + expectedSplit.size(), "register pairs were not written as two ranges"); + check(sizes[i](0) == 0, "an empty register list has a size"); + check(sizes[i](0x15555555u) == 0xfffffffcu, "largest register list size mismatch"); + for (const auto count : {0x15555556u, 0x40000000u, 0xffffffffu}) { + expectFailure([&] { sizes[i](count); }); + } + } +} + } int main() { try { testDirect(); testIndirect(); + testDirectList(); std::puts("AGC register command tests passed"); return 0; } catch (const std::exception& error) { From abbcb31e54fb64b9397f9afd32d8d90789e547b3 Mon Sep 17 00:00:00 2001 From: Chad Date: Sun, 4 Oct 2026 18:09:01 -0500 Subject: [PATCH 017/164] feat(shader): implement v_dot2_f32_f16 VOP3P 0x13 threw "VOP3P opcode is not implemented". It computes src0.lo * src1.lo + src0.hi * src1.hi + src2 like v_dot2c_f32_f16, with the VOP3P op_sel/op_sel_hi and neg_lo/neg_hi modifiers on the packed f16 sources, neg on the f32 accumulator and clamp. op_sel and neg_hi on the accumulator throw. Co-Authored-By: Claude Opus 5.5 --- .../tests/execution/Float16Dot.cpp | 161 ++++++++++++++++++ .../include/RdnaDecoder/RdnaOpcode.hpp | 1 + .../recompiler/RdnaDecoder/src/RdnaOpcode.cpp | 1 + .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 6 +- .../Translation/TranslationContext.hpp | 1 + .../Translation/src/FloatInstructions.cpp | 12 ++ .../Translation/src/VectorInstructions.cpp | 2 + 7 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Float16Dot.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Float16Dot.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Float16Dot.cpp new file mode 100644 index 000000000..56d54ed41 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Float16Dot.cpp @@ -0,0 +1,161 @@ +#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 +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 8; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array DotCode{ + 0x34020084, 0x34060085, 0xe0301000, 0x80000401, 0xe0301004, 0x80000501, 0xe0301008, 0x80000601, + 0xbf8c3f70, 0xcc13400a, 0x1c1a0b04, 0xcc13480b, 0x141a0b04, 0xcc13420c, 0xbc1a0b04, 0xcc13c00d, + 0x1c1a0b04, 0xe0701000, 0x80010a03, 0xe0701004, 0x80010b03, 0xe0701008, 0x80010c03, 0xe070100c, + 0x80010d03, 0xbf810000, +}; + +constexpr std::array, 8> Edges{{ + {0x3c003c00u, 0x3c003c00u, 0x00000000u}, {0x7c000000u, 0x3c000000u, 0x3f800000u}, {0x7c003c00u, 0x00003c00u, 0x3f800000u}, + {0xfc007c00u, 0x3c003c00u, 0x40000000u}, {0x80000000u, 0x00008000u, 0x80000000u}, {0x7bff7bffu, 0x7bff7bffu, 0xcf800000u}, + {0x38003800u, 0xb800b800u, 0x3e800000u}, {0x7e003c00u, 0x3c003c00u, 0x00000000u}, +}}; + +float Half(std::uint32_t bits) { + const std::uint32_t sign = (bits & 0x8000u) << 16u; + const std::uint32_t exponent = (bits >> 10u) & 0x1fu; + const std::uint32_t mantissa = bits & 0x3ffu; + if (exponent == 0x1fu) return std::bit_cast(sign | 0x7f800000u | (mantissa << 13u)); + if (exponent == 0u) return std::bit_cast(sign) + (sign != 0u ? -1.0f : 1.0f) * std::ldexp(static_cast(mantissa), -24); + return std::bit_cast(sign | ((exponent + 112u) << 23u) | (mantissa << 13u)); +} + +std::uint32_t RandomHalf(std::uint64_t& state) { + state = state * 6364136223846793005ull + 1442695040888963407ull; + const auto bits = static_cast(state >> 33u); + return (bits & 0x8000u) | ((8u + (bits >> 16u) % 15u) << 10u) | (bits & 0x3ffu); +} + +void FillInput() { + std::uint64_t state = 0x9e3779b97f4a7c15ull; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + auto* words = &Input[tid * Inputs]; + if (tid < Edges.size()) { + std::copy(Edges[tid].begin(), Edges[tid].end(), words); + continue; + } + words[0] = RandomHalf(state) | (RandomHalf(state) << 16u); + words[1] = RandomHalf(state) | (RandomHalf(state) << 16u); + words[2] = std::bit_cast(Half(RandomHalf(state)) * Half(RandomHalf(state))); + } +} + +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; +} + +auto 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}, 0, {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) { + Output.fill(0xdeadbeefu); + const std::span code(DotCode); + const auto result = Compile(device, code); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void CheckRefused(AgcDriver::VulkanDevice& device, std::uint32_t word0, const std::string& what) { + alignas(256) const std::array code{word0, 0x1c1a0b04u, 0xbf810000u}; + std::string refusal; + try { + static_cast(Compile(device, code)); + } catch (const std::exception& error) { + refusal = error.what(); + } + Require(refusal.find("v_dot2_f32_f16 accumulator op_sel and neg_hi are not implemented") != std::string::npos, what + " was not refused"); +} + +float Dot(float aLow, float aHigh, float bLow, float bHigh, float c) { + return std::fma(aHigh, bHigh, std::fma(aLow, bLow, c)); +} + +void Expect(std::uint32_t tid, std::uint32_t actual, float expected, const char* name) { + const std::uint32_t bits = std::bit_cast(expected); + const bool nan = std::isnan(expected) && std::isnan(std::bit_cast(actual)); + Require(nan || actual == bits, std::string("v_dot2_f32_f16: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(bits)); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto* in = &Input[tid * Inputs]; + const auto* out = &Output[tid * Results]; + const float aLow = Half(in[0] & 0xffffu); + const float aHigh = Half(in[0] >> 16u); + const float bLow = Half(in[1] & 0xffffu); + const float bHigh = Half(in[1] >> 16u); + const float c = std::bit_cast(in[2]); + const float plain = Dot(aLow, aHigh, bLow, bHigh, c); + Expect(tid, out[0], plain, "plain"); + Expect(tid, out[1], Dot(aHigh, aLow, bLow, bHigh, c), "op_sel swapping the halves of src0"); + Expect(tid, out[2], Dot(-aLow, aHigh, bLow, -bHigh, -c), "neg_lo src0, neg_hi src1 and neg src2"); + if (!std::isnan(plain)) Expect(tid, out[3], plain > 0.0f ? std::min(plain, 1.0f) : 0.0f, "clamp"); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + FillInput(); + Run(*device); + Check(); + CheckRefused(*device, 0xcc13600au, "op_sel on the accumulator"); + CheckRefused(*device, 0xcc13440au, "neg_hi on the accumulator"); + std::puts("v_dot2_f32_f16 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 5ed43f202..8870b4e4a 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -279,6 +279,7 @@ enum class RdnaOpcode : std::uint16_t { VCubetcF32, VCubemaF32, VDot2cF32F16, + VDot2F32F16, VDot4cI32I8, VDot2I32I16, VDot2U32U16, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp index 4b0069c92..b490dc22f 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp @@ -228,6 +228,7 @@ bool IsVectorAluOpcode(RdnaOpcode opcode) { case RdnaOpcode::VCubetcF32: case RdnaOpcode::VCubemaF32: case RdnaOpcode::VDot2cF32F16: + case RdnaOpcode::VDot2F32F16: case RdnaOpcode::VDot4cI32I8: case RdnaOpcode::VDot2I32I16: case RdnaOpcode::VDot2U32U16: diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index 7a09fc34e..cb44b5325 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -590,6 +590,7 @@ constexpr VectorOpcodeInfo vop3pOpcodes[] = { {0x10u, RdnaOpcode::VPkMulF16}, {0x11u, RdnaOpcode::VPkMinF16}, {0x12u, RdnaOpcode::VPkMaxF16}, + {0x13u, RdnaOpcode::VDot2F32F16}, {0x14u, RdnaOpcode::VDot2I32I16}, {0x15u, RdnaOpcode::VDot2U32U16}, {0x16u, RdnaOpcode::VDot4I32I8}, @@ -2199,7 +2200,10 @@ RdnaInstruction DecodeRdnaVop3p(std::uint32_t programCounter, std::span 2u) { instruction.source2 = DecodeRdnaScalarSource(src2, programCounter); } - if (instruction.op == RdnaOpcode::VFmaF32 || isMadMixF16(instruction.op) || isPackedVop3p(instruction.op) || isVop3pIntegerSaturate(instruction.op)) { + if (instruction.op == RdnaOpcode::VDot2F32F16 && (((opSel | negHi) >> 2u) & 1u) != 0u) { + throw std::invalid_argument("VOP3P v_dot2_f32_f16 accumulator op_sel and neg_hi are not implemented"); + } + if (instruction.op == RdnaOpcode::VFmaF32 || instruction.op == RdnaOpcode::VDot2F32F16 || isMadMixF16(instruction.op) || isPackedVop3p(instruction.op) || isVop3pIntegerSaturate(instruction.op)) { instruction.destination.clamp = clamp != 0u; } else if (clamp != 0u && instruction.op != RdnaOpcode::VPkMulLoU16) { throw std::invalid_argument("VOP3P integer clamp is not implemented"); diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index 8622e85b1..9fb00946a 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -171,6 +171,7 @@ private: void emitFloat16ClassCompare(const RdnaInstruction& inst, bool cmpx); bool float64Operation(const RdnaInstruction& inst, IrOpcode opcode); bool vDot2cF32F16(const RdnaInstruction& inst); + bool vDot2F32F16(const RdnaInstruction& inst); bool vCubeidF32(const RdnaInstruction& inst); bool vCubescF32(const RdnaInstruction& inst); bool vCubetcF32(const RdnaInstruction& inst); diff --git a/core/shader/recompiler/Translation/src/FloatInstructions.cpp b/core/shader/recompiler/Translation/src/FloatInstructions.cpp index 4307ca9c9..b11dbe80d 100644 --- a/core/shader/recompiler/Translation/src/FloatInstructions.cpp +++ b/core/shader/recompiler/Translation/src/FloatInstructions.cpp @@ -433,6 +433,18 @@ bool TranslationContext::vDot2cF32F16(const RdnaInstruction& inst) { return true; } +bool TranslationContext::vDot2F32F16(const RdnaInstruction& inst) { + const IrF32 aLow = readF16LaneAsF32(sourceAt(inst, 0u), false, true); + const IrF32 aHigh = readF16LaneAsF32(sourceAt(inst, 0u), true, true); + const IrF32 bLow = readF16LaneAsF32(sourceAt(inst, 1u), false, true); + const IrF32 bHigh = readF16LaneAsF32(sourceAt(inst, 1u), true, true); + IrValue* accumulator = readOperand(sourceAt(inst, 2u), IrType::F32); + const IrF32 low(ir.Emit(IrOpcode::FPFma32, IrType::F32, {&aLow.Value(), &bLow.Value(), accumulator})); + const IrF32 result(ir.Emit(IrOpcode::FPFma32, IrType::F32, {&aHigh.Value(), &bHigh.Value(), &low.Value()})); + writeOperand(inst.destination, &result.Value()); + return true; +} + bool TranslationContext::vCubeidF32(const RdnaInstruction& inst) { return floatCube(inst, 0u); } diff --git a/core/shader/recompiler/Translation/src/VectorInstructions.cpp b/core/shader/recompiler/Translation/src/VectorInstructions.cpp index 9e2d42450..75ba73621 100644 --- a/core/shader/recompiler/Translation/src/VectorInstructions.cpp +++ b/core/shader/recompiler/Translation/src/VectorInstructions.cpp @@ -950,6 +950,8 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { return floatTernary(inst, IrOpcode::FPMedTri32, false, false); case RdnaOpcode::VDot2cF32F16: return vDot2cF32F16(inst); + case RdnaOpcode::VDot2F32F16: + return vDot2F32F16(inst); case RdnaOpcode::VDot4cI32I8: return integerDot(inst, 8u, true, true); case RdnaOpcode::VDot2I32I16: From 4e888083f9d35cb3ce79739cdb99a26a51e14438 Mon Sep 17 00:00:00 2001 From: Chad Date: Sun, 4 Oct 2026 18:11:58 -0500 Subject: [PATCH 018/164] feat(shader): implement s_getreg_b32 for the MODE round mode fields SOPK 0x12 threw in the decoder. The recompiler emits round to nearest even, which s_setreg_imm32_b32 already requires, so reads that stay within MODE bits 0..3 return 0. Other hardware registers and fields throw with the register and bits read. Co-Authored-By: Claude Opus 5.5 --- .../tests/execution/ScalarGetreg.cpp | 102 ++++++++++++++++++ .../include/RdnaDecoder/RdnaOpcode.hpp | 1 + .../RdnaDecoder/src/RdnaScalarOpDecoder.cpp | 6 ++ .../Translation/src/EmbeddedVertexFetch.cpp | 1 + .../Translation/src/ScalarInstructions.cpp | 10 ++ 5 files changed, 120 insertions(+) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/ScalarGetreg.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarGetreg.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarGetreg.cpp new file mode 100644 index 000000000..b580923c1 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarGetreg.cpp @@ -0,0 +1,102 @@ +#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 Results = 16; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34060086, 0xbe9403ff, 0x55555555, 0xbe9503ff, 0x55555555, 0xb9141801, 0xb9150881, 0x7e140214, + 0x7e160215, 0xe0701000, 0x80010a03, 0xe0701004, 0x80010b03, 0xbf810000, +}; + +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::RecompileRequest Request(AgcDriver::VulkanDevice& device, std::span code, std::span userData, std::span memory) { + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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 request; +} + +void ExpectRefused(AgcDriver::VulkanDevice& device, std::uint32_t word, const char* what) { + alignas(256) const std::array code{word, 0xbf810000u}; + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(std::span(code))}}}; + const std::vector userData(8, 0u); + std::string refusal; + try { + static_cast(ShaderRecompiler::Recompile(Request(device, code, userData, memory))); + } catch (const std::exception& error) { + refusal = error.what(); + } + Require(refusal.find("only the MODE round mode fields are modeled") != std::string::npos, std::string("s_getreg_b32 reading ") + what + " was not refused"); +} + +void Run(AgcDriver::VulkanDevice& device) { + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const auto result = ShaderRecompiler::Recompile(Request(device, code, userData, memory)); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + constexpr std::array names{"hwreg(MODE, 0, 4)", "hwreg(MODE, 2, 2)"}; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + for (std::uint32_t index = 0; index < names.size(); ++index) { + const std::uint32_t actual = Output[tid * Results + index]; + Require(actual == 0u, std::string("s_getreg_b32: lane ") + std::to_string(tid) + " " + names[index] + " is " + Hex(actual) + ", expected 0x00000000"); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + ExpectRefused(*device, 0xb9141901u, "the MODE denormal fields"); + ExpectRefused(*device, 0xb914f804u, "HW_ID"); + std::puts("s_getreg_b32 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 5ed43f202..28177fce9 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -897,6 +897,7 @@ enum class RdnaOpcode : std::uint16_t { VInterpMovF32, SWaitcntDepctr, SSendmsg, + SGetregB32, SSetregB32, SSetregImm32B32, SCmovkI32, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaScalarOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaScalarOpDecoder.cpp index 15afeecfd..04f82bdcc 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaScalarOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaScalarOpDecoder.cpp @@ -175,6 +175,7 @@ RdnaOpcode decodeSopkOpcode(std::uint32_t opcode) { case 0x0eu: return RdnaOpcode::SCmpLeU32; case 0x0fu: return RdnaOpcode::SAddI32; case 0x10u: return RdnaOpcode::SMulkI32; + case 0x12u: return RdnaOpcode::SGetregB32; case 0x13u: return RdnaOpcode::SSetregB32; case 0x15u: return RdnaOpcode::SSetregImm32B32; case 0x17u: return RdnaOpcode::SWaitcnt; @@ -417,6 +418,11 @@ RdnaInstruction DecodeRdnaSopk(std::uint32_t programCounter, std::span> 6u) & 0x1fu; + const std::uint32_t size = ((field >> 11u) & 0x1fu) + 1u; + if ((field & 0x3fu) != 1u || offset + size > 4u) { + throw std::runtime_error("s_getreg_b32 at pc " + std::to_string(inst.programCounter) + " reads hardware register " + std::to_string(field & 0x3fu) + " bits " + std::to_string(offset) + ".." + std::to_string(offset + size - 1u) + ": only the MODE round mode fields are modeled"); + } + writeRawU32(inst.destination, IrU32(ir.Constant(0u))); + return true; + } case RdnaOpcode::SCmovkI32: { const IrU32 previous = readU32(inst.destination); writeRawU32(inst.destination, IrU32(ir.Select(ir.GetScc(), ir.Constant(inst.source0.value), previous.Value()))); From d78eea1f5f00deb115f3e2d5730b77b7717772a4 Mon Sep 17 00:00:00 2001 From: Chad Date: Sun, 4 Oct 2026 17:58:56 -0500 Subject: [PATCH 019/164] feat(shader): implement the 64-bit LDS atomics ds_add/sub/rsub/inc/dec_u64, ds_min/max_i64/u64, ds_and/or/xor/mskor_b64, ds_cmpst_b64/f64, ds_min/max_f64, their _rtn forms and ds_wrxchg_rtn_b64 threw in the decoder. LDS is a 32-bit workgroup array, so the two dwords are read and written under a lock dword placed after the guest LDS, which is cleared with a workgroup barrier at entry. The lock is acquired and released in the same loop iteration, so lanes of one subgroup cannot deadlock on it. Stages whose LDS is a function variable update it directly. 64-bit GDS atomics throw. Co-Authored-By: Claude Opus 5.5 --- .../tests/execution/LdsAtomics64.cpp | 240 ++++++++++++++++++ .../IntermediateRepresentation/IrOpcode.hpp | 18 ++ .../src/IrOpcode.cpp | 54 ++++ .../include/RdnaDecoder/RdnaOpcode.hpp | 35 +++ .../RdnaDecoder/src/RdnaMemoryOpDecoder.cpp | 72 +++++- .../include/SpirvBackend/SpirvAnalysis.hpp | 2 + .../SpirvBackend/SpirvEmitterHelpers.hpp | 1 + .../SpirvBackend/SpirvEmitterInstructions.hpp | 18 ++ .../SpirvMemory/SpirvBufferAccess.hpp | 1 + .../SpirvBackend/src/SpirvAnalysis.cpp | 5 + .../SpirvBackend/src/SpirvFlowEmitter.cpp | 18 ++ .../src/SpirvMemory/SpirvBufferAccess.cpp | 12 +- .../SpirvMemory/SpirvMemoryInstructions.cpp | 193 ++++++++++++++ .../src/SpirvMemory/SpirvModuleSetup.cpp | 3 + .../SpirvBackend/src/SpirvModuleEmitter.cpp | 4 + .../Translation/TranslationContext.hpp | 1 + .../MemoryInstructionDispatch.cpp | 70 +++++ .../SharedMemoryInstructions.cpp | 19 ++ docs/dev/TechnicalDebt.md | 1 + 19 files changed, 765 insertions(+), 2 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/LdsAtomics64.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/LdsAtomics64.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LdsAtomics64.cpp new file mode 100644 index 000000000..4d05f27af --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LdsAtomics64.cpp @@ -0,0 +1,240 @@ +#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 + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 8; +constexpr std::uint32_t Results = 128; +constexpr std::uint32_t Operations = 35; +constexpr std::uint32_t ReturningOperations = 18; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020085, 0x34060089, 0x34040083, 0x7ef80280, 0x7efa0280, 0x7efc0280, 0xe0301000, 0x80000401, + 0xe0301004, 0x80000501, 0xe0301008, 0x80000601, 0xe030100c, 0x80000701, 0xe0301010, 0x80000801, + 0xe0301014, 0x80000901, 0xbf8c3f70, 0xd9340000, 0x00000402, 0xd9340100, 0x00000402, 0xd9340200, + 0x00000402, 0xd9340300, 0x00000402, 0xd9340400, 0x00000402, 0xd9340500, 0x00000402, 0xd9340600, + 0x00000402, 0xd9340700, 0x00000402, 0xd9340800, 0x00000402, 0xd9340900, 0x00000402, 0xd9340a00, + 0x00000402, 0xd9340b00, 0x00000402, 0xd9340c00, 0x00000402, 0xd9340d00, 0x00000402, 0xd9340e00, + 0x00000402, 0xd9340f00, 0x00000402, 0xd9341000, 0x00000402, 0xd9341100, 0x00000402, 0xd9341200, + 0x00000402, 0xd9341300, 0x00000402, 0xd9341400, 0x00000402, 0xd9341500, 0x00000402, 0xd9341600, + 0x00000402, 0xd9341700, 0x00000402, 0xd9341800, 0x00000402, 0xd9341900, 0x00000402, 0xd9341a00, + 0x00000402, 0xd9341b00, 0x00000402, 0xd9341c00, 0x00000402, 0xd9341d00, 0x00000402, 0xd9341e00, + 0x00000402, 0xd9341f00, 0x00000402, 0xd9342000, 0x00000402, 0xd9342100, 0x00000402, 0xd9342200, + 0x00000402, 0xd9342300, 0x00007c7e, 0xd9342308, 0x00007c7e, 0xbf8cc07f, 0xd9000000, 0x00000602, + 0xd9040100, 0x00000602, 0xd9080200, 0x00000602, 0xd90c0300, 0x00000602, 0xd9100400, 0x00000602, + 0xd9140500, 0x00000602, 0xd9180600, 0x00000602, 0xd91c0700, 0x00000602, 0xd9200800, 0x00000602, + 0xd9240900, 0x00000602, 0xd9280a00, 0x00000602, 0xd92c0b00, 0x00000602, 0xd9300c00, 0x00080602, + 0xd9400d00, 0x00080602, 0xd9440e00, 0x00080602, 0xd9480f00, 0x00000602, 0xd94c1000, 0x00000602, + 0xd9801100, 0x0a000602, 0xd9841200, 0x0c000602, 0xd9881300, 0x0e000602, 0xd98c1400, 0x10000602, + 0xd9901500, 0x12000602, 0xd9941600, 0x14000602, 0xd9981700, 0x16000602, 0xd99c1800, 0x18000602, + 0xd9a01900, 0x1a000602, 0xd9a41a00, 0x1c000602, 0xd9a81b00, 0x1e000602, 0xd9ac1c00, 0x20000602, + 0xd9b01d00, 0x22080602, 0xd9b41e00, 0x24000602, 0xd9c01f00, 0x26080602, 0xd9c42000, 0x28080602, + 0xd9c82100, 0x2a000602, 0xd9cc2200, 0x2c000602, 0xd9802300, 0x2e00067e, 0xd9a02308, 0x3000087e, + 0xbf8cc07f, 0xd9d80000, 0x32000002, 0xd9d80100, 0x34000002, 0xd9d80200, 0x36000002, 0xd9d80300, + 0x38000002, 0xd9d80400, 0x3a000002, 0xd9d80500, 0x3c000002, 0xd9d80600, 0x3e000002, 0xd9d80700, + 0x40000002, 0xd9d80800, 0x42000002, 0xd9d80900, 0x44000002, 0xd9d80a00, 0x46000002, 0xd9d80b00, + 0x48000002, 0xd9d80c00, 0x4a000002, 0xd9d80d00, 0x4c000002, 0xd9d80e00, 0x4e000002, 0xd9d80f00, + 0x50000002, 0xd9d81000, 0x52000002, 0xd9d81100, 0x54000002, 0xd9d81200, 0x56000002, 0xd9d81300, + 0x58000002, 0xd9d81400, 0x5a000002, 0xd9d81500, 0x5c000002, 0xd9d81600, 0x5e000002, 0xd9d81700, + 0x60000002, 0xd9d81800, 0x62000002, 0xd9d81900, 0x64000002, 0xd9d81a00, 0x66000002, 0xd9d81b00, + 0x68000002, 0xd9d81c00, 0x6a000002, 0xd9d81d00, 0x6c000002, 0xd9d81e00, 0x6e000002, 0xd9d81f00, + 0x70000002, 0xd9d82000, 0x72000002, 0xd9d82100, 0x74000002, 0xd9d82200, 0x76000002, 0xd9d82300, + 0x7800007e, 0xd9d82308, 0x7a00007e, 0xbf8cc07f, 0xe0701000, 0x80013203, 0xe0701004, 0x80013303, + 0xe0701008, 0x80013403, 0xe070100c, 0x80013503, 0xe0701010, 0x80013603, 0xe0701014, 0x80013703, + 0xe0701018, 0x80013803, 0xe070101c, 0x80013903, 0xe0701020, 0x80013a03, 0xe0701024, 0x80013b03, + 0xe0701028, 0x80013c03, 0xe070102c, 0x80013d03, 0xe0701030, 0x80013e03, 0xe0701034, 0x80013f03, + 0xe0701038, 0x80014003, 0xe070103c, 0x80014103, 0xe0701040, 0x80014203, 0xe0701044, 0x80014303, + 0xe0701048, 0x80014403, 0xe070104c, 0x80014503, 0xe0701050, 0x80014603, 0xe0701054, 0x80014703, + 0xe0701058, 0x80014803, 0xe070105c, 0x80014903, 0xe0701060, 0x80014a03, 0xe0701064, 0x80014b03, + 0xe0701068, 0x80014c03, 0xe070106c, 0x80014d03, 0xe0701070, 0x80014e03, 0xe0701074, 0x80014f03, + 0xe0701078, 0x80015003, 0xe070107c, 0x80015103, 0xe0701080, 0x80015203, 0xe0701084, 0x80015303, + 0xe0701088, 0x80015403, 0xe070108c, 0x80015503, 0xe0701090, 0x80015603, 0xe0701094, 0x80015703, + 0xe0701098, 0x80015803, 0xe070109c, 0x80015903, 0xe07010a0, 0x80015a03, 0xe07010a4, 0x80015b03, + 0xe07010a8, 0x80015c03, 0xe07010ac, 0x80015d03, 0xe07010b0, 0x80015e03, 0xe07010b4, 0x80015f03, + 0xe07010b8, 0x80016003, 0xe07010bc, 0x80016103, 0xe07010c0, 0x80016203, 0xe07010c4, 0x80016303, + 0xe07010c8, 0x80016403, 0xe07010cc, 0x80016503, 0xe07010d0, 0x80016603, 0xe07010d4, 0x80016703, + 0xe07010d8, 0x80016803, 0xe07010dc, 0x80016903, 0xe07010e0, 0x80016a03, 0xe07010e4, 0x80016b03, + 0xe07010e8, 0x80016c03, 0xe07010ec, 0x80016d03, 0xe07010f0, 0x80016e03, 0xe07010f4, 0x80016f03, + 0xe07010f8, 0x80017003, 0xe07010fc, 0x80017103, 0xe0701100, 0x80017203, 0xe0701104, 0x80017303, + 0xe0701108, 0x80017403, 0xe070110c, 0x80017503, 0xe0701110, 0x80017603, 0xe0701114, 0x80017703, + 0xe0701118, 0x80017803, 0xe070111c, 0x80017903, 0xe0701120, 0x80017a03, 0xe0701124, 0x80017b03, + 0xe0701128, 0x80010a03, 0xe070112c, 0x80010b03, 0xe0701130, 0x80010c03, 0xe0701134, 0x80010d03, + 0xe0701138, 0x80010e03, 0xe070113c, 0x80010f03, 0xe0701140, 0x80011003, 0xe0701144, 0x80011103, + 0xe0701148, 0x80011203, 0xe070114c, 0x80011303, 0xe0701150, 0x80011403, 0xe0701154, 0x80011503, + 0xe0701158, 0x80011603, 0xe070115c, 0x80011703, 0xe0701160, 0x80011803, 0xe0701164, 0x80011903, + 0xe0701168, 0x80011a03, 0xe070116c, 0x80011b03, 0xe0701170, 0x80011c03, 0xe0701174, 0x80011d03, + 0xe0701178, 0x80011e03, 0xe070117c, 0x80011f03, 0xe0701180, 0x80012003, 0xe0701184, 0x80012103, + 0xe0701188, 0x80012203, 0xe070118c, 0x80012303, 0xe0701190, 0x80012403, 0xe0701194, 0x80012503, + 0xe0701198, 0x80012603, 0xe070119c, 0x80012703, 0xe07011a0, 0x80012803, 0xe07011a4, 0x80012903, + 0xe07011a8, 0x80012a03, 0xe07011ac, 0x80012b03, 0xe07011b0, 0x80012c03, 0xe07011b4, 0x80012d03, + 0xe07011b8, 0x80012e03, 0xe07011bc, 0x80012f03, 0xe07011c0, 0x80013003, 0xe07011c4, 0x80013103, + 0xbf810000, +}; + +constexpr std::uint64_t Sign = 0x8000000000000000ull; + +bool Nan(std::uint64_t bits) { return (bits & ~Sign) > 0x7ff0000000000000ull; } +std::uint64_t OrderKey(std::uint64_t bits) { return (bits & Sign) != 0u ? ~bits : bits | Sign; } +std::uint64_t MinMax(std::uint64_t old, std::uint64_t data, bool max) { + if (Nan(old)) return data; + if (Nan(data)) return old; + return (max ? OrderKey(data) > OrderKey(old) : OrderKey(data) < OrderKey(old)) ? data : old; +} +bool FloatEqual(std::uint64_t a, std::uint64_t b) { + return (a == b && !Nan(a)) || ((a | b) & ~Sign) == 0u; +} + +std::uint64_t Word(const std::uint32_t* words, std::uint32_t index) { + return words[index * 2u] | (static_cast(words[index * 2u + 1u]) << 32u); +} + +void Fill(std::uint32_t tid, std::uint32_t* words) { + constexpr std::array floats{ + 0x0000000000000000ull, 0x8000000000000000ull, 0x3ff0000000000000ull, 0xbff0000000000000ull, + 0x7ff8000000000000ull, 0x7ff0000000000000ull, 0x4004000000000000ull, 0xc008000000000000ull, + 0x0000000000000001ull, 0x7ff0000000000001ull, 0xfff0000000000000ull, 0x800fffffffffffffull, + }; + const std::uint32_t kind = tid % 4u; + const std::uint64_t seed = (tid + 1u) * 0x9e3779b97f4a7c15ull; + std::array values{}; + if (kind == 0u) values = {seed, seed * 0xbf58476d1ce4e5b9ull, seed ^ 0x94d049bb133111ebull}; + if (kind == 1u) values = {tid % 7u, (tid * 3u) % 7u, tid % 5u}; + if (kind == 2u) values = {floats[tid % 12u], floats[(tid / 2u + 3u) % 12u], floats[(tid + 5u) % 12u]}; + if (kind == 3u) values = {seed, (tid & 4u) != 0u ? seed : seed + 0x100000000ull, seed * 3u}; + for (std::uint32_t i = 0; i < values.size(); ++i) { + words[i * 2u] = static_cast(values[i]); + words[i * 2u + 1u] = static_cast(values[i] >> 32u); + } +} + +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::uint64_t value) { + char text[24]; + std::snprintf(text, sizeof(text), "0x%016llx", static_cast(value)); + return text; +} + +void Expect(std::uint32_t tid, std::uint64_t actual, std::uint64_t expected, const std::string& name) { + Require(actual == expected, "lds 64-bit atomics: lane " + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 2244u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void CheckLanes() { + constexpr std::array names{ + "ds_add_u64", "ds_sub_u64", "ds_rsub_u64", "ds_inc_u64", "ds_dec_u64", "ds_min_i64", "ds_max_i64", "ds_min_u64", "ds_max_u64", + "ds_and_b64", "ds_or_b64", "ds_xor_b64", "ds_mskor_b64", "ds_cmpst_b64", "ds_cmpst_f64", "ds_min_f64", "ds_max_f64", + "ds_add_rtn_u64", "ds_sub_rtn_u64", "ds_rsub_rtn_u64", "ds_inc_rtn_u64", "ds_dec_rtn_u64", "ds_min_rtn_i64", "ds_max_rtn_i64", + "ds_min_rtn_u64", "ds_max_rtn_u64", "ds_and_rtn_b64", "ds_or_rtn_b64", "ds_xor_rtn_b64", "ds_mskor_rtn_b64", "ds_wrxchg_rtn_b64", + "ds_cmpst_rtn_b64", "ds_cmpst_rtn_f64", "ds_min_rtn_f64", "ds_max_rtn_f64", + }; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + const std::uint64_t a = Word(in, 0); + const std::uint64_t b = Word(in, 1); + const std::uint64_t c = Word(in, 2); + const auto sa = static_cast(a); + const auto sb = static_cast(b); + const std::array common{ + a + b, a - b, b - a, a >= b ? 0u : a + 1u, (a == 0u || a > b) ? b : a - 1u, + sb < sa ? b : a, sb > sa ? b : a, b < a ? b : a, b > a ? b : a, + a & b, a | b, a ^ b, (a & ~b) | c, a == b ? c : a, FloatEqual(a, b) ? c : a, + MinMax(a, b, false), MinMax(a, b, true), + }; + std::array memory{}; + std::copy(common.begin(), common.end(), memory.begin()); + std::copy(common.begin(), common.begin() + 13, memory.begin() + 17); + memory[30] = b; + std::copy(common.begin() + 13, common.end(), memory.begin() + 31); + for (std::uint32_t j = 0; j < Operations; ++j) Expect(tid, Word(out, j), memory[j], names[j]); + for (std::uint32_t j = 0; j < ReturningOperations; ++j) Expect(tid, Word(out, Operations + 2u + j), a, std::string(names[17u + j]) + " returned value"); + } +} + +void CheckContention() { + std::uint64_t total = 0; + std::uint64_t maximum = 0; + std::vector pending; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + total += Word(&Input[tid * Inputs], 1); + maximum = std::max(maximum, Word(&Input[tid * Inputs], 2)); + pending.push_back(tid); + } + Expect(0, Word(Output.data(), Operations), total, "contended ds_add_rtn_u64 total"); + Expect(0, Word(Output.data(), Operations + 1u), maximum, "contended ds_max_rtn_u64 result"); + std::uint64_t current = 0; + while (!pending.empty()) { + auto next = pending.end(); + for (auto it = pending.begin(); it != pending.end(); ++it) { + if (Word(&Output[*it * Results], Operations + 2u + ReturningOperations) != current) continue; + if (next == pending.end() || Word(&Input[*it * Inputs], 1) == 0u) next = it; + } + Require(next != pending.end(), "lds 64-bit atomics: contended ds_add_rtn_u64 returned values do not form one serial order at " + Hex(current)); + current += Word(&Input[*next * Inputs], 1); + pending.erase(next); + } + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint64_t old = Word(&Output[tid * Results], Operations + 3u + ReturningOperations); + bool seen = old == 0u; + for (std::uint32_t lane = 0; lane < Threads; ++lane) seen |= Word(&Input[lane * Inputs], 2) == old; + Require(seen && old <= maximum, "lds 64-bit atomics: contended ds_max_rtn_u64 returned " + Hex(old)); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + CheckLanes(); + CheckContention(); + std::puts("lds 64-bit atomics tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp index fefa0c3cb..9be8d4d75 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp @@ -364,6 +364,24 @@ enum class IrOpcode : std::uint16_t { SharedAtomicCmpstF32, SharedAtomicMskor32, SharedAtomicWrap32, + SharedAtomicSwap64, + SharedAtomicIAdd64, + SharedAtomicISub64, + SharedAtomicRsub64, + SharedAtomicInc64, + SharedAtomicDec64, + SharedAtomicSMin64, + SharedAtomicUMin64, + SharedAtomicSMax64, + SharedAtomicUMax64, + SharedAtomicAnd64, + SharedAtomicOr64, + SharedAtomicXor64, + SharedAtomicFMin64, + SharedAtomicFMax64, + SharedAtomicCmpst64, + SharedAtomicCmpstF64, + SharedAtomicMskor64, DataAppend, DataConsume, SwizzleU32, diff --git a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp index c9b3e81ab..d080b96b2 100644 --- a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp +++ b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp @@ -405,6 +405,24 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("SharedAtomicCmpstF32", U32, U32, U32, U32, U1), makeMeta("SharedAtomicMskor32", U32, U32, U32, U32, U1), makeMeta("SharedAtomicWrap32", U32, U32, U32, U32, U1), + makeMeta("SharedAtomicSwap64", U64, U32, U64, U1), + makeMeta("SharedAtomicIAdd64", U64, U32, U64, U1), + makeMeta("SharedAtomicISub64", U64, U32, U64, U1), + makeMeta("SharedAtomicRsub64", U64, U32, U64, U1), + makeMeta("SharedAtomicInc64", U64, U32, U64, U1), + makeMeta("SharedAtomicDec64", U64, U32, U64, U1), + makeMeta("SharedAtomicSMin64", U64, U32, U64, U1), + makeMeta("SharedAtomicUMin64", U64, U32, U64, U1), + makeMeta("SharedAtomicSMax64", U64, U32, U64, U1), + makeMeta("SharedAtomicUMax64", U64, U32, U64, U1), + makeMeta("SharedAtomicAnd64", U64, U32, U64, U1), + makeMeta("SharedAtomicOr64", U64, U32, U64, U1), + makeMeta("SharedAtomicXor64", U64, U32, U64, U1), + makeMeta("SharedAtomicFMin64", U64, U32, U64, U1), + makeMeta("SharedAtomicFMax64", U64, U32, U64, U1), + makeMeta("SharedAtomicCmpst64", U64, U32, U64, U64, U1), + makeMeta("SharedAtomicCmpstF64", U64, U32, U64, U64, U1), + makeMeta("SharedAtomicMskor64", U64, U32, U64, U64, U1), makeMeta("DataAppend", U32, U32, U1, U32, U32), makeMeta("DataConsume", U32, U32, U1, U32, U32), makeMeta("SwizzleU32", U32, U32, U32, U1), @@ -595,6 +613,24 @@ SharedAccess SharedAccessOf(IrOpcode opcode) { case IrOpcode::SharedAtomicCmpstF32: case IrOpcode::SharedAtomicMskor32: case IrOpcode::SharedAtomicWrap32: + case IrOpcode::SharedAtomicSwap64: + case IrOpcode::SharedAtomicIAdd64: + case IrOpcode::SharedAtomicISub64: + case IrOpcode::SharedAtomicRsub64: + case IrOpcode::SharedAtomicInc64: + case IrOpcode::SharedAtomicDec64: + case IrOpcode::SharedAtomicSMin64: + case IrOpcode::SharedAtomicUMin64: + case IrOpcode::SharedAtomicSMax64: + case IrOpcode::SharedAtomicUMax64: + case IrOpcode::SharedAtomicAnd64: + case IrOpcode::SharedAtomicOr64: + case IrOpcode::SharedAtomicXor64: + case IrOpcode::SharedAtomicFMin64: + case IrOpcode::SharedAtomicFMax64: + case IrOpcode::SharedAtomicCmpst64: + case IrOpcode::SharedAtomicCmpstF64: + case IrOpcode::SharedAtomicMskor64: return SharedAccess::Atomic; case IrOpcode::DataAppend: return SharedAccess::Append; @@ -609,6 +645,24 @@ std::uint32_t SharedComponentCount(IrOpcode opcode) { switch (opcode) { case IrOpcode::LoadSharedU32x2: case IrOpcode::WriteSharedU32x2: + case IrOpcode::SharedAtomicSwap64: + case IrOpcode::SharedAtomicIAdd64: + case IrOpcode::SharedAtomicISub64: + case IrOpcode::SharedAtomicRsub64: + case IrOpcode::SharedAtomicInc64: + case IrOpcode::SharedAtomicDec64: + case IrOpcode::SharedAtomicSMin64: + case IrOpcode::SharedAtomicUMin64: + case IrOpcode::SharedAtomicSMax64: + case IrOpcode::SharedAtomicUMax64: + case IrOpcode::SharedAtomicAnd64: + case IrOpcode::SharedAtomicOr64: + case IrOpcode::SharedAtomicXor64: + case IrOpcode::SharedAtomicFMin64: + case IrOpcode::SharedAtomicFMax64: + case IrOpcode::SharedAtomicCmpst64: + case IrOpcode::SharedAtomicCmpstF64: + case IrOpcode::SharedAtomicMskor64: return 2u; case IrOpcode::LoadSharedU32x3: case IrOpcode::WriteSharedU32x3: diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp index 5ed43f202..3a519e5d5 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -826,6 +826,41 @@ enum class RdnaOpcode : std::uint16_t { DsCmpstRtnB32, DsCmpstRtnF32, DsWrapRtnB32, + DsAddU64, + DsSubU64, + DsRsubU64, + DsIncU64, + DsDecU64, + DsMinI64, + DsMaxI64, + DsMinU64, + DsMaxU64, + DsAndB64, + DsOrB64, + DsXorB64, + DsMskorB64, + DsCmpstB64, + DsCmpstF64, + DsMinF64, + DsMaxF64, + DsAddRtnU64, + DsSubRtnU64, + DsRsubRtnU64, + DsIncRtnU64, + DsDecRtnU64, + DsMinRtnI64, + DsMaxRtnI64, + DsMinRtnU64, + DsMaxRtnU64, + DsAndRtnB64, + DsOrRtnB64, + DsXorRtnB64, + DsMskorRtnB64, + DsWrxchgRtnB64, + DsCmpstRtnB64, + DsCmpstRtnF64, + DsMinRtnF64, + DsMaxRtnF64, DsNop, DsReadU8D16, DsReadU8D16Hi, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp index 3d40b4cf9..452522cbc 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp @@ -203,6 +203,41 @@ constexpr MemoryOpcodeInfo dsOpcodes[] = { {0x33u, RdnaOpcode::DsMaxRtnF32, 1, 32, false, false, false}, {0x34u, RdnaOpcode::DsWrapRtnB32, 1, 32, false, false, false}, {0x55u, RdnaOpcode::DsAddRtnF32, 1, 32, false, false, false}, + {0x40u, RdnaOpcode::DsAddU64, 2, 32, false, false, false}, + {0x41u, RdnaOpcode::DsSubU64, 2, 32, false, false, false}, + {0x42u, RdnaOpcode::DsRsubU64, 2, 32, false, false, false}, + {0x43u, RdnaOpcode::DsIncU64, 2, 32, false, false, false}, + {0x44u, RdnaOpcode::DsDecU64, 2, 32, false, false, false}, + {0x45u, RdnaOpcode::DsMinI64, 2, 32, false, false, false}, + {0x46u, RdnaOpcode::DsMaxI64, 2, 32, false, false, false}, + {0x47u, RdnaOpcode::DsMinU64, 2, 32, false, false, false}, + {0x48u, RdnaOpcode::DsMaxU64, 2, 32, false, false, false}, + {0x49u, RdnaOpcode::DsAndB64, 2, 32, false, false, false}, + {0x4au, RdnaOpcode::DsOrB64, 2, 32, false, false, false}, + {0x4bu, RdnaOpcode::DsXorB64, 2, 32, false, false, false}, + {0x4cu, RdnaOpcode::DsMskorB64, 2, 32, false, false, false}, + {0x50u, RdnaOpcode::DsCmpstB64, 2, 32, false, false, false}, + {0x51u, RdnaOpcode::DsCmpstF64, 2, 32, false, false, false}, + {0x52u, RdnaOpcode::DsMinF64, 2, 32, false, false, false}, + {0x53u, RdnaOpcode::DsMaxF64, 2, 32, false, false, false}, + {0x60u, RdnaOpcode::DsAddRtnU64, 2, 32, false, false, false}, + {0x61u, RdnaOpcode::DsSubRtnU64, 2, 32, false, false, false}, + {0x62u, RdnaOpcode::DsRsubRtnU64, 2, 32, false, false, false}, + {0x63u, RdnaOpcode::DsIncRtnU64, 2, 32, false, false, false}, + {0x64u, RdnaOpcode::DsDecRtnU64, 2, 32, false, false, false}, + {0x65u, RdnaOpcode::DsMinRtnI64, 2, 32, false, false, false}, + {0x66u, RdnaOpcode::DsMaxRtnI64, 2, 32, false, false, false}, + {0x67u, RdnaOpcode::DsMinRtnU64, 2, 32, false, false, false}, + {0x68u, RdnaOpcode::DsMaxRtnU64, 2, 32, false, false, false}, + {0x69u, RdnaOpcode::DsAndRtnB64, 2, 32, false, false, false}, + {0x6au, RdnaOpcode::DsOrRtnB64, 2, 32, false, false, false}, + {0x6bu, RdnaOpcode::DsXorRtnB64, 2, 32, false, false, false}, + {0x6cu, RdnaOpcode::DsMskorRtnB64, 2, 32, false, false, false}, + {0x6du, RdnaOpcode::DsWrxchgRtnB64, 2, 32, false, false, false}, + {0x70u, RdnaOpcode::DsCmpstRtnB64, 2, 32, false, false, false}, + {0x71u, RdnaOpcode::DsCmpstRtnF64, 2, 32, false, false, false}, + {0x72u, RdnaOpcode::DsMinRtnF64, 2, 32, false, false, false}, + {0x73u, RdnaOpcode::DsMaxRtnF64, 2, 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}, @@ -418,6 +453,35 @@ bool isDsAtomicOpcode(RdnaOpcode opcode) { case RdnaOpcode::DsMinRtnF32: case RdnaOpcode::DsMaxRtnF32: case RdnaOpcode::DsAddRtnF32: + case RdnaOpcode::DsAddU64: + case RdnaOpcode::DsSubU64: + case RdnaOpcode::DsRsubU64: + case RdnaOpcode::DsIncU64: + case RdnaOpcode::DsDecU64: + case RdnaOpcode::DsMinI64: + case RdnaOpcode::DsMaxI64: + case RdnaOpcode::DsMinU64: + case RdnaOpcode::DsMaxU64: + case RdnaOpcode::DsAndB64: + case RdnaOpcode::DsOrB64: + case RdnaOpcode::DsXorB64: + case RdnaOpcode::DsMinF64: + case RdnaOpcode::DsMaxF64: + case RdnaOpcode::DsAddRtnU64: + case RdnaOpcode::DsSubRtnU64: + case RdnaOpcode::DsRsubRtnU64: + case RdnaOpcode::DsIncRtnU64: + case RdnaOpcode::DsDecRtnU64: + case RdnaOpcode::DsMinRtnI64: + case RdnaOpcode::DsMaxRtnI64: + case RdnaOpcode::DsMinRtnU64: + case RdnaOpcode::DsMaxRtnU64: + case RdnaOpcode::DsAndRtnB64: + case RdnaOpcode::DsOrRtnB64: + case RdnaOpcode::DsXorRtnB64: + case RdnaOpcode::DsWrxchgRtnB64: + case RdnaOpcode::DsMinRtnF64: + case RdnaOpcode::DsMaxRtnF64: return true; default: return false; } @@ -435,7 +499,13 @@ std::uint32_t dsSourceCount(RdnaOpcode opcode) { case RdnaOpcode::DsMskorRtnB32: case RdnaOpcode::DsCmpstRtnB32: case RdnaOpcode::DsCmpstRtnF32: - case RdnaOpcode::DsWrapRtnB32: return 3u; + case RdnaOpcode::DsWrapRtnB32: + case RdnaOpcode::DsMskorB64: + case RdnaOpcode::DsCmpstB64: + case RdnaOpcode::DsCmpstF64: + case RdnaOpcode::DsMskorRtnB64: + case RdnaOpcode::DsCmpstRtnB64: + case RdnaOpcode::DsCmpstRtnF64: return 3u; case RdnaOpcode::DsMinF32: case RdnaOpcode::DsMaxF32: return 2u; case RdnaOpcode::DsNop: return 0u; diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp index 40ba4fccb..78ab61204 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp @@ -16,9 +16,11 @@ struct SpirvRequirements { bool computeDerivatives = false; bool imageGatherExtended = false; bool functionLds = false; + bool ldsLock = false; bool functionScratch = false; bool pixelValidMask = false; bool bufferInt64Atomics = false; + bool sharedInt64Atomics = false; bool float64 = false; bool coherentBuffers = false; std::vector capabilities; diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp index efb17a21a..1902a0536 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp @@ -96,6 +96,7 @@ std::uint32_t StorageBufferPackedStride(const SpirvEmitterState& state, const Me IrBufferFormat StorageBufferFormat(const SpirvEmitterState& state, const MemoryInfo& mem); void EmitMemoryOffsets(SpirvEmitterState& state); std::uint32_t LdsDwordCount(const SpirvEmitterState& state); +std::uint32_t EmitLdsLockPointer(SpirvEmitterState& state); MemoryResourceAccess PrepareMemoryResourceAccess(SpirvEmitterState& state, const MemoryInfo& mem); MemoryResourceAccess PrepareStorageBufferResourceAccess(SpirvEmitterState& state, const MemoryInfo& mem, std::uint32_t variable, std::uint32_t pointerType); std::uint32_t EmitMemoryElementIndex(SpirvEmitterState& state, const MemoryResourceAccess& access, std::uint32_t rawIndex); diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp index d89f236b3..02438feab 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp @@ -313,6 +313,24 @@ std::uint32_t EmitSharedAtomicCmpst32(SpirvValueEmitContext& ctx, const IrValue& std::uint32_t EmitSharedAtomicCmpstF32(SpirvValueEmitContext& ctx, const IrValue& inst); std::uint32_t EmitSharedAtomicMskor32(SpirvValueEmitContext& ctx, const IrValue& inst); std::uint32_t EmitSharedAtomicWrap32(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicSwap64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicIAdd64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicISub64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicRsub64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicInc64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicDec64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicSMin64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicUMin64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicSMax64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicUMax64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicAnd64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicOr64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicXor64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicFMin64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicFMax64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicCmpst64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicCmpstF64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicMskor64(SpirvValueEmitContext& ctx, const IrValue& inst); std::uint32_t EmitDataAppend(SpirvValueEmitContext& ctx, const IrValue& inst); std::uint32_t EmitDataConsume(SpirvValueEmitContext& ctx, const IrValue& inst); void EmitImageQueryDimensions(SpirvValueEmitContext& ctx, const IrValue& inst); diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvBufferAccess.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvBufferAccess.hpp index 360dce9ed..2701d0530 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvBufferAccess.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvBufferAccess.hpp @@ -13,6 +13,7 @@ std::uint32_t StorageBufferPackedStride(const SpirvEmitterState& state, const Me IrBufferFormat StorageBufferFormat(const SpirvEmitterState& state, const MemoryInfo& mem); void EmitMemoryOffsets(SpirvEmitterState& state); std::uint32_t LdsDwordCount(const SpirvEmitterState& state); +std::uint32_t EmitLdsLockPointer(SpirvEmitterState& state); MemoryResourceAccess PrepareStorageBufferResourceAccess(SpirvEmitterState& state, const MemoryInfo& mem, std::uint32_t variable, std::uint32_t pointerType); MemoryResourceAccess PrepareMemoryResourceAccess(SpirvEmitterState& state, const MemoryInfo& mem); std::uint32_t EmitMemoryElementIndex(SpirvEmitterState& state, const MemoryResourceAccess& access, std::uint32_t rawIndex); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp index edcc2d49b..e417aee83 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp @@ -113,6 +113,9 @@ SpirvRequirements AnalyzeProgramRequirements(const IrProgram& program) { } } const auto sharedAccess = SharedAccessOf(inst->Opcode()); + if (sharedAccess == SharedAccess::Atomic && inst->Type() == IrType::U64) { + requirements.sharedInt64Atomics = true; + } if (sharedAccess != SharedAccess::None) { const auto index = inst->Flags().index; if (index >= program.Resources().memoryInfo.size()) { @@ -124,6 +127,8 @@ SpirvRequirements AnalyzeProgramRequirements(const IrProgram& program) { } if (program.Resources().stage != IrShaderStage::Compute && program.Resources().stage != IrShaderStage::Mesh && kind == ResourceKind::Lds) { requirements.functionLds = true; + } else if (sharedAccess == SharedAccess::Atomic && inst->Type() == IrType::U64 && kind == ResourceKind::Lds) { + requirements.ldsLock = true; } if (sharedAccess == SharedAccess::Append || sharedAccess == SharedAccess::Consume) { requirements.subgroupBallot = true; diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp index eac7c878b..9c713071d 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp @@ -538,6 +538,24 @@ void EmitDirectInstruction(SpirvValueEmitContext& ctx, const IrValue& inst) { case IrOpcode::SharedAtomicCmpstF32: return Invoke(EmitSharedAtomicCmpstF32, ctx, inst); case IrOpcode::SharedAtomicMskor32: return Invoke(EmitSharedAtomicMskor32, ctx, inst); case IrOpcode::SharedAtomicWrap32: return Invoke(EmitSharedAtomicWrap32, ctx, inst); + case IrOpcode::SharedAtomicSwap64: return Invoke(EmitSharedAtomicSwap64, ctx, inst); + case IrOpcode::SharedAtomicIAdd64: return Invoke(EmitSharedAtomicIAdd64, ctx, inst); + case IrOpcode::SharedAtomicISub64: return Invoke(EmitSharedAtomicISub64, ctx, inst); + case IrOpcode::SharedAtomicRsub64: return Invoke(EmitSharedAtomicRsub64, ctx, inst); + case IrOpcode::SharedAtomicInc64: return Invoke(EmitSharedAtomicInc64, ctx, inst); + case IrOpcode::SharedAtomicDec64: return Invoke(EmitSharedAtomicDec64, ctx, inst); + case IrOpcode::SharedAtomicSMin64: return Invoke(EmitSharedAtomicSMin64, ctx, inst); + case IrOpcode::SharedAtomicUMin64: return Invoke(EmitSharedAtomicUMin64, ctx, inst); + case IrOpcode::SharedAtomicSMax64: return Invoke(EmitSharedAtomicSMax64, ctx, inst); + case IrOpcode::SharedAtomicUMax64: return Invoke(EmitSharedAtomicUMax64, ctx, inst); + case IrOpcode::SharedAtomicAnd64: return Invoke(EmitSharedAtomicAnd64, ctx, inst); + case IrOpcode::SharedAtomicOr64: return Invoke(EmitSharedAtomicOr64, ctx, inst); + case IrOpcode::SharedAtomicXor64: return Invoke(EmitSharedAtomicXor64, ctx, inst); + case IrOpcode::SharedAtomicFMin64: return Invoke(EmitSharedAtomicFMin64, ctx, inst); + case IrOpcode::SharedAtomicFMax64: return Invoke(EmitSharedAtomicFMax64, ctx, inst); + case IrOpcode::SharedAtomicCmpst64: return Invoke(EmitSharedAtomicCmpst64, ctx, inst); + case IrOpcode::SharedAtomicCmpstF64: return Invoke(EmitSharedAtomicCmpstF64, ctx, inst); + case IrOpcode::SharedAtomicMskor64: return Invoke(EmitSharedAtomicMskor64, ctx, inst); case IrOpcode::DataAppend: return Invoke(EmitDataAppend, ctx, inst); case IrOpcode::DataConsume: return Invoke(EmitDataConsume, ctx, inst); case IrOpcode::SwizzleU32: return Invoke(EmitSwizzleU32, ctx, inst); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp index 1292d8e47..627319a5a 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp @@ -41,7 +41,7 @@ namespace { if (ShaderWorkgroupInput(state) == nullptr) { FailEmit("function LDS was not prepared before function emission"); } - state.ldsVariable = state.module.DefineGlobalVariable(TypeU32ArrayPointer(state, spv::StorageClassWorkgroup, LdsDwordCount(state)), spv::StorageClassWorkgroup); + state.ldsVariable = state.module.DefineGlobalVariable(TypeU32ArrayPointer(state, spv::StorageClassWorkgroup, LdsDwordCount(state) + (state.requirements.ldsLock ? 1u : 0u)), spv::StorageClassWorkgroup); state.module.AddName(state.ldsVariable, "lds_dwords"); } @@ -99,6 +99,16 @@ std::uint32_t LdsDwordCount(const SpirvEmitterState& state) { return workgroup != nullptr ? workgroup->ldsSizeDwords : FunctionLdsDwords; } +std::uint32_t EmitLdsLockPointer(SpirvEmitterState& state) { + if (!state.requirements.ldsLock) { + FailEmit("LDS lock was not requested by the program analysis"); + } + EnsureLdsStorage(state); + const auto pointer = state.module.AllocateId(); + state.module.AddFunction(spv::OpAccessChain, TypeU32ElementPointer(state, spv::StorageClassWorkgroup), pointer, state.ldsVariable, ConstantU32(state, LdsDwordCount(state))); + return pointer; +} + MemoryResourceAccess PrepareStorageBufferResourceAccess(SpirvEmitterState& state, const MemoryInfo& mem, std::uint32_t variable, std::uint32_t pointerType) { if (variable == 0) { ExitDescriptorBindingFailure(state, DescriptorBindingKind::Buffers, mem.resource, "storage buffer descriptor array was not emitted"); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp index 23216880a..d091a2f2a 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp @@ -1086,6 +1086,106 @@ std::uint32_t AppendConsume(SpirvValueEmitContext& ctx, const IrValue& inst, boo return result; } +template +std::uint32_t LockedLdsUpdate(SpirvEmitterState& state, TUpdate&& update) { + const auto lock = EmitLdsLockPointer(state); + const auto header = state.module.AllocateId(); + const auto body = state.module.AllocateId(); + const auto critical = state.module.AllocateId(); + const auto after = state.module.AllocateId(); + const auto cont = state.module.AllocateId(); + const auto merge = state.module.AllocateId(); + state.module.AddFunction(spv::OpBranch, header); + EmitLabel(state, header); + state.module.AddFunction(spv::OpLoopMerge, merge, cont, spv::LoopControlMaskNone); + state.module.AddFunction(spv::OpBranch, body); + EmitLabel(state, body); + const auto previous = state.module.AllocateId(); + state.module.AddFunction(spv::OpAtomicCompareExchange, TypeU32(state), previous, lock, ConstantU32(state, spv::ScopeWorkgroup), ConstantU32(state, spv::MemorySemanticsAcquireMask | spv::MemorySemanticsWorkgroupMemoryMask), ConstantU32(state, spv::MemorySemanticsMaskNone), ConstantU32(state, 1u), ConstantU32(state, 0u)); + const auto acquired = Binary(state, spv::OpIEqual, TypeBool(state), previous, ConstantU32(state, 0u)); + state.module.AddFunction(spv::OpSelectionMerge, after, spv::SelectionControlMaskNone); + state.module.AddFunction(spv::OpBranchConditional, acquired, critical, after); + EmitLabel(state, critical); + const auto old = update(); + state.module.AddFunction(spv::OpAtomicStore, lock, ConstantU32(state, spv::ScopeWorkgroup), ConstantU32(state, spv::MemorySemanticsReleaseMask | spv::MemorySemanticsWorkgroupMemoryMask), ConstantU32(state, 0u)); + const auto criticalExit = state.currentLabel; + state.module.AddFunction(spv::OpBranch, after); + EmitLabel(state, after); + const auto result = state.module.AllocateId(); + state.module.AddFunction(spv::OpPhi, TypeU64(state), result, old, criticalExit, ConstantU64(state, 0u), body); + state.module.AddFunction(spv::OpBranchConditional, acquired, merge, cont); + EmitLabel(state, cont); + state.module.AddFunction(spv::OpBranch, header); + EmitLabel(state, merge); + return result; +} + +template +std::uint32_t SharedAtomic64(SpirvValueEmitContext& ctx, const IrValue& inst, TReplacement&& replacement) { + auto& state = ctx.state; + const auto& mem = SharedMemory(ctx, inst); + return EmitValueOrDefaultIfCondition(state, ActiveArgument(ctx, inst), TypeU64(state), ConstantU64(state, 0u), [&]() { + const auto resource = PrepareMemoryResourceAccess(state, mem); + const auto rawIndex = DwordIndex(ctx, inst, mem); + const auto low = EmitMemoryElementIndex(state, resource, rawIndex); + const auto high = EmitMemoryElementIndex(state, resource, Binary(state, spv::OpIAdd, TypeU32(state), rawIndex, ConstantU32(state, 1u))); + return EmitValueOrDefaultIfCondition(state, EmitMemoryElementInBounds(state, resource, high), TypeU64(state), ConstantU64(state, 0u), [&]() { + const auto update = [&]() { + const auto lowPointer = EmitMemoryElementPointer(state, resource, low); + const auto highPointer = EmitMemoryElementPointer(state, resource, high); + const auto lowValue = state.module.AllocateId(); + const auto highValue = state.module.AllocateId(); + state.module.AddFunction(spv::OpLoad, TypeU32(state), lowValue, lowPointer); + state.module.AddFunction(spv::OpLoad, TypeU32(state), highValue, highPointer); + const auto old = state.module.AllocateId(); + state.module.AddFunction(spv::OpCompositeConstruct, TypeU64(state), old, lowValue, highValue); + const auto next = Unary(state, spv::OpBitcast, TypeU64(state), replacement(state, Unary(state, spv::OpBitcast, TypeScalarU64(state), old))); + const auto nextLow = state.module.AllocateId(); + const auto nextHigh = state.module.AllocateId(); + state.module.AddFunction(spv::OpCompositeExtract, TypeU32(state), nextLow, next, 0u); + state.module.AddFunction(spv::OpCompositeExtract, TypeU32(state), nextHigh, next, 1u); + state.module.AddFunction(spv::OpStore, lowPointer, nextLow); + state.module.AddFunction(spv::OpStore, highPointer, nextHigh); + return old; + }; + return state.requirements.ldsLock ? LockedLdsUpdate(state, update) : update(); + }); + }); +} + +template +std::uint32_t SharedAtomic64Binary(SpirvValueEmitContext& ctx, const IrValue& inst, TOperation&& operation) { + const auto data = ScalarU64Argument(ctx, inst, 2u); + return SharedAtomic64(ctx, inst, [&](SpirvEmitterState& state, std::uint32_t old) { + return operation(state, old, data); + }); +} + +std::uint32_t SharedAtomic64Op(SpirvValueEmitContext& ctx, const IrValue& inst, spv::Op opcode, bool dataFirst) { + return SharedAtomic64Binary(ctx, inst, [&](SpirvEmitterState& state, std::uint32_t old, std::uint32_t data) { + return dataFirst ? Binary(state, opcode, TypeScalarU64(state), data, old) : Binary(state, opcode, TypeScalarU64(state), old, data); + }); +} + +std::uint32_t SharedAtomic64Select(SpirvValueEmitContext& ctx, const IrValue& inst, spv::Op compare) { + return SharedAtomic64Binary(ctx, inst, [&](SpirvEmitterState& state, std::uint32_t old, std::uint32_t data) { + return Select(state, TypeScalarU64(state), Binary(state, compare, TypeBool(state), data, old), data, old); + }); +} + +std::uint32_t SharedFloatMinMax64(SpirvValueEmitContext& ctx, const IrValue& inst, bool maxValue) { + return SharedAtomic64Binary(ctx, inst, [&](SpirvEmitterState& state, std::uint32_t old, std::uint32_t data) { + const AtomicFloatBits bits{state, true}; + auto result = bits.select(bits.test(maxValue ? spv::OpUGreaterThan : spv::OpULessThan, bits.orderKey(data), bits.orderKey(old)), data, old); + result = bits.select(bits.nan(data), old, result); + return bits.select(bits.nan(old), data, result); + }); +} + +std::uint32_t ScalarU64Constant(SpirvEmitterState& state, std::uint32_t value) { + return state.module.Constant(spv::OpConstant, TypeScalarU64(state), value, 0u); +} + } std::uint32_t AtomicIncrement(SpirvEmitterState& state, std::uint32_t old, std::uint32_t limit) { @@ -1499,6 +1599,99 @@ std::uint32_t EmitSharedAtomicWrap32(SpirvValueEmitContext& ctx, const IrValue& }); } +std::uint32_t EmitSharedAtomicSwap64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Binary(ctx, inst, [](SpirvEmitterState&, std::uint32_t, std::uint32_t data) { return data; }); +} + +std::uint32_t EmitSharedAtomicIAdd64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Op(ctx, inst, spv::OpIAdd, false); +} + +std::uint32_t EmitSharedAtomicISub64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Op(ctx, inst, spv::OpISub, false); +} + +std::uint32_t EmitSharedAtomicRsub64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Op(ctx, inst, spv::OpISub, true); +} + +std::uint32_t EmitSharedAtomicInc64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Binary(ctx, inst, [](SpirvEmitterState& state, std::uint32_t old, std::uint32_t limit) { + const auto wrap = Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), old, limit); + return Select(state, TypeScalarU64(state), wrap, ScalarU64Constant(state, 0u), Binary(state, spv::OpIAdd, TypeScalarU64(state), old, ScalarU64Constant(state, 1u))); + }); +} + +std::uint32_t EmitSharedAtomicDec64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Binary(ctx, inst, [](SpirvEmitterState& state, std::uint32_t old, std::uint32_t limit) { + const auto zero = Binary(state, spv::OpIEqual, TypeBool(state), old, ScalarU64Constant(state, 0u)); + const auto wrap = Binary(state, spv::OpLogicalOr, TypeBool(state), zero, Binary(state, spv::OpUGreaterThan, TypeBool(state), old, limit)); + return Select(state, TypeScalarU64(state), wrap, limit, Binary(state, spv::OpISub, TypeScalarU64(state), old, ScalarU64Constant(state, 1u))); + }); +} + +std::uint32_t EmitSharedAtomicSMin64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Select(ctx, inst, spv::OpSLessThan); +} + +std::uint32_t EmitSharedAtomicUMin64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Select(ctx, inst, spv::OpULessThan); +} + +std::uint32_t EmitSharedAtomicSMax64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Select(ctx, inst, spv::OpSGreaterThan); +} + +std::uint32_t EmitSharedAtomicUMax64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Select(ctx, inst, spv::OpUGreaterThan); +} + +std::uint32_t EmitSharedAtomicAnd64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Op(ctx, inst, spv::OpBitwiseAnd, false); +} + +std::uint32_t EmitSharedAtomicOr64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Op(ctx, inst, spv::OpBitwiseOr, false); +} + +std::uint32_t EmitSharedAtomicXor64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Op(ctx, inst, spv::OpBitwiseXor, false); +} + +std::uint32_t EmitSharedAtomicFMin64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedFloatMinMax64(ctx, inst, false); +} + +std::uint32_t EmitSharedAtomicFMax64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedFloatMinMax64(ctx, inst, true); +} + +std::uint32_t EmitSharedAtomicCmpst64(SpirvValueEmitContext& ctx, const IrValue& inst) { + const auto compare = ScalarU64Argument(ctx, inst, 3u); + const auto source = ScalarU64Argument(ctx, inst, 2u); + return SharedAtomic64(ctx, inst, [&](SpirvEmitterState& state, std::uint32_t old) { + return Select(state, TypeScalarU64(state), Binary(state, spv::OpIEqual, TypeBool(state), old, compare), source, old); + }); +} + +std::uint32_t EmitSharedAtomicCmpstF64(SpirvValueEmitContext& ctx, const IrValue& inst) { + const auto compare = ScalarU64Argument(ctx, inst, 3u); + const auto source = ScalarU64Argument(ctx, inst, 2u); + return SharedAtomic64(ctx, inst, [&](SpirvEmitterState& state, std::uint32_t old) { + const AtomicFloatBits bits{state, true}; + return bits.select(bits.equal(old, compare), source, old); + }); +} + +std::uint32_t EmitSharedAtomicMskor64(SpirvValueEmitContext& ctx, const IrValue& inst) { + const auto mask = ScalarU64Argument(ctx, inst, 3u); + const auto source = ScalarU64Argument(ctx, inst, 2u); + return SharedAtomic64(ctx, inst, [&](SpirvEmitterState& state, std::uint32_t old) { + const auto kept = Binary(state, spv::OpBitwiseAnd, TypeScalarU64(state), old, Unary(state, spv::OpNot, TypeScalarU64(state), mask)); + return Binary(state, spv::OpBitwiseOr, TypeScalarU64(state), kept, source); + }); +} + std::uint32_t EmitBufferAtomicInc32(SpirvValueEmitContext& ctx, const IrValue& inst) { return EmitAtomicUpdate(ctx, inst, BufferMemory(ctx, inst), AtomicIncrement); } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp index 3a3b9539d..9b56e98ae 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp @@ -123,6 +123,9 @@ void DefineModule(SpirvEmitterState& state) { state.module.EmitCapability(spv::CapabilityInt64); state.module.EmitCapability(spv::CapabilityInt64Atomics); } + if (state.requirements.sharedInt64Atomics) { + state.module.EmitCapability(spv::CapabilityInt64); + } if (state.clipDistanceVariable != 0) { state.module.EmitCapability(spv::CapabilityClipDistance); } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp index b85d7ee98..ea1c82f37 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp @@ -916,6 +916,10 @@ void EmitProgram(SpirvEmitterState& state) { state.module.AddFunction(spv::OpVariable, TypePointer(state, spv::StorageClassFunction, TypeU32(state)), lane.scratchU32Variable, spv::StorageClassFunction); } } + if (state.requirements.ldsLock) { + state.module.AddFunction(spv::OpStore, EmitLdsLockPointer(state), ConstantU32(state, 0u)); + state.module.AddFunction(spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup), ConstantU32(state, spv::ScopeWorkgroup), ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask | spv::MemorySemanticsWorkgroupMemoryMask)); + } if (state.gdsVariable != 0u) { state.gdsLength = state.module.AllocateId(); state.module.AddFunction(spv::OpArrayLength, TypeU32(state), state.gdsLength, state.gdsVariable, 0u); diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index 8622e85b1..35d84281e 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -108,6 +108,7 @@ private: bool dsWrite(const RdnaInstruction& inst); bool dsWrite2(const RdnaInstruction& inst); bool dsAtomic2(const RdnaInstruction& inst, IrOpcode opcode, bool returnsValue); + 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); diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp index 1031f7aa8..054a1e1e8 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp @@ -276,6 +276,76 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) { return dsAtomic2(inst, IrOpcode::SharedAtomicCmpstF32, true); case RdnaOpcode::DsWrapRtnB32: return dsAtomic2(inst, IrOpcode::SharedAtomicWrap32, true); + case RdnaOpcode::DsAddU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicIAdd64, false); + case RdnaOpcode::DsAddRtnU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicIAdd64, true); + case RdnaOpcode::DsSubU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicISub64, false); + case RdnaOpcode::DsSubRtnU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicISub64, true); + case RdnaOpcode::DsRsubU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicRsub64, false); + case RdnaOpcode::DsRsubRtnU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicRsub64, true); + case RdnaOpcode::DsIncU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicInc64, false); + case RdnaOpcode::DsIncRtnU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicInc64, true); + case RdnaOpcode::DsDecU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicDec64, false); + case RdnaOpcode::DsDecRtnU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicDec64, true); + case RdnaOpcode::DsMinI64: + return dsAtomic64(inst, IrOpcode::SharedAtomicSMin64, false); + case RdnaOpcode::DsMinRtnI64: + return dsAtomic64(inst, IrOpcode::SharedAtomicSMin64, true); + case RdnaOpcode::DsMaxI64: + return dsAtomic64(inst, IrOpcode::SharedAtomicSMax64, false); + case RdnaOpcode::DsMaxRtnI64: + return dsAtomic64(inst, IrOpcode::SharedAtomicSMax64, true); + case RdnaOpcode::DsMinU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicUMin64, false); + case RdnaOpcode::DsMinRtnU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicUMin64, true); + case RdnaOpcode::DsMaxU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicUMax64, false); + case RdnaOpcode::DsMaxRtnU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicUMax64, true); + case RdnaOpcode::DsAndB64: + return dsAtomic64(inst, IrOpcode::SharedAtomicAnd64, false); + case RdnaOpcode::DsAndRtnB64: + return dsAtomic64(inst, IrOpcode::SharedAtomicAnd64, true); + case RdnaOpcode::DsOrB64: + return dsAtomic64(inst, IrOpcode::SharedAtomicOr64, false); + case RdnaOpcode::DsOrRtnB64: + return dsAtomic64(inst, IrOpcode::SharedAtomicOr64, true); + case RdnaOpcode::DsXorB64: + return dsAtomic64(inst, IrOpcode::SharedAtomicXor64, false); + case RdnaOpcode::DsXorRtnB64: + return dsAtomic64(inst, IrOpcode::SharedAtomicXor64, true); + case RdnaOpcode::DsMskorB64: + return dsAtomic64(inst, IrOpcode::SharedAtomicMskor64, false); + case RdnaOpcode::DsMskorRtnB64: + return dsAtomic64(inst, IrOpcode::SharedAtomicMskor64, true); + case RdnaOpcode::DsCmpstB64: + return dsAtomic64(inst, IrOpcode::SharedAtomicCmpst64, false); + case RdnaOpcode::DsCmpstRtnB64: + return dsAtomic64(inst, IrOpcode::SharedAtomicCmpst64, true); + case RdnaOpcode::DsCmpstF64: + return dsAtomic64(inst, IrOpcode::SharedAtomicCmpstF64, false); + case RdnaOpcode::DsCmpstRtnF64: + return dsAtomic64(inst, IrOpcode::SharedAtomicCmpstF64, true); + case RdnaOpcode::DsMinF64: + return dsAtomic64(inst, IrOpcode::SharedAtomicFMin64, false); + case RdnaOpcode::DsMinRtnF64: + return dsAtomic64(inst, IrOpcode::SharedAtomicFMin64, true); + case RdnaOpcode::DsMaxF64: + return dsAtomic64(inst, IrOpcode::SharedAtomicFMax64, false); + case RdnaOpcode::DsMaxRtnF64: + return dsAtomic64(inst, IrOpcode::SharedAtomicFMax64, true); + case RdnaOpcode::DsWrxchgRtnB64: + return dsAtomic64(inst, IrOpcode::SharedAtomicSwap64, true); case RdnaOpcode::DsNop: emitControlNop(); return true; diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/SharedMemoryInstructions.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/SharedMemoryInstructions.cpp index 019d6596a..11dc351fa 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/SharedMemoryInstructions.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/SharedMemoryInstructions.cpp @@ -182,6 +182,25 @@ bool TranslationContext::dsAtomic2(const RdnaInstruction& inst, IrOpcode opcode, return true; } +bool TranslationContext::dsAtomic64(const RdnaInstruction& inst, IrOpcode opcode, bool returnsValue) { + if (inst.gds) { + throw std::runtime_error("64-bit GDS atomics are not supported"); + } + const MemoryInfo memory = sharedMemoryInfoFromInstruction(inst); + const IrU32 address = readU32(inst.source0); + const IrU64 data0 = readU64(inst.source1); + IrValue& active = ir.GetExec(); + IrValue* old; + if (IrOpcodeOperandCount(opcode) == 4u) { + const IrU64 data1 = readU64(inst.source2); + old = &ir.Emit(opcode, IrOpcodeType(opcode), {&address.Value(), &data0.Value(), &data1.Value(), &active}, addMemoryInfo(memory, inst.programCounter)); + } else { + old = &ir.Emit(opcode, IrOpcodeType(opcode), {&address.Value(), &data0.Value(), &active}, addMemoryInfo(memory, inst.programCounter)); + } + if (returnsValue) writeOperand(inst.destination, old); + return true; +} + bool TranslationContext::dsAppendConsume(const RdnaInstruction& inst, IrOpcode opcode) { const MemoryInfo memory = sharedMemoryInfoFromInstruction(inst); const IrU32 address = readU32(makeM0Operand()); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 9fe00e510..0d701db33 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -70,3 +70,4 @@ Throughout the project, every function at every stage either **does exactly what - [libSceAudiodec](../../core/libs/prx/libSceAudiodec/Export.cpp) throws for the 24-bit PCM word size (`iBwPcm` 0): its sample layout is unknown. ATRAC9 decoding is covered only by configuration and error tests, as no ATRAC9 encoder is available for a fixture. - [libScePngEnc](../../core/libs/prx/libScePngEnc/Export.cpp) honours `filter_type` only as all filters (adaptive) or a single filter: the [PNG encoder](../../core/Decoder/Png/src/Png.cpp) (stb) cannot restrict adaptive filtering to a subset, so the first filter in the mask is used. - [libScePlayerInvitationDialog](../../core/libs/prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.cpp) simulates dialog completion without displaying UI or sending invitations; its parameter ABI remains unverified. +- [64-bit LDS atomics](../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) (`ds_*_u64`, `ds_*_b64`, `ds_*_f64`) read and write the two dwords under a workgroup lock stored after the guest LDS, as LDS is a 32-bit array without 64-bit atomics. They are atomic with respect to each other only: 32-bit accesses to the same dwords issued concurrently are not ordered by the lock. 64-bit GDS atomics throw. `ds_min/max(_rtn)_f64` and `ds_cmpst(_rtn)_f64` follow the NaN and signed-zero rules measured for the f32 forms; the f64 forms were not measured on hardware. From b6c97084ed5b8e746ba2579fc6de0f38b795ffc6 Mon Sep 17 00:00:00 2001 From: DiulioAires Date: Sun, 4 Oct 2026 20:17:21 -0300 Subject: [PATCH 020/164] fix(relinker): check byte writer bounds --- core/relinker/CMakeLists.txt | 1 + core/relinker/io/src/ByteWriter.cpp | 9 +++++++++ core/relinker/io/tests/BufferBoundsTests.cpp | 6 ++++++ 3 files changed, 16 insertions(+) diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index c04c4883a..5865b02a9 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -128,6 +128,7 @@ if(BUILD_TESTING) add_test_executable(buffer_bounds_tests ${CMAKE_CURRENT_SOURCE_DIR}/io/tests/BufferBoundsTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io/src/ByteReader.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io/src/ByteWriter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io/src/BufferUtils.cpp ) target_include_directories(buffer_bounds_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/io/include) diff --git a/core/relinker/io/src/ByteWriter.cpp b/core/relinker/io/src/ByteWriter.cpp index 080fd5f0b..5d7aed2d3 100644 --- a/core/relinker/io/src/ByteWriter.cpp +++ b/core/relinker/io/src/ByteWriter.cpp @@ -1,21 +1,30 @@ #include #include +#include namespace Io { void ByteWriter::WriteU8(std::vector& buf, std::size_t offset, std::uint8_t v) const { + if (offset >= buf.size()) + throw std::out_of_range("ByteWriter::WriteU8 out of bounds"); buf[offset] = v; } void ByteWriter::WriteU16(std::vector& buf, std::size_t offset, std::uint16_t v) const { + if (offset > buf.size() || buf.size() - offset < 2) + throw std::out_of_range("ByteWriter::WriteU16 out of bounds"); std::memcpy(buf.data() + offset, &v, 2); } void ByteWriter::WriteU32(std::vector& buf, std::size_t offset, std::uint32_t v) const { + if (offset > buf.size() || buf.size() - offset < 4) + throw std::out_of_range("ByteWriter::WriteU32 out of bounds"); std::memcpy(buf.data() + offset, &v, 4); } void ByteWriter::WriteU64(std::vector& buf, std::size_t offset, std::uint64_t v) const { + if (offset > buf.size() || buf.size() - offset < 8) + throw std::out_of_range("ByteWriter::WriteU64 out of bounds"); std::memcpy(buf.data() + offset, &v, 8); } diff --git a/core/relinker/io/tests/BufferBoundsTests.cpp b/core/relinker/io/tests/BufferBoundsTests.cpp index 8cdacaf8a..2de9beb8b 100644 --- a/core/relinker/io/tests/BufferBoundsTests.cpp +++ b/core/relinker/io/tests/BufferBoundsTests.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -75,9 +76,14 @@ void writeBounds(const TWrite& write, const std::string& name) { int main() { try { const Io::ByteReader reader; + const Io::ByteWriter writer; readBounds([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU16(bytes, offset); }, "ByteReader::ReadU16"); readBounds([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU32(bytes, offset); }, "ByteReader::ReadU32"); readBounds([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU64(bytes, offset); }, "ByteReader::ReadU64"); + writeBounds([&](Bytes& bytes, std::size_t offset, std::uint8_t value) { writer.WriteU8(bytes, offset, value); }, "ByteWriter::WriteU8"); + writeBounds([&](Bytes& bytes, std::size_t offset, std::uint16_t value) { writer.WriteU16(bytes, offset, value); }, "ByteWriter::WriteU16"); + writeBounds([&](Bytes& bytes, std::size_t offset, std::uint32_t value) { writer.WriteU32(bytes, offset, value); }, "ByteWriter::WriteU32"); + writeBounds([&](Bytes& bytes, std::size_t offset, std::uint64_t value) { writer.WriteU64(bytes, offset, value); }, "ByteWriter::WriteU64"); readBounds(Io::ReadU16, "ReadU16"); readBounds(Io::ReadU32, "ReadU32"); readBounds(Io::ReadU64, "ReadU64"); From 9e0152f18d1242c84f2ce6812043d4a3ab71380c Mon Sep 17 00:00:00 2001 From: Pouare514 Date: Mon, 5 Oct 2026 01:21:44 +0200 Subject: [PATCH 021/164] fix(libkernel): replace mappings on fixed reserve over registered range --- .../libkernel/DirectMemory/DirectMemory.cpp | 6 +++- core/libs/tests/GuestMemory.cpp | 30 ++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index 820c6737b..c3bcd4c6b 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -593,7 +593,11 @@ int DoReserveVirtual(void** addr, size_t len, int flags, size_t alignment) { if (len == 0 || (len & (PS5_PAGE_SIZE - 1)) != 0) return SCE_KERNEL_ERROR_EINVAL; GuestAllocations::Mutation mutation; const bool fixed = *addr != nullptr && (flags & GuestMapFixedFlag) != 0; - if (fixed && mutation.Covers(*addr, len)) return 0; + if (fixed && mutation.Covers(*addr, len)) { + constexpr int GuestMapNoOverwrite = 0x80; + if ((flags & GuestMapNoOverwrite) == 0 && RemapFixedIntoRegistered(mutation, *addr, len, 0, GuestMapFixedFlag)) return 0; + mutation.RequireAvailable(*addr, len); + } if (fixed) mutation.RequireAvailable(*addr, len); constexpr int GuestMapNoCoalesce = 0x400000; void* mapped = MapAligned(fixed ? *addr : nullptr, len, PROT_NONE, fixed ? GuestMapFixedFlag | (flags & GuestMapNoCoalesce) : 0, alignment); diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp index 7415ff4f8..7cdbbcccd 100644 --- a/core/libs/tests/GuestMemory.cpp +++ b/core/libs/tests/GuestMemory.cpp @@ -175,7 +175,35 @@ static void CheckFixedVirtualReservation() { void* again = requested; Require(sceKernelReserveVirtualRange(&again, page * 2, 0x400010, 0) == 0); Require(again == requested); - Require(sceKernelMunmap(fixed, page * 2) == 0); + std::int64_t phys = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0); + void* mapped = requested; + Require(sceKernelMapDirectMemory(&mapped, page * 2, 3, 0x10, phys, 0) == 0); + Require(mapped == requested); + static_cast(mapped)[0] = 11; + VirtualQueryInfo before{}; + Require(sceKernelVirtualQuery(mapped, 0, &before, sizeof(before)) == 0); + Require(before.is_direct); + void* reserved = requested; + Require(sceKernelReserveVirtualRange(&reserved, page * 2, 0x10, 0) == 0); + Require(reserved == requested); + VirtualQueryInfo after{}; + Require(sceKernelVirtualQuery(reserved, 0, &after, sizeof(after)) != 0); + void* remapped = requested; + Require(sceKernelMapDirectMemory(&remapped, page * 2, 3, 0x10, phys, 0) == 0); + Require(remapped == requested); + VirtualQueryInfo revived{}; + Require(sceKernelVirtualQuery(remapped, 0, &revived, sizeof(revived)) == 0); + Require(revived.is_direct); + bool refused = false; + try { + sceKernelReserveVirtualRange(&reserved, page * 2, 0x90, 0); + } catch (const std::exception&) { + refused = true; + } + Require(refused); + Require(sceKernelMunmap(reserved, page * 2) == 0); + Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); void* pooled = nullptr; Require(sceKernelMemoryPoolReserve(requested, page * 2, 0, 0x10, &pooled) == 0); Require(pooled == requested); From f99be7e4d355ae374a753746c7e14835d74d5310 Mon Sep 17 00:00:00 2001 From: DotDebian Date: Mon, 5 Oct 2026 01:24:18 +0200 Subject: [PATCH 022/164] feat(ajm): implement sceAjmDecMp3ParseFrame Read the MPEG audio frame header and report the frame size, channel count, samples per channel, bitrate and sample rate. The signature, the result layout and the rate tables follow shadPS4's reimplementation of the PS4 library. Requesting the original file length throws: that lookup is not implemented. Co-Authored-By: Claude Opus 5.5 --- core/libs/SceTypes.hpp | 12 ++++++ core/libs/prx/libSceAjm.native/Export.cpp | 5 --- core/libs/prx/libSceAjm.native/src/Ajm.cpp | 30 ++++++++++++++ core/libs/tests/GuestAjm.cpp | 48 ++++++++++++++++++++++ docs/dev/TechnicalDebt.md | 2 + 5 files changed, 92 insertions(+), 5 deletions(-) diff --git a/core/libs/SceTypes.hpp b/core/libs/SceTypes.hpp index def7785d9..f58476bec 100644 --- a/core/libs/SceTypes.hpp +++ b/core/libs/SceTypes.hpp @@ -389,6 +389,18 @@ struct AjmDecAt9ConfigDataInfo { std::uint32_t superframe_size; }; +struct AjmDecMp3ParseFrame { + std::uint64_t frame_size; + std::uint32_t num_channels; + std::uint32_t samples_per_channel; + std::uint32_t bitrate; + std::uint32_t sample_rate; + std::uint32_t encoder_delay; + std::uint32_t num_frames; + std::uint32_t total_samples; + std::uint32_t ofl_type; +}; + using AudioOut2ContextHandle = std::uint64_t; using AudioOut2PortHandle = std::uint64_t; using AudioOut2UserHandle = std::uintptr_t; diff --git a/core/libs/prx/libSceAjm.native/Export.cpp b/core/libs/prx/libSceAjm.native/Export.cpp index ff03fa3b4..11bc7e1b7 100644 --- a/core/libs/prx/libSceAjm.native/Export.cpp +++ b/core/libs/prx/libSceAjm.native/Export.cpp @@ -129,9 +129,4 @@ const char* APS5_VABI sceAjmStrError(int error) { return nullptr; } -int APS5_VABI sceAjmDecMp3ParseFrame() { - AjmStub(__func__); - return 0; -} - } diff --git a/core/libs/prx/libSceAjm.native/src/Ajm.cpp b/core/libs/prx/libSceAjm.native/src/Ajm.cpp index 22749392c..5678a519b 100644 --- a/core/libs/prx/libSceAjm.native/src/Ajm.cpp +++ b/core/libs/prx/libSceAjm.native/src/Ajm.cpp @@ -832,6 +832,36 @@ int APS5_VABI sceAjmDecAt9ParseConfigData(const void* config_data, AjmDecAt9Conf return 0; } +int APS5_VABI sceAjmDecMp3ParseFrame(const uint8_t* stream, uint32_t streamSize, int parseOfl, AjmDecMp3ParseFrame* frame) { + if (!stream || streamSize < 4 || !frame) return SCE_AJM_ERROR_INVALID_PARAMETER; + static constexpr std::uint32_t sampleRates[4][4] = {{11025, 12000, 8000, 0}, {0, 0, 0, 0}, {22050, 24000, 16000, 0}, {44100, 48000, 32000, 0}}; + static constexpr std::uint32_t kbps[4][16] = { + {0, 8, 16, 24, 32, 40, 48, 56, 64, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0}, + {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0}, + }; + const std::uint32_t header = (std::uint32_t{stream[0]} << 24u) | (std::uint32_t{stream[1]} << 16u) | (std::uint32_t{stream[2]} << 8u) | stream[3]; + const std::uint32_t version = (header >> 19u) & 3u; + const std::uint32_t sampleRate = sampleRates[version][(header >> 10u) & 3u]; + const std::uint32_t bitrate = kbps[version][(header >> 12u) & 15u] * 1000u; + const bool valid = (header >> 21u) == 0x7FFu && sampleRate != 0 && bitrate != 0; + AJM_TRACE("[ajm] parse mp3 frame %02x %02x %02x %02x (%u bytes, parse ofl %d) -> %s, %u Hz, %u bps\n", stream[0], stream[1], stream[2], stream[3], streamSize, parseOfl, valid ? "ok" : "invalid", sampleRate, bitrate); + if (!valid) return SCE_AJM_ERROR_INVALID_PARAMETER; + if (parseOfl) NotImplemented_nid_no_patch("sceAjmDecMp3ParseFrame (original file length lookup)"); + const bool mpeg1 = version == 3; + frame->frame_size = (mpeg1 ? 144u : 72u) * bitrate / sampleRate + ((header >> 9u) & 1u); + frame->num_channels = ((header >> 6u) & 3u) == 3u ? 1u : 2u; + frame->samples_per_channel = mpeg1 ? 1152u : 576u; + frame->bitrate = bitrate; + frame->sample_rate = sampleRate; + frame->encoder_delay = 0; + frame->num_frames = 0; + frame->total_samples = 0; + frame->ofl_type = 0; + return 0; +} + int APS5_VABI sceAjmBatchInitialize(void* buffer, size_t size, AjmBatchInfo* info) { if (!buffer || !info) return SCE_AJM_ERROR_INVALID_PARAMETER; info->p_buffer = buffer; diff --git a/core/libs/tests/GuestAjm.cpp b/core/libs/tests/GuestAjm.cpp index f262079b3..590e6b9ab 100644 --- a/core/libs/tests/GuestAjm.cpp +++ b/core/libs/tests/GuestAjm.cpp @@ -11,6 +11,7 @@ extern "C" { int APS5_VABI sceAjmInitialize(std::int64_t, std::uint32_t*); int APS5_VABI sceAjmFinalize(std::uint32_t); int APS5_VABI sceAjmDecAt9ParseConfigData(const void*, AjmDecAt9ConfigDataInfo*); +int APS5_VABI sceAjmDecMp3ParseFrame(const std::uint8_t*, std::uint32_t, int, AjmDecMp3ParseFrame*); int APS5_VABI sceAjmInstanceCreate(std::uint32_t, std::uint32_t, std::uint64_t, std::uint32_t*); int APS5_VABI sceAjmInstanceDestroy(std::uint32_t, std::uint32_t); int APS5_VABI sceAjmBatchInitialize(void*, std::size_t, AjmBatchInfo*); @@ -148,6 +149,52 @@ void TestOpus(std::uint32_t context) { Require(sceAjmInstanceDestroy(context, instance) == 0); } +bool ParsesTo(const std::uint8_t* stream, std::uint32_t streamSize, std::uint64_t frameSize, std::uint32_t channels, std::uint32_t samples, std::uint32_t bitrate, std::uint32_t sampleRate) { + AjmDecMp3ParseFrame frame; + std::memset(&frame, 0xFF, sizeof(frame)); + if (sceAjmDecMp3ParseFrame(stream, streamSize, 0, &frame) != 0) return false; + return frame.frame_size == frameSize && frame.num_channels == channels && frame.samples_per_channel == samples && frame.bitrate == bitrate && frame.sample_rate == sampleRate && + frame.encoder_delay == 0 && frame.num_frames == 0 && frame.total_samples == 0 && frame.ofl_type == 0; +} + +void TestMp3ParseFrame() { + constexpr int invalidParameter = static_cast(0x80930005); + static_assert(sizeof(AjmDecMp3ParseFrame) == 40); + for (std::size_t offset = 0; offset < sizeof(MP3_MONO); offset += 96) { + Require(ParsesTo(MP3_MONO + offset, static_cast(sizeof(MP3_MONO) - offset), 96, 1, 1152, 32000, 48000)); + } + + const std::uint8_t mpeg1StereoPadded[4] = {0xFF, 0xFB, 0x92, 0x00}; + Require(ParsesTo(mpeg1StereoPadded, 4, 418, 2, 1152, 128000, 44100)); + const std::uint8_t mpeg1Protected[4] = {0xFF, 0xFA, 0xE8, 0x40}; + Require(ParsesTo(mpeg1Protected, 4, 1440, 2, 1152, 320000, 32000)); + const std::uint8_t mpeg2Mono[4] = {0xFF, 0xF3, 0x80, 0xC0}; + Require(ParsesTo(mpeg2Mono, 4, 208, 1, 576, 64000, 22050)); + const std::uint8_t mpeg2DualChannel[4] = {0xFF, 0xF3, 0xE8, 0x80}; + Require(ParsesTo(mpeg2DualChannel, 4, 720, 2, 576, 160000, 16000)); + const std::uint8_t mpeg25Mono[4] = {0xFF, 0xE3, 0x88, 0xC0}; + Require(ParsesTo(mpeg25Mono, 4, 576, 1, 576, 64000, 8000)); + const std::uint8_t mpeg25StereoPadded[4] = {0xFF, 0xE3, 0x12, 0x00}; + Require(ParsesTo(mpeg25StereoPadded, 4, 53, 2, 576, 8000, 11025)); + + AjmDecMp3ParseFrame frame{}; + Require(sceAjmDecMp3ParseFrame(nullptr, 4, 0, &frame) == invalidParameter); + Require(sceAjmDecMp3ParseFrame(MP3_MONO, 4, 0, nullptr) == invalidParameter); + Require(sceAjmDecMp3ParseFrame(MP3_MONO, 3, 0, &frame) == invalidParameter); + const std::uint8_t brokenSync[4] = {0xFF, 0xDB, 0x14, 0xC4}; + Require(sceAjmDecMp3ParseFrame(brokenSync, 4, 0, &frame) == invalidParameter); + const std::uint8_t reservedVersion[4] = {0xFF, 0xEB, 0x14, 0xC4}; + Require(sceAjmDecMp3ParseFrame(reservedVersion, 4, 0, &frame) == invalidParameter); + const std::uint8_t freeBitrate[4] = {0xFF, 0xFB, 0x04, 0xC4}; + Require(sceAjmDecMp3ParseFrame(freeBitrate, 4, 0, &frame) == invalidParameter); + const std::uint8_t forbiddenBitrate[4] = {0xFF, 0xFB, 0xF4, 0xC4}; + Require(sceAjmDecMp3ParseFrame(forbiddenBitrate, 4, 0, &frame) == invalidParameter); + const std::uint8_t reservedSampleRate[4] = {0xFF, 0xFB, 0x1C, 0xC4}; + Require(sceAjmDecMp3ParseFrame(reservedSampleRate, 4, 0, &frame) == invalidParameter); + const std::uint8_t mpeg25Above64Kbps[4] = {0xFF, 0xE3, 0x98, 0xC0}; + Require(sceAjmDecMp3ParseFrame(mpeg25Above64Kbps, 4, 0, &frame) == invalidParameter); +} + } int main() { @@ -164,6 +211,7 @@ int main() { const std::uint8_t badHeader[4] = {0xFD, 0x72, 0x1F, 0xF0}; Require(sceAjmDecAt9ParseConfigData(badHeader, &info) == invalidParameter); Require(sceAjmDecAt9ParseConfigData(nullptr, &info) == invalidParameter); + TestMp3ParseFrame(); TestMp3(context); TestOpus(context); Require(sceAjmFinalize(context) == 0); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 9fe00e510..1aea9327d 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -51,6 +51,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceAvPlayerStartEx](../../core/libs/prx/libSceAvPlayer/Export.cpp) (libSceAvPlayer) - start info layout unknown; it is ignored and playback starts as with `sceAvPlayerStart` - [sceAvPlayerInit / sceAvPlayerInitEx](../../core/libs/prx/libSceAvPlayer/src/Player.cpp) (libSceAvPlayer) - behaviour without a memory replacement unknown; frame and sample buffers then come from the guest heap - [SceAvPlayerVideoEx](../../core/libs/SceTypes.hpp) (libSceAvPlayer) - frame rate field and encoding unknown; it is left zero in frame and stream info +- [sceAjmDecMp3ParseFrame](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - signature, result layout and rate tables taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation of the PS4 library, not confirmed on a PS5 title: the layer field is not checked and MPEG-2.5 stops at 64 kbps - [AJM Opus decoder](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native, codec 24) - the initialize parameter layout (`u32 channels, u32 sample rate, u32`, seen as `2, 48000, 0`) and the little-endian `u16` byte count before each packet come from one title (The Smurfs Dreams); only 48000 Hz and a zero third word are accepted ### Functional @@ -68,5 +69,6 @@ Throughout the project, every function at every stage either **does exactly what - `--to-intel` replaces the approximate VEX.128 register forms of VRSQRTPS and VRCPPS with [correctly rounded](../../core/relinker/codegen/src/x86/ReciprocalLowering.cpp) 1/sqrt(x) and 1/x, not with the AMD approximation: its tables are not public. Intel's approximation of 1/sqrt(1.0) is 0x3F7FF000, and two Newton-Raphson steps on it settle on 0.99999994 instead of 1.0; a renormalised identity quaternion then turns into a NaN axis in PPSA21564, which the console does not show. Refined results now reach the exact value; unrefined ones can still differ from the console in the low bits. Each site costs an out-of-line stub (two jumps, a spill below the red zone, SQRTPS/DIVPS). The 256-bit, scalar, legacy and memory forms keep the native instruction, as does a 4-byte site with no movable instruction after it for the jump (5 sites in PPSA21564). - [sceKeyboardGetKey2Char](../../core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp) (libSceKeyboard) translates only the 101-key (US) arrangement and throws for the 106-key (Japanese) one; Ctrl and Alt do not change the character. - [libSceAudiodec](../../core/libs/prx/libSceAudiodec/Export.cpp) throws for the 24-bit PCM word size (`iBwPcm` 0): its sample layout is unknown. ATRAC9 decoding is covered only by configuration and error tests, as no ATRAC9 encoder is available for a fixture. +- [sceAjmDecMp3ParseFrame](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) throws when the original file length is requested (`parse_ofl` non-zero): reading it from the frame's Xing/Info, VBRI or Fraunhofer data is not implemented. - [libScePngEnc](../../core/libs/prx/libScePngEnc/Export.cpp) honours `filter_type` only as all filters (adaptive) or a single filter: the [PNG encoder](../../core/Decoder/Png/src/Png.cpp) (stb) cannot restrict adaptive filtering to a subset, so the first filter in the mask is used. - [libScePlayerInvitationDialog](../../core/libs/prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.cpp) simulates dialog completion without displaying UI or sending invitations; its parameter ABI remains unverified. From 7a4e1a22458dcc94feb087d34d36fc33e24154d8 Mon Sep 17 00:00:00 2001 From: DotDebian Date: Mon, 5 Oct 2026 01:30:17 +0200 Subject: [PATCH 023/164] fix(shader): give the a16 bias of image samples its own address dword With a16 the bias is a half that occupies a full address dword and the coordinates start in the next one. The layout packed the first coordinate into the upper half of the bias dword, so image_sample_b, image_sample_b_cl, image_sample_b_o and the biased gathers read their coordinates from the wrong halves. Co-Authored-By: Claude Opus 5.5 --- .../tests/execution/ImageSampleBiasA16.cpp | 189 ++++++++++++++++++ .../RdnaDecoder/src/RdnaImageOpDecoder.cpp | 6 +- 2 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleBiasA16.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleBiasA16.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleBiasA16.cpp new file mode 100644 index 000000000..ec41e7e3f --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleBiasA16.cpp @@ -0,0 +1,189 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#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 Words = 32; +constexpr std::uint32_t ResultWord = 8; +constexpr std::uint32_t Size = 16; +constexpr std::uint32_t Levels = 5; +constexpr std::uint32_t Clamps = 4; +constexpr std::uint32_t Format8888UNorm = 56; +constexpr std::uint32_t Type2D = 9; +constexpr std::uint32_t MinLodCapability = 42; +alignas(256) std::array Buffer{}; +alignas(256) std::array Texels{}; + +alignas(256) constexpr std::array BiasCode{ + 0x34020087, 0xe0301000, 0x80000501, 0xe0301004, 0x80000601, 0xbf8c3f70, 0xf0940f08, 0x40610c05, + 0xe0701020, 0x80000c01, 0xe0701024, 0x80000d01, 0xe0701028, 0x80000e01, 0xe070102c, 0x80000f01, + 0x7e0802ff, 0x00000201, 0xf0d40f08, 0x40610c04, 0xe0701030, 0x80000c01, 0xe0701034, 0x80000d01, + 0xe0701038, 0x80000e01, 0xe070103c, 0x80000f01, 0x7e0802ff, 0x00003e3f, 0xf0d40f08, 0x40610c04, + 0xe0701040, 0x80000c01, 0xe0701044, 0x80000d01, 0xe0701048, 0x80000e01, 0xe070104c, 0x80000f01, + 0xbf810000, +}; + +alignas(256) constexpr std::array BiasClampCode{ + 0x34020087, 0xe0301000, 0x80000501, 0xe0301004, 0x80000601, 0xe0301008, 0x80000701, 0xbf8c3f70, + 0xf0980f08, 0x40610c05, 0xe0701050, 0x80000c01, 0xe0701054, 0x80000d01, 0xe0701058, 0x80000e01, + 0xe070105c, 0x80000f01, 0xbf810000, +}; + +struct Sample { + std::uint32_t x; + std::uint32_t y; + std::uint32_t clamp; +}; + +Sample SampleOf(std::uint32_t tid) { + return {(tid * 7u + 3u) % Size, (tid * 5u + 1u) % Size, (tid / 3u) % Clamps}; +} + +std::uint32_t Half(float value) { + if (value == 0.0f) { + return 0u; + } + const auto bits = std::bit_cast(value); + return (((bits >> 23u) & 0xffu) - 112u) << 10u | ((bits >> 13u) & 0x3ffu); +} + +std::uint32_t Coordinate(std::uint32_t texel) { + return Half((static_cast(texel) + 0.5f) / static_cast(Size)); +} + +void FillInput() { + Buffer.fill(0xdeadbeefu); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto sample = SampleOf(tid); + auto* words = &Buffer[tid * Words]; + words[0] = Half(0.0f) | (Coordinate((sample.x + 5u) % Size) << 16u); + words[1] = Coordinate(sample.x) | (Coordinate(sample.y) << 16u); + words[2] = Half(static_cast(sample.clamp)) | (Half(4.0f) << 16u); + } +} + +void FillTexture() { + const auto mips = AgcDriver::Graphics::ComputeMipLayout(AgcDriver::Graphics::TextureTileMode::kLinear, Format8888UNorm, Size, Size, Levels); + Require(AgcDriver::Graphics::ComputeSurfaceSize(mips, 1) <= Texels.size(), "image sample a16 bias: the mip chain does not fit the texel storage"); + Texels.fill(0xeeu); + for (std::uint32_t level = 0; level < Levels; ++level) { + const auto& mip = mips[level]; + for (std::uint32_t y = 0; y < mip.height; ++y) { + for (std::uint32_t x = 0; x < mip.width; ++x) { + auto* texel = &Texels[mip.tiledOffset + static_cast(y) * mip.pitchBytes + x * 4u]; + texel[0] = static_cast(level); + texel[1] = static_cast(x); + texel[2] = static_cast(y); + texel[3] = 255u; + } + } + } +} + +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::array TextureDescriptor(const void* data) { + const auto address = static_cast(reinterpret_cast(data)); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (((Size - 1u) & 3u) << 30u), + ((Size - 1u) >> 2u) | ((Size - 1u) << 14u), + 0xfacu | ((Levels - 1u) << 16u) | (Type2D << 28u), + 0u, + (Levels - 1u) << 4u, + 0u, + 0u, + }; +} + +std::array SamplerDescriptor() { + return {0u, 0xfffu << 12u, 1u << 26u, 0u}; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code) { + std::vector userData(16, 0u); + const auto buffer = BufferDescriptor(Buffer.data(), static_cast(Buffer.size() * 4u)); + const auto texture = TextureDescriptor(Texels.data()); + const auto sampler = SamplerDescriptor(); + std::copy(buffer.begin(), buffer.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + std::copy(sampler.begin(), sampler.end(), userData.begin() + 12); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +std::uint32_t Wrap(std::uint32_t coordinate, std::int32_t offset, std::uint32_t size) { + const auto extent = static_cast(size); + return static_cast(((static_cast(coordinate) + offset) % extent + extent) % extent); +} + +void Check(bool clamped) { + constexpr std::array names{"image_sample_b a16", "image_sample_b_o a16 (1, 2)", "image_sample_b_o a16 (-1, -2)", "image_sample_b_cl a16"}; + constexpr std::array, 4> offsets{{{0, 0}, {1, 2}, {-1, -2}, {0, 0}}}; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto sample = SampleOf(tid); + const std::array levels{0u, 0u, 0u, sample.clamp}; + for (std::uint32_t index = 0; index < (clamped ? 4u : 3u); ++index) { + const std::uint32_t level = levels[index]; + const std::uint32_t size = Size >> level; + const std::array expected{level, Wrap(sample.x >> level, offsets[index][0], size), Wrap(sample.y >> level, offsets[index][1], size), 255u}; + for (std::uint32_t component = 0; component < 4u; ++component) { + const float value = std::bit_cast(Buffer[tid * Words + ResultWord + index * 4u + component]) * 255.0f; + Require(std::lround(value) == static_cast(expected[component]), std::string(names[index]) + ": thread " + std::to_string(tid) + " component " + std::to_string(component) + " is " + std::to_string(value) + ", expected " + std::to_string(expected[component])); + } + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const auto capabilities = device->Target().supportedCapabilities; + const bool clamped = std::find(capabilities.begin(), capabilities.end(), MinLodCapability) != capabilities.end(); + FillInput(); + FillTexture(); + Run(*device, BiasCode); + if (clamped) { + Run(*device, BiasClampCode); + } + Check(clamped); + std::puts(clamped ? "image sample a16 bias tests passed" : "image sample a16 bias tests passed, image_sample_b_cl skipped without shaderResourceMinLod"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp index e749c8d24..58983dc6f 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp @@ -159,6 +159,10 @@ std::uint32_t componentWidth(std::uint32_t flags, std::uint32_t component) { return 16; } +bool isBiasComponent(std::uint32_t flags, std::uint32_t component) { + return (flags & RdnaImageSampleFlagBias) != 0u && component == ((flags & RdnaImageSampleFlagOffset) != 0u ? 1u : 0u); +} + RdnaImageDimension decodeDimension(std::uint32_t dimension) { switch (dimension) { case 0: return RdnaImageDimension::Dim1D; @@ -238,7 +242,7 @@ RdnaImageAddressComponent GetRdnaImageAddressComponentLayout(std::uint32_t flags if (index == component) { return {offset, width}; } - offset += width; + offset += isBiasComponent(flags, index) ? 32u : width; } throw std::runtime_error("invalid image address component"); } From 7907e876d64945d5428899f0d92f6560e1bf903c Mon Sep 17 00:00:00 2001 From: Chad Date: Sun, 4 Oct 2026 18:46:54 -0500 Subject: [PATCH 024/164] test(shader): order the contended LDS 64-bit atomics with barriers On a device whose subgroups are narrower than the wave, the recompiled wave runs as several subgroups, so one subgroup's zeroing write could land after another's atomics, and a read could run before another subgroup's atomics. An s_barrier after the zeroing writes and another after the atomics order them on any subgroup width. Co-Authored-By: Claude Opus 5.5 --- .../tests/execution/LdsAtomics64.cpp | 100 +++++++++--------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/LdsAtomics64.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LdsAtomics64.cpp index 4d05f27af..5d9e2bc7e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/LdsAtomics64.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LdsAtomics64.cpp @@ -23,7 +23,7 @@ constexpr std::uint32_t ReturningOperations = 18; alignas(256) std::array Input{}; alignas(256) std::array Output{}; -alignas(256) constexpr std::array Code{ +alignas(256) constexpr std::array Code{ 0x34020085, 0x34060089, 0x34040083, 0x7ef80280, 0x7efa0280, 0x7efc0280, 0xe0301000, 0x80000401, 0xe0301004, 0x80000501, 0xe0301008, 0x80000601, 0xe030100c, 0x80000701, 0xe0301010, 0x80000801, 0xe0301014, 0x80000901, 0xbf8c3f70, 0xd9340000, 0x00000402, 0xd9340100, 0x00000402, 0xd9340200, @@ -35,55 +35,55 @@ alignas(256) constexpr std::array Code{ 0x00000402, 0xd9341700, 0x00000402, 0xd9341800, 0x00000402, 0xd9341900, 0x00000402, 0xd9341a00, 0x00000402, 0xd9341b00, 0x00000402, 0xd9341c00, 0x00000402, 0xd9341d00, 0x00000402, 0xd9341e00, 0x00000402, 0xd9341f00, 0x00000402, 0xd9342000, 0x00000402, 0xd9342100, 0x00000402, 0xd9342200, - 0x00000402, 0xd9342300, 0x00007c7e, 0xd9342308, 0x00007c7e, 0xbf8cc07f, 0xd9000000, 0x00000602, - 0xd9040100, 0x00000602, 0xd9080200, 0x00000602, 0xd90c0300, 0x00000602, 0xd9100400, 0x00000602, - 0xd9140500, 0x00000602, 0xd9180600, 0x00000602, 0xd91c0700, 0x00000602, 0xd9200800, 0x00000602, - 0xd9240900, 0x00000602, 0xd9280a00, 0x00000602, 0xd92c0b00, 0x00000602, 0xd9300c00, 0x00080602, - 0xd9400d00, 0x00080602, 0xd9440e00, 0x00080602, 0xd9480f00, 0x00000602, 0xd94c1000, 0x00000602, - 0xd9801100, 0x0a000602, 0xd9841200, 0x0c000602, 0xd9881300, 0x0e000602, 0xd98c1400, 0x10000602, - 0xd9901500, 0x12000602, 0xd9941600, 0x14000602, 0xd9981700, 0x16000602, 0xd99c1800, 0x18000602, - 0xd9a01900, 0x1a000602, 0xd9a41a00, 0x1c000602, 0xd9a81b00, 0x1e000602, 0xd9ac1c00, 0x20000602, - 0xd9b01d00, 0x22080602, 0xd9b41e00, 0x24000602, 0xd9c01f00, 0x26080602, 0xd9c42000, 0x28080602, - 0xd9c82100, 0x2a000602, 0xd9cc2200, 0x2c000602, 0xd9802300, 0x2e00067e, 0xd9a02308, 0x3000087e, - 0xbf8cc07f, 0xd9d80000, 0x32000002, 0xd9d80100, 0x34000002, 0xd9d80200, 0x36000002, 0xd9d80300, - 0x38000002, 0xd9d80400, 0x3a000002, 0xd9d80500, 0x3c000002, 0xd9d80600, 0x3e000002, 0xd9d80700, - 0x40000002, 0xd9d80800, 0x42000002, 0xd9d80900, 0x44000002, 0xd9d80a00, 0x46000002, 0xd9d80b00, - 0x48000002, 0xd9d80c00, 0x4a000002, 0xd9d80d00, 0x4c000002, 0xd9d80e00, 0x4e000002, 0xd9d80f00, - 0x50000002, 0xd9d81000, 0x52000002, 0xd9d81100, 0x54000002, 0xd9d81200, 0x56000002, 0xd9d81300, - 0x58000002, 0xd9d81400, 0x5a000002, 0xd9d81500, 0x5c000002, 0xd9d81600, 0x5e000002, 0xd9d81700, - 0x60000002, 0xd9d81800, 0x62000002, 0xd9d81900, 0x64000002, 0xd9d81a00, 0x66000002, 0xd9d81b00, - 0x68000002, 0xd9d81c00, 0x6a000002, 0xd9d81d00, 0x6c000002, 0xd9d81e00, 0x6e000002, 0xd9d81f00, - 0x70000002, 0xd9d82000, 0x72000002, 0xd9d82100, 0x74000002, 0xd9d82200, 0x76000002, 0xd9d82300, - 0x7800007e, 0xd9d82308, 0x7a00007e, 0xbf8cc07f, 0xe0701000, 0x80013203, 0xe0701004, 0x80013303, - 0xe0701008, 0x80013403, 0xe070100c, 0x80013503, 0xe0701010, 0x80013603, 0xe0701014, 0x80013703, - 0xe0701018, 0x80013803, 0xe070101c, 0x80013903, 0xe0701020, 0x80013a03, 0xe0701024, 0x80013b03, - 0xe0701028, 0x80013c03, 0xe070102c, 0x80013d03, 0xe0701030, 0x80013e03, 0xe0701034, 0x80013f03, - 0xe0701038, 0x80014003, 0xe070103c, 0x80014103, 0xe0701040, 0x80014203, 0xe0701044, 0x80014303, - 0xe0701048, 0x80014403, 0xe070104c, 0x80014503, 0xe0701050, 0x80014603, 0xe0701054, 0x80014703, - 0xe0701058, 0x80014803, 0xe070105c, 0x80014903, 0xe0701060, 0x80014a03, 0xe0701064, 0x80014b03, - 0xe0701068, 0x80014c03, 0xe070106c, 0x80014d03, 0xe0701070, 0x80014e03, 0xe0701074, 0x80014f03, - 0xe0701078, 0x80015003, 0xe070107c, 0x80015103, 0xe0701080, 0x80015203, 0xe0701084, 0x80015303, - 0xe0701088, 0x80015403, 0xe070108c, 0x80015503, 0xe0701090, 0x80015603, 0xe0701094, 0x80015703, - 0xe0701098, 0x80015803, 0xe070109c, 0x80015903, 0xe07010a0, 0x80015a03, 0xe07010a4, 0x80015b03, - 0xe07010a8, 0x80015c03, 0xe07010ac, 0x80015d03, 0xe07010b0, 0x80015e03, 0xe07010b4, 0x80015f03, - 0xe07010b8, 0x80016003, 0xe07010bc, 0x80016103, 0xe07010c0, 0x80016203, 0xe07010c4, 0x80016303, - 0xe07010c8, 0x80016403, 0xe07010cc, 0x80016503, 0xe07010d0, 0x80016603, 0xe07010d4, 0x80016703, - 0xe07010d8, 0x80016803, 0xe07010dc, 0x80016903, 0xe07010e0, 0x80016a03, 0xe07010e4, 0x80016b03, - 0xe07010e8, 0x80016c03, 0xe07010ec, 0x80016d03, 0xe07010f0, 0x80016e03, 0xe07010f4, 0x80016f03, - 0xe07010f8, 0x80017003, 0xe07010fc, 0x80017103, 0xe0701100, 0x80017203, 0xe0701104, 0x80017303, - 0xe0701108, 0x80017403, 0xe070110c, 0x80017503, 0xe0701110, 0x80017603, 0xe0701114, 0x80017703, - 0xe0701118, 0x80017803, 0xe070111c, 0x80017903, 0xe0701120, 0x80017a03, 0xe0701124, 0x80017b03, - 0xe0701128, 0x80010a03, 0xe070112c, 0x80010b03, 0xe0701130, 0x80010c03, 0xe0701134, 0x80010d03, - 0xe0701138, 0x80010e03, 0xe070113c, 0x80010f03, 0xe0701140, 0x80011003, 0xe0701144, 0x80011103, - 0xe0701148, 0x80011203, 0xe070114c, 0x80011303, 0xe0701150, 0x80011403, 0xe0701154, 0x80011503, - 0xe0701158, 0x80011603, 0xe070115c, 0x80011703, 0xe0701160, 0x80011803, 0xe0701164, 0x80011903, - 0xe0701168, 0x80011a03, 0xe070116c, 0x80011b03, 0xe0701170, 0x80011c03, 0xe0701174, 0x80011d03, - 0xe0701178, 0x80011e03, 0xe070117c, 0x80011f03, 0xe0701180, 0x80012003, 0xe0701184, 0x80012103, - 0xe0701188, 0x80012203, 0xe070118c, 0x80012303, 0xe0701190, 0x80012403, 0xe0701194, 0x80012503, - 0xe0701198, 0x80012603, 0xe070119c, 0x80012703, 0xe07011a0, 0x80012803, 0xe07011a4, 0x80012903, - 0xe07011a8, 0x80012a03, 0xe07011ac, 0x80012b03, 0xe07011b0, 0x80012c03, 0xe07011b4, 0x80012d03, - 0xe07011b8, 0x80012e03, 0xe07011bc, 0x80012f03, 0xe07011c0, 0x80013003, 0xe07011c4, 0x80013103, - 0xbf810000, + 0x00000402, 0xd9342300, 0x00007c7e, 0xd9342308, 0x00007c7e, 0xbf8cc07f, 0xbf8a0000, 0xd9000000, + 0x00000602, 0xd9040100, 0x00000602, 0xd9080200, 0x00000602, 0xd90c0300, 0x00000602, 0xd9100400, + 0x00000602, 0xd9140500, 0x00000602, 0xd9180600, 0x00000602, 0xd91c0700, 0x00000602, 0xd9200800, + 0x00000602, 0xd9240900, 0x00000602, 0xd9280a00, 0x00000602, 0xd92c0b00, 0x00000602, 0xd9300c00, + 0x00080602, 0xd9400d00, 0x00080602, 0xd9440e00, 0x00080602, 0xd9480f00, 0x00000602, 0xd94c1000, + 0x00000602, 0xd9801100, 0x0a000602, 0xd9841200, 0x0c000602, 0xd9881300, 0x0e000602, 0xd98c1400, + 0x10000602, 0xd9901500, 0x12000602, 0xd9941600, 0x14000602, 0xd9981700, 0x16000602, 0xd99c1800, + 0x18000602, 0xd9a01900, 0x1a000602, 0xd9a41a00, 0x1c000602, 0xd9a81b00, 0x1e000602, 0xd9ac1c00, + 0x20000602, 0xd9b01d00, 0x22080602, 0xd9b41e00, 0x24000602, 0xd9c01f00, 0x26080602, 0xd9c42000, + 0x28080602, 0xd9c82100, 0x2a000602, 0xd9cc2200, 0x2c000602, 0xd9802300, 0x2e00067e, 0xd9a02308, + 0x3000087e, 0xbf8cc07f, 0xbf8a0000, 0xd9d80000, 0x32000002, 0xd9d80100, 0x34000002, 0xd9d80200, + 0x36000002, 0xd9d80300, 0x38000002, 0xd9d80400, 0x3a000002, 0xd9d80500, 0x3c000002, 0xd9d80600, + 0x3e000002, 0xd9d80700, 0x40000002, 0xd9d80800, 0x42000002, 0xd9d80900, 0x44000002, 0xd9d80a00, + 0x46000002, 0xd9d80b00, 0x48000002, 0xd9d80c00, 0x4a000002, 0xd9d80d00, 0x4c000002, 0xd9d80e00, + 0x4e000002, 0xd9d80f00, 0x50000002, 0xd9d81000, 0x52000002, 0xd9d81100, 0x54000002, 0xd9d81200, + 0x56000002, 0xd9d81300, 0x58000002, 0xd9d81400, 0x5a000002, 0xd9d81500, 0x5c000002, 0xd9d81600, + 0x5e000002, 0xd9d81700, 0x60000002, 0xd9d81800, 0x62000002, 0xd9d81900, 0x64000002, 0xd9d81a00, + 0x66000002, 0xd9d81b00, 0x68000002, 0xd9d81c00, 0x6a000002, 0xd9d81d00, 0x6c000002, 0xd9d81e00, + 0x6e000002, 0xd9d81f00, 0x70000002, 0xd9d82000, 0x72000002, 0xd9d82100, 0x74000002, 0xd9d82200, + 0x76000002, 0xd9d82300, 0x7800007e, 0xd9d82308, 0x7a00007e, 0xbf8cc07f, 0xe0701000, 0x80013203, + 0xe0701004, 0x80013303, 0xe0701008, 0x80013403, 0xe070100c, 0x80013503, 0xe0701010, 0x80013603, + 0xe0701014, 0x80013703, 0xe0701018, 0x80013803, 0xe070101c, 0x80013903, 0xe0701020, 0x80013a03, + 0xe0701024, 0x80013b03, 0xe0701028, 0x80013c03, 0xe070102c, 0x80013d03, 0xe0701030, 0x80013e03, + 0xe0701034, 0x80013f03, 0xe0701038, 0x80014003, 0xe070103c, 0x80014103, 0xe0701040, 0x80014203, + 0xe0701044, 0x80014303, 0xe0701048, 0x80014403, 0xe070104c, 0x80014503, 0xe0701050, 0x80014603, + 0xe0701054, 0x80014703, 0xe0701058, 0x80014803, 0xe070105c, 0x80014903, 0xe0701060, 0x80014a03, + 0xe0701064, 0x80014b03, 0xe0701068, 0x80014c03, 0xe070106c, 0x80014d03, 0xe0701070, 0x80014e03, + 0xe0701074, 0x80014f03, 0xe0701078, 0x80015003, 0xe070107c, 0x80015103, 0xe0701080, 0x80015203, + 0xe0701084, 0x80015303, 0xe0701088, 0x80015403, 0xe070108c, 0x80015503, 0xe0701090, 0x80015603, + 0xe0701094, 0x80015703, 0xe0701098, 0x80015803, 0xe070109c, 0x80015903, 0xe07010a0, 0x80015a03, + 0xe07010a4, 0x80015b03, 0xe07010a8, 0x80015c03, 0xe07010ac, 0x80015d03, 0xe07010b0, 0x80015e03, + 0xe07010b4, 0x80015f03, 0xe07010b8, 0x80016003, 0xe07010bc, 0x80016103, 0xe07010c0, 0x80016203, + 0xe07010c4, 0x80016303, 0xe07010c8, 0x80016403, 0xe07010cc, 0x80016503, 0xe07010d0, 0x80016603, + 0xe07010d4, 0x80016703, 0xe07010d8, 0x80016803, 0xe07010dc, 0x80016903, 0xe07010e0, 0x80016a03, + 0xe07010e4, 0x80016b03, 0xe07010e8, 0x80016c03, 0xe07010ec, 0x80016d03, 0xe07010f0, 0x80016e03, + 0xe07010f4, 0x80016f03, 0xe07010f8, 0x80017003, 0xe07010fc, 0x80017103, 0xe0701100, 0x80017203, + 0xe0701104, 0x80017303, 0xe0701108, 0x80017403, 0xe070110c, 0x80017503, 0xe0701110, 0x80017603, + 0xe0701114, 0x80017703, 0xe0701118, 0x80017803, 0xe070111c, 0x80017903, 0xe0701120, 0x80017a03, + 0xe0701124, 0x80017b03, 0xe0701128, 0x80010a03, 0xe070112c, 0x80010b03, 0xe0701130, 0x80010c03, + 0xe0701134, 0x80010d03, 0xe0701138, 0x80010e03, 0xe070113c, 0x80010f03, 0xe0701140, 0x80011003, + 0xe0701144, 0x80011103, 0xe0701148, 0x80011203, 0xe070114c, 0x80011303, 0xe0701150, 0x80011403, + 0xe0701154, 0x80011503, 0xe0701158, 0x80011603, 0xe070115c, 0x80011703, 0xe0701160, 0x80011803, + 0xe0701164, 0x80011903, 0xe0701168, 0x80011a03, 0xe070116c, 0x80011b03, 0xe0701170, 0x80011c03, + 0xe0701174, 0x80011d03, 0xe0701178, 0x80011e03, 0xe070117c, 0x80011f03, 0xe0701180, 0x80012003, + 0xe0701184, 0x80012103, 0xe0701188, 0x80012203, 0xe070118c, 0x80012303, 0xe0701190, 0x80012403, + 0xe0701194, 0x80012503, 0xe0701198, 0x80012603, 0xe070119c, 0x80012703, 0xe07011a0, 0x80012803, + 0xe07011a4, 0x80012903, 0xe07011a8, 0x80012a03, 0xe07011ac, 0x80012b03, 0xe07011b0, 0x80012c03, + 0xe07011b4, 0x80012d03, 0xe07011b8, 0x80012e03, 0xe07011bc, 0x80012f03, 0xe07011c0, 0x80013003, + 0xe07011c4, 0x80013103, 0xbf810000, }; constexpr std::uint64_t Sign = 0x8000000000000000ull; From 4b5baea6c41776addd5bb92bf07fe83191c952fb Mon Sep 17 00:00:00 2001 From: Chad Date: Sun, 4 Oct 2026 18:47:31 -0500 Subject: [PATCH 025/164] fix(shader): refuse v_dot2_f32_f16 without op_sel_hi on the accumulator Its meaning for the f32 accumulator is not known, like op_sel and neg_hi, and the integer dots already refuse anything but op_sel_hi set on every source. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/tests/execution/Float16Dot.cpp | 3 ++- .../shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Float16Dot.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Float16Dot.cpp index 56d54ed41..7242e910c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/Float16Dot.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Float16Dot.cpp @@ -111,7 +111,7 @@ void CheckRefused(AgcDriver::VulkanDevice& device, std::uint32_t word0, const st } catch (const std::exception& error) { refusal = error.what(); } - Require(refusal.find("v_dot2_f32_f16 accumulator op_sel and neg_hi are not implemented") != std::string::npos, what + " was not refused"); + Require(refusal.find("v_dot2_f32_f16 accumulator op_sel, op_sel_hi and neg_hi are not implemented") != std::string::npos, what + " was not refused"); } float Dot(float aLow, float aHigh, float bLow, float bHigh, float c) { @@ -152,6 +152,7 @@ int main() { Check(); CheckRefused(*device, 0xcc13600au, "op_sel on the accumulator"); CheckRefused(*device, 0xcc13440au, "neg_hi on the accumulator"); + CheckRefused(*device, 0xcc13000au, "op_sel_hi cleared on the accumulator"); std::puts("v_dot2_f32_f16 tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index cb44b5325..d36faa036 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -2200,8 +2200,8 @@ RdnaInstruction DecodeRdnaVop3p(std::uint32_t programCounter, std::span 2u) { instruction.source2 = DecodeRdnaScalarSource(src2, programCounter); } - if (instruction.op == RdnaOpcode::VDot2F32F16 && (((opSel | negHi) >> 2u) & 1u) != 0u) { - throw std::invalid_argument("VOP3P v_dot2_f32_f16 accumulator op_sel and neg_hi are not implemented"); + if (instruction.op == RdnaOpcode::VDot2F32F16 && ((((opSel | negHi) >> 2u) & 1u) != 0u || ((opSelHi >> 2u) & 1u) == 0u)) { + throw std::invalid_argument("VOP3P v_dot2_f32_f16 accumulator op_sel, op_sel_hi and neg_hi are not implemented"); } if (instruction.op == RdnaOpcode::VFmaF32 || instruction.op == RdnaOpcode::VDot2F32F16 || isMadMixF16(instruction.op) || isPackedVop3p(instruction.op) || isVop3pIntegerSaturate(instruction.op)) { instruction.destination.clamp = clamp != 0u; From 53928c712e64bb944b74d10092952f104905303a Mon Sep 17 00:00:00 2001 From: Chad Date: Sun, 4 Oct 2026 18:48:04 -0500 Subject: [PATCH 026/164] docs: record that s_getreg_b32 reads round to nearest even after s_setreg_b32 s_setreg_b32 has no effect, so a read of the MODE round fields after it wrote another mode still reports round to nearest even, the rounding the recompiled code uses. The refused HW_ID test row is named as hardware register 4, which has no name on gfx10. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/tests/execution/ScalarGetreg.cpp | 2 +- docs/dev/TechnicalDebt.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarGetreg.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarGetreg.cpp index b580923c1..ae8c61581 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarGetreg.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarGetreg.cpp @@ -92,7 +92,7 @@ int main() { Run(*device); Check(); ExpectRefused(*device, 0xb9141901u, "the MODE denormal fields"); - ExpectRefused(*device, 0xb914f804u, "HW_ID"); + ExpectRefused(*device, 0xb914f804u, "hardware register 4"); std::puts("s_getreg_b32 tests passed"); return 0; } catch (const std::exception& error) { diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 9fe00e510..cb9ee7550 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -55,7 +55,7 @@ Throughout the project, every function at every stage either **does exactly what ### Functional -- The shader recompiler [ignores `s_setreg_b32`](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp): writes to MODE (float rounding and denormal controls) and the other hardware registers have no effect. +- The shader recompiler [ignores `s_setreg_b32`](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp): writes to MODE (float rounding and denormal controls) and the other hardware registers have no effect, and `s_getreg_b32` reads of the MODE round fields report round to nearest even even after `s_setreg_b32` wrote another mode. The initial round mode from the shader's program registers is not read either. - [Shader recompilation](../../core/shader/recompiler/Recompiler.cpp) currently occurs right before it was transferred to Vulkan with caching, but should be moved to the [relinker](../../core/relinker/main.cpp) stage. For this purpose, [shader/recompiler](../../core/shader/recompiler) was written completely independently from [libs/prx](../../core/libs/prx). - [v_fma_f64](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp) is fused exactly only for normal inputs: with a subnormal input it is a separate multiply and add, which rounds twice. - The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability. From 52c13a0bde393c574fb4ed87577a14318f51cc65 Mon Sep 17 00:00:00 2001 From: DotDebian Date: Mon, 5 Oct 2026 01:49:49 +0200 Subject: [PATCH 027/164] feat(font): implement sceFontGetRenderScaledKerning Return the kerning between two character codes at the render scale of the font, the one set by sceFontSetupRenderScalePixel and sceFontSetupRenderScalePoint, and fail with NOT_BOUND_RENDERER when no renderer is bound. sceFontGetKerning keeps its behaviour and shares the FreeType lookup. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceFont/src/Render.cpp | 48 ++++++++--- .../libs/prx/libSceFont/src/Unimplemented.cpp | 5 -- core/libs/tests/GuestFont.cpp | 81 ++++++++++++++++--- docs/dev/TechnicalDebt.md | 1 + 4 files changed, 106 insertions(+), 29 deletions(-) diff --git a/core/libs/prx/libSceFont/src/Render.cpp b/core/libs/prx/libSceFont/src/Render.cpp index 74cdde106..0da94912c 100644 --- a/core/libs/prx/libSceFont/src/Render.cpp +++ b/core/libs/prx/libSceFont/src/Render.cpp @@ -81,6 +81,18 @@ int CachedColumnOffset(FontHandle handle, FontHandleNative* font, float& offset) return rc; } +void LoadKerning(FT_Face face, float scaleW, float scaleH, std::uint32_t preCode, std::uint32_t code, FontKerning* kerning) { + const FT_UInt previousGlyph = face ? FT_Get_Char_Index(face, preCode) : 0; + const FT_UInt glyph = face ? FT_Get_Char_Index(face, code) : 0; + if (!face || previousGlyph == 0 || glyph == 0) return; + const auto charW = static_cast(static_cast(scaleW * 64.0f)); + const auto charH = static_cast(static_cast(scaleH * 64.0f)); + FT_Set_Char_Size(face, charW, charH, 72, 72); + FT_Vector delta{}; + FT_Get_Kerning(face, previousGlyph, glyph, FT_KERNING_DEFAULT, &delta); + kerning->offsetX = static_cast(delta.x) / 64.0f; +} + int RenderDirectional(FontHandle fontHandle, std::uint32_t code, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* result, std::uint16_t direction) { auto* font = GetNativeFont(fontHandle); if (!font || font->magic != HANDLE_MAGIC) { @@ -175,21 +187,35 @@ int APS5_VABI sceFontGetKerning(FontHandle fontHandle, std::uint32_t preCode, st if (!font || font->magic != HANDLE_MAGIC || !AcquireFontLock(font, fontLock)) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; *kerning = {}; const FontState* state = TryGetState(fontHandle); - const FT_Face face = state ? state->face : nullptr; - const FT_UInt previousGlyph = face ? FT_Get_Char_Index(face, preCode) : 0; - const FT_UInt glyph = face ? FT_Get_Char_Index(face, code) : 0; - if (face && previousGlyph != 0 && glyph != 0) { - const auto charW = static_cast(static_cast(state->scaleW * 64.0f)); - const auto charH = static_cast(static_cast(state->scaleH * 64.0f)); - FT_Set_Char_Size(face, charW, charH, 72, 72); - FT_Vector delta{}; - FT_Get_Kerning(face, previousGlyph, glyph, FT_KERNING_DEFAULT, &delta); - kerning->offsetX = static_cast(delta.x) / 64.0f; - } + if (state) LoadKerning(state->face, state->scaleW, state->scaleH, preCode, code, kerning); ReleaseFontLock(font, fontLock); return SCE_FONT_OK; } +int APS5_VABI sceFontGetRenderScaledKerning(FontHandle fontHandle, std::uint32_t preCode, std::uint32_t code, FontKerning* kerning) { + if (!kerning) return SCE_FONT_ERROR_INVALID_PARAMETER; + *kerning = {}; + auto* font = GetNativeFont(fontHandle); + std::uint32_t fontLock = 0; + if (!font || font->magic != HANDLE_MAGIC || !AcquireFontLock(font, fontLock)) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + std::uint32_t cachedLock = 0; + if (!AcquireCachedStyleLock(font, cachedLock)) { + ReleaseFontLock(font, fontLock); + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } + int rc = SCE_FONT_ERROR_NOT_BOUND_RENDERER; + if (font->renderer) { + float scaleW = 0.0f; + float scaleH = 0.0f; + rc = StyleStateGetScalePixel(&font->cached_style.state, &scaleW, &scaleH); + const FontState* state = TryGetState(fontHandle); + if (rc == SCE_FONT_OK && state) LoadKerning(state->face, scaleW, scaleH, preCode, code, kerning); + } + ReleaseCachedStyleLock(font, cachedLock); + ReleaseFontLock(font, fontLock); + return rc; +} + int APS5_VABI sceFontRenderCharGlyphImage(FontHandle fontHandle, std::uint32_t code, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* result) { auto* font = GetNativeFont(fontHandle); std::uint32_t fontLock = 0; diff --git a/core/libs/prx/libSceFont/src/Unimplemented.cpp b/core/libs/prx/libSceFont/src/Unimplemented.cpp index 16ac20268..10960e239 100644 --- a/core/libs/prx/libSceFont/src/Unimplemented.cpp +++ b/core/libs/prx/libSceFont/src/Unimplemented.cpp @@ -122,11 +122,6 @@ int APS5_VABI sceFontGetPixelResolution() { return 0; } -int APS5_VABI sceFontGetRenderScaledKerning() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFontGetScriptLanguage() { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/tests/GuestFont.cpp b/core/libs/tests/GuestFont.cpp index 569b3aef4..de875228f 100644 --- a/core/libs/tests/GuestFont.cpp +++ b/core/libs/tests/GuestFont.cpp @@ -4,7 +4,8 @@ #include #include #include -#include +#include +#include #include extern "C" { @@ -21,6 +22,13 @@ int APS5_VABI sceFontOpenFontMemory(FontLibrary, const void*, std::uint32_t, con int APS5_VABI sceFontCloseFont(FontHandle); int APS5_VABI sceFontSetResolutionDpi(FontHandle, std::uint32_t, std::uint32_t); int APS5_VABI sceFontGetResolutionDpi(FontHandle, std::uint32_t*, std::uint32_t*); +int APS5_VABI sceFontSetScalePixel(FontHandle, float, float); +int APS5_VABI sceFontBindRenderer(FontHandle, FontRenderer); +int APS5_VABI sceFontUnbindRenderer(FontHandle); +int APS5_VABI sceFontSetupRenderScalePixel(FontHandle, float, float); +int APS5_VABI sceFontSetupRenderScalePoint(FontHandle, float, float); +int APS5_VABI sceFontGetKerning(FontHandle, std::uint32_t, std::uint32_t, FontKerning*); +int APS5_VABI sceFontGetRenderScaledKerning(FontHandle, std::uint32_t, std::uint32_t, FontKerning*); const void* APS5_VABI sceFontSelectLibraryFt(int); const void* APS5_VABI sceFontSelectRendererFt(int); } @@ -54,19 +62,21 @@ static std::vector Words(std::initializer_list values) { return out; } -static std::vector EmptyGlyphFont() { - const std::vector>> tables = { - {"glyf", Words({0, 0})}, - {"head", Words({1, 0, 1, 0, 0, 0, 0x5F0F, 0x3CF5, 0, 1000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 2, 0, 0})}, - {"hhea", Words({1, 0, 800, -200, 0, 500, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1})}, - {"hmtx", Words({500, 0})}, - {"loca", Words({0, 0})}, - {"maxp", Words({1, 0, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0})}, - }; - std::vector font = Words({1, 0, static_cast(tables.size()), 64, 2, 32}); - int offset = 12 + 16 * static_cast(tables.size()); +static std::vector BuildFont(int glyphCount, std::map> tables) { + tables["glyf"] = Words({0, 0}); + tables["head"] = Words({1, 0, 1, 0, 0, 0, 0x5F0F, 0x3CF5, 0, 1000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 2, 0, 0}); + tables["hhea"] = Words({1, 0, 800, -200, 0, 500, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1}); + tables["hmtx"] = Words({500}); + tables["hmtx"].resize(2 + 2 * static_cast(glyphCount)); + tables["loca"].assign(2 * (static_cast(glyphCount) + 1), 0); + tables["maxp"] = Words({1, 0, glyphCount, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0}); + const int tableCount = static_cast(tables.size()); + int entrySelector = 0; + while ((2 << entrySelector) <= tableCount) ++entrySelector; + std::vector font = Words({1, 0, tableCount, 16 << entrySelector, entrySelector, 16 * tableCount - (16 << entrySelector)}); + int offset = 12 + 16 * tableCount; for (const auto& table : tables) { - font.insert(font.end(), table.first, table.first + 4); + font.insert(font.end(), table.first.begin(), table.first.end()); for (const int value : {0, 0, offset >> 16, offset & 0xFFFF, 0, static_cast(table.second.size())}) Put16(font, value); offset += static_cast((table.second.size() + 3) & ~std::size_t{3}); } @@ -77,6 +87,21 @@ static std::vector EmptyGlyphFont() { return font; } +static std::vector EmptyGlyphFont() { + return BuildFont(1, {}); +} + +static std::vector KerningFont() { + return BuildFont(3, { + {"cmap", Words({0, 1, 3, 1, 0, 12, 4, 40, 0, 6, 4, 1, 2, 'A', 'V', 0xFFFF, 0, 'A', 'V', 0xFFFF, 1 - 'A', 2 - 'V', 1, 0, 0, 0})}, + {"kern", Words({0, 1, 0, 20, 1, 1, 6, 0, 0, 1, 2, -200})}, + }); +} + +static bool KerningIs(const FontKerning& kerning, float offsetX) { + return kerning.offsetX == offsetX && kerning.offsetY == 0.0f && kerning.positionX == 0.0f && kerning.positionY == 0.0f; +} + int main() { constexpr std::uint32_t SystemFontSet = 0x18070043u; const FontMemoryInterface iface{Allocate, Release, nullptr, nullptr, nullptr, nullptr}; @@ -126,6 +151,36 @@ int main() { FontRenderer renderer = nullptr; Require(sceFontCreateRenderer(&memory, sceFontSelectRendererFt(0), &renderer) == SCE_FONT_OK && renderer != nullptr); + + const std::vector kerningData = KerningFont(); + font = nullptr; + Require(sceFontOpenFontMemory(library, kerningData.data(), static_cast(kerningData.size()), nullptr, &font) == SCE_FONT_OK && font != nullptr); + Require(sceFontSetResolutionDpi(font, 144, 144) == SCE_FONT_OK); + Require(sceFontSetScalePixel(font, 100.0f, 100.0f) == SCE_FONT_OK); + FontKerning kerning{1.0f, 1.0f, 1.0f, 1.0f}; + Require(sceFontGetKerning(font, 'A', 'V', &kerning) == SCE_FONT_OK && KerningIs(kerning, -20.0f)); + kerning = {1.0f, 1.0f, 1.0f, 1.0f}; + Require(sceFontGetRenderScaledKerning(font, 'A', 'V', &kerning) == SCE_FONT_ERROR_NOT_BOUND_RENDERER && KerningIs(kerning, 0.0f)); + Require(sceFontBindRenderer(font, renderer) == SCE_FONT_OK); + Require(sceFontGetRenderScaledKerning(font, 'A', 'V', &kerning) == SCE_FONT_OK && KerningIs(kerning, -20.0f)); + Require(sceFontSetupRenderScalePixel(font, 50.0f, 50.0f) == SCE_FONT_OK); + Require(sceFontGetRenderScaledKerning(font, 'A', 'V', &kerning) == SCE_FONT_OK && KerningIs(kerning, -10.0f)); + Require(sceFontGetKerning(font, 'A', 'V', &kerning) == SCE_FONT_OK && KerningIs(kerning, -20.0f)); + Require(sceFontSetupRenderScalePoint(font, 20.0f, 20.0f) == SCE_FONT_OK); + Require(sceFontGetRenderScaledKerning(font, 'A', 'V', &kerning) == SCE_FONT_OK && KerningIs(kerning, -8.0f)); + kerning = {1.0f, 1.0f, 1.0f, 1.0f}; + Require(sceFontGetRenderScaledKerning(font, 'V', 'A', &kerning) == SCE_FONT_OK && KerningIs(kerning, 0.0f)); + kerning = {1.0f, 1.0f, 1.0f, 1.0f}; + Require(sceFontGetRenderScaledKerning(font, 'A', 'B', &kerning) == SCE_FONT_OK && KerningIs(kerning, 0.0f)); + Require(sceFontGetRenderScaledKerning(font, 'A', 'V', nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + kerning = {1.0f, 1.0f, 1.0f, 1.0f}; + Require(sceFontGetRenderScaledKerning(nullptr, 'A', 'V', &kerning) == SCE_FONT_ERROR_INVALID_FONT_HANDLE && KerningIs(kerning, 0.0f)); + kerning = {1.0f, 1.0f, 1.0f, 1.0f}; + Require(sceFontGetRenderScaledKerning(&unopened, 'A', 'V', &kerning) == SCE_FONT_ERROR_INVALID_FONT_HANDLE && KerningIs(kerning, 0.0f)); + Require(sceFontUnbindRenderer(font) == SCE_FONT_OK); + Require(sceFontGetRenderScaledKerning(font, 'A', 'V', &kerning) == SCE_FONT_ERROR_NOT_BOUND_RENDERER); + Require(sceFontCloseFont(font) == SCE_FONT_OK); + Require(sceFontDestroyRenderer(&renderer) == SCE_FONT_OK && renderer == nullptr); Require(sceFontDestroyRenderer(&renderer) == SCE_FONT_ERROR_INVALID_RENDERER); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 9fe00e510..26a96af9b 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -26,6 +26,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - the open param's first word is unknown (PPSA21564 passes 16; it is not the byte size, since the affinity mask is at offset 16); only 16 is accepted. Whether the param continues past offset 24 is also unknown - [AMPR WaitOnAddress / WaitOnCounter](../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libSceAmpr) - compare encoding assumed to be the WAIT_REG_MEM one (0 always, 1 <, 2 <=, 3 ==, 4 !=, 5 >=, 6 >) - [AMPR markers](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - signatures taken from the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation, not confirmed on a title: `sceAmprCommandBufferSetMarkerWithColor` takes the color by pointer, the push and measure variants by value. Colors are not recorded +- [sceFontGetRenderScaledKerning](../../core/libs/prx/libSceFont/src/Render.cpp) (libSceFont) - arguments, the cleared output and the bound renderer check taken from `cellFontGetRenderScaledKerning` in [RPCS3](https://github.com/RPCS3/rpcs3/blob/master/rpcs3/Emu/Cell/Modules/cellFont.cpp) (PS3 predecessor of the library), not confirmed on a title. The kerning is the `sceFontGetKerning` one at the render scale - [sceAgcSetSubmitMode](../../core/libs/prx/libSceAgc/Misc/src/Suspend.cpp) (libSceAgc) - mode values unknown; only 0 is accepted - [zARR5aCmkoY](../../core/libs/prx/libSceAgc/DcbFlow/src/Control.cpp) (libSceAgc) - unknown name, signature - [qj7QZpgr9Uw](../../core/libs/prx/libSceAgc/DcbState/src/ContextState.cpp) (libSceAgc) - unknown name From e11b4b55540a29ee2287f086a7cbee5b777b9769 Mon Sep 17 00:00:00 2001 From: DotDebian Date: Mon, 5 Oct 2026 02:02:08 +0200 Subject: [PATCH 028/164] feat(relinker): lower SHA-1 instructions with a memory operand SHA1RNDS4/NEXTE/MSG1/MSG2 with a memory source made `--to-intel` fail the whole relink. The stub now spills a scratch xmm, loads the operand into it with the unaligned load already used for SHA-256 and runs the register-form lowering. The SHA1RNDS4 immediate is read after the SIB byte and displacement. RIP-relative operands still fail: the stub runs at a different address. Co-Authored-By: Claude Opus 5.5 --- .../include/codegen/x86/Sha1Operands.hpp | 3 + .../include/codegen/x86/StubBodyBuilder.hpp | 1 + .../relinker/codegen/src/x86/Sha1Lowering.cpp | 11 ++ .../relinker/codegen/src/x86/Sha1Operands.cpp | 22 ++- .../codegen/src/x86/StubBodyBuilder.cpp | 3 +- .../codegen/tests/Amd64OnlyConverterTests.cpp | 49 +++++- .../tests/Amd64OnlyWindowsTests.cpp | 145 ++++++++++++++++++ docs/dev/TechnicalDebt.md | 2 +- 8 files changed, 220 insertions(+), 16 deletions(-) diff --git a/core/relinker/codegen/include/codegen/x86/Sha1Operands.hpp b/core/relinker/codegen/include/codegen/x86/Sha1Operands.hpp index 6f00e1d92..01faa731e 100644 --- a/core/relinker/codegen/include/codegen/x86/Sha1Operands.hpp +++ b/core/relinker/codegen/include/codegen/x86/Sha1Operands.hpp @@ -1,8 +1,10 @@ #ifndef CODEGEN_X86_SHA1OPERANDS_HPP #define CODEGEN_X86_SHA1OPERANDS_HPP +#include #include #include +#include namespace Codegen { @@ -18,6 +20,7 @@ struct Sha1Operands { std::uint8_t Destination; std::uint8_t Source; std::uint8_t Function; + std::optional Memory; }; [[nodiscard]] Sha1Operands DecodeSha1(const std::uint8_t* data, std::size_t length); diff --git a/core/relinker/codegen/include/codegen/x86/StubBodyBuilder.hpp b/core/relinker/codegen/include/codegen/x86/StubBodyBuilder.hpp index 942f7db56..692cbc6a4 100644 --- a/core/relinker/codegen/include/codegen/x86/StubBodyBuilder.hpp +++ b/core/relinker/codegen/include/codegen/x86/StubBodyBuilder.hpp @@ -25,6 +25,7 @@ struct MemoryOperand { std::uint8_t Sib; std::int32_t Displacement; bool StackBase; + std::size_t EncodedSize; }; [[nodiscard]] MemoryOperand DecodeMemoryOperand(const std::uint8_t* data, std::size_t length, std::size_t modRmOffset, std::uint8_t rex, std::vector prefixes); diff --git a/core/relinker/codegen/src/x86/Sha1Lowering.cpp b/core/relinker/codegen/src/x86/Sha1Lowering.cpp index 0c5253747..c8a4d2b5c 100644 --- a/core/relinker/codegen/src/x86/Sha1Lowering.cpp +++ b/core/relinker/codegen/src/x86/Sha1Lowering.cpp @@ -156,6 +156,17 @@ void _emitMessage2(StubBodyBuilder& body, const Sha1Operands& operands) { } void Sha1Lowering::EmitOutOfLine(StubBodyBuilder& body, const Sha1Operands& operands) const { + if (operands.Memory) { + auto loaded = operands; + loaded.Memory.reset(); + loaded.Source = loaded.Destination; + loaded.Source = _scratch<1>(loaded)[0]; + body.Spill(loaded.Source); + body.Load(loaded.Source, *operands.Memory); + EmitOutOfLine(body, loaded); + body.Restore(loaded.Source); + return; + } switch (operands.Operation) { case Sha1Operation::Rnds4: _emitRounds(body, operands); diff --git a/core/relinker/codegen/src/x86/Sha1Operands.cpp b/core/relinker/codegen/src/x86/Sha1Operands.cpp index ffcb25b82..950267e12 100644 --- a/core/relinker/codegen/src/x86/Sha1Operands.cpp +++ b/core/relinker/codegen/src/x86/Sha1Operands.cpp @@ -1,6 +1,8 @@ #include #include #include +#include +#include namespace Codegen { @@ -9,6 +11,7 @@ using namespace X64OpcodeConstants; Sha1Operands DecodeSha1(const std::uint8_t* data, const std::size_t length) { std::size_t pos = 0; std::uint8_t rex = 0; + std::vector prefixes; while (pos < length) { const std::uint8_t b = data[pos]; @@ -25,6 +28,8 @@ Sha1Operands DecodeSha1(const std::uint8_t* data, const std::size_t length) { b != PrefixSegEs && b != PrefixSegFs && b != PrefixSegGs) { break; } + if (b != PrefixLock) + prefixes.push_back(b); rex = 0; pos += 1; } @@ -46,19 +51,22 @@ Sha1Operands DecodeSha1(const std::uint8_t* data, const std::size_t length) { throw CodegenException("Not a SHA-1 instruction"); } - const std::uint8_t modrm = data[pos + 3]; + const std::size_t modRmOffset = pos + 3; + const std::uint8_t modrm = data[modRmOffset]; + std::size_t immediateOffset = modRmOffset + 1; + operands.Destination = static_cast(((modrm >> ModRmRegShift) & ModRmRegMask) | (((rex & 0x4) != 0) ? 8 : 0)); if (((modrm >> ModRmModShift) & ModRmModMask) != ModRmModRegister) { - throw CodegenException("SHA-1 instruction with a memory operand"); + operands.Memory = DecodeMemoryOperand(data, length, modRmOffset, rex, std::move(prefixes)); + immediateOffset = modRmOffset + operands.Memory->EncodedSize; + } else { + operands.Source = static_cast((modrm & ModRmRmMask) | (((rex & 0x1) != 0) ? 8 : 0)); } if (operands.Operation == Sha1Operation::Rnds4) { - if (pos + 5 > length) { + if (immediateOffset >= length) { throw CodegenException("SHA1RNDS4 truncated before its immediate"); } - operands.Function = static_cast(data[pos + 4] & 3); + operands.Function = static_cast(data[immediateOffset] & 3); } - - operands.Destination = static_cast(((modrm >> ModRmRegShift) & ModRmRegMask) | (((rex & 0x4) != 0) ? 8 : 0)); - operands.Source = static_cast((modrm & ModRmRmMask) | (((rex & 0x1) != 0) ? 8 : 0)); return operands; } diff --git a/core/relinker/codegen/src/x86/StubBodyBuilder.cpp b/core/relinker/codegen/src/x86/StubBodyBuilder.cpp index 0fef6e627..7828b9691 100644 --- a/core/relinker/codegen/src/x86/StubBodyBuilder.cpp +++ b/core/relinker/codegen/src/x86/StubBodyBuilder.cpp @@ -58,7 +58,7 @@ MemoryOperand DecodeMemoryOperand(const std::uint8_t* data, const std::size_t le if (modRmOffset >= length) throw CodegenException("Memory operand truncated before its ModRM byte"); const auto modrm = data[modRmOffset]; - MemoryOperand operand{std::move(prefixes), static_cast(rex & (kRexX | kRexB)), static_cast((modrm >> ModRmModShift) & ModRmModMask), static_cast(modrm & ModRmRmMask), 0, 0, false}; + MemoryOperand operand{std::move(prefixes), static_cast(rex & (kRexX | kRexB)), static_cast((modrm >> ModRmModShift) & ModRmModMask), static_cast(modrm & ModRmRmMask), 0, 0, false, 0}; if (operand.Mod == ModRmModRegister) throw CodegenException("Register operand where a memory operand was expected"); if (operand.Mod == ModRmModIndirect && operand.Rm == ModRmRmRipRelative) @@ -73,6 +73,7 @@ MemoryOperand DecodeMemoryOperand(const std::uint8_t* data, const std::size_t le const auto size = _displacementSize(operand.Mod, operand.Rm, operand.Sib); if (pos + size > length) throw CodegenException("Memory operand truncated in its displacement"); + operand.EncodedSize = pos + size - modRmOffset; if (size == Disp8Size) operand.Displacement = static_cast(data[pos]); for (std::size_t index = 0; size == Disp32Size && index < size; ++index) diff --git a/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp b/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp index 4bd58800d..c4fab89f4 100644 --- a/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp +++ b/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp @@ -221,9 +221,26 @@ void sha1Operands() { check({0x44, 0x0F, 0x38, 0xC8, 0xC0}, Codegen::Sha1Operation::Nexte, 8, 0, 0); check({0x0F, 0x38, 0xC9, 0xD5}, Codegen::Sha1Operation::Msg1, 2, 5, 0); check({0x2E, 0x41, 0x0F, 0x38, 0xCA, 0xCA}, Codegen::Sha1Operation::Msg2, 1, 10, 0); - requireFailure([] { const Bytes bytes = {0x0F, 0x38, 0xC9, 0x08}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA-1 memory operand was accepted"); - requireFailure([] { const Bytes bytes = {0x0F, 0x3A, 0xCC, 0x08, 0x00}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA1RNDS4 memory operand was accepted"); + const Bytes memory = {0x65, 0x67, 0x44, 0x0F, 0x3A, 0xCC, 0x54, 0x8C, 0xF0, 0xFE}; + const auto decoded = Codegen::DecodeSha1(memory.data(), memory.size()); + require(decoded.Operation == Codegen::Sha1Operation::Rnds4 && decoded.Destination == 10 && decoded.Function == 2 && decoded.Memory && decoded.Memory->Prefixes == Bytes{0x65, 0x67} && decoded.Memory->Mod == 1 && decoded.Memory->Rm == 4 && decoded.Memory->Sib == 0x8C && decoded.Memory->Displacement == -16 && decoded.Memory->StackBase, "SHA-1 memory operand was decoded incorrectly"); + const auto memoryFunction = [](const Bytes& site, const int dst, const std::int32_t displacement, const int function) { + const auto operands = Codegen::DecodeSha1(site.data(), site.size()); + require(operands.Operation == Codegen::Sha1Operation::Rnds4 && operands.Destination == dst && operands.Memory && !operands.Memory->StackBase && operands.Memory->Displacement == displacement && operands.Function == function, "SHA1RNDS4 immediate was not read after its memory operand"); + }; + memoryFunction({0x0F, 0x3A, 0xCC, 0x08, 0x03}, 1, 0, 3); + memoryFunction({0x0F, 0x3A, 0xCC, 0x50, 0x02, 0x01}, 2, 2, 1); + memoryFunction({0x0F, 0x3A, 0xCC, 0x91, 0x78, 0x56, 0x34, 0x12, 0x01}, 2, 0x12345678, 1); + memoryFunction({0x41, 0x0F, 0x3A, 0xCC, 0x14, 0x24, 0x02}, 2, 0, 2); + memoryFunction({0x0F, 0x3A, 0xCC, 0x14, 0x25, 0x78, 0x56, 0x34, 0x12, 0x03}, 2, 0x12345678, 3); + const Bytes message = {0x0F, 0x38, 0xC9, 0x08}; + const auto decodedMessage = Codegen::DecodeSha1(message.data(), message.size()); + require(decodedMessage.Operation == Codegen::Sha1Operation::Msg1 && decodedMessage.Destination == 1 && decodedMessage.Memory && decodedMessage.Memory->Mod == 0 && decodedMessage.Memory->Rm == 0, "SHA1MSG1 memory operand was decoded incorrectly"); + requireFailure([] { const Bytes bytes = {0x0F, 0x38, 0xC9, 0x15, 0, 0, 0, 0}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA-1 RIP-relative operand was accepted"); + requireFailure([] { const Bytes bytes = {0x0F, 0x3A, 0xCC, 0x15, 0, 0, 0, 0, 0x00}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA1RNDS4 RIP-relative operand was accepted"); + requireFailure([] { const Bytes bytes = {0x0F, 0x38, 0xC9, 0x54, 0x24}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "Truncated SHA-1 memory operand was accepted"); requireFailure([] { const Bytes bytes = {0x0F, 0x3A, 0xCC, 0xCA}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA1RNDS4 without its immediate was accepted"); + requireFailure([] { const Bytes bytes = {0x0F, 0x3A, 0xCC, 0x50, 0x02}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA1RNDS4 memory form without its immediate was accepted"); requireFailure([] { const Bytes bytes = {0x66, 0x0F, 0x38, 0xC8, 0xCA}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "Prefixed 0F 38 C8 was decoded as SHA-1"); requireFailure([] { const Bytes bytes = {0x0F, 0x38, 0xCC, 0xCA}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA-256 was decoded as SHA-1"); const Bytes prefixedRounds = {0x66, 0x0F, 0x3A, 0xCC, 0xCA, 0x00}; @@ -293,6 +310,12 @@ void matcherSubstitutions() { const auto sha1 = match(bytes); require(sha1 && sha1->Lowering == Codegen::Amd64OnlyLowering::Trampoline && sha1->InstructionName == name, "SHA-1 instruction was not lowered through a stub"); } + for (const auto& [bytes, name] : {std::pair{Bytes{0x0F, 0x3A, 0xCC, 0x08, 0x01}, "SHA1RNDS4"}, {Bytes{0x0F, 0x38, 0xC8, 0x48, 0x10}, "SHA1NEXTE"}, {Bytes{0x0F, 0x38, 0xC9, 0x4C, 0x24, 0x18}, "SHA1MSG1"}, {Bytes{0x45, 0x0F, 0x38, 0xCA, 0x21}, "SHA1MSG2"}}) { + const auto sha1 = match(bytes); + require(sha1 && sha1->Lowering == Codegen::Amd64OnlyLowering::Trampoline && sha1->InstructionName == name, "SHA-1 memory form was not lowered through a stub"); + } + const auto stackMessage = match({0x0F, 0x38, 0xC9, 0x4C, 0x24, 0x18}); + require(stackMessage && stackMessage->StubBody.size() > 22 && Bytes(stackMessage->StubBody.begin() + 13, stackMessage->StubBody.begin() + 22) == Bytes{0xF3, 0x0F, 0x6F, 0x84, 0x24, 0xA8, 0x00, 0x00, 0x00}, "SHA-1 stack operand was not loaded past the spilled scratch register"); const auto stub = match(kInsertqHighSite); require(stub && stub->Lowering == Codegen::Amd64OnlyLowering::Trampoline && stub->StubBody == kInsertqHighBody && stub->ReturnBranchOffset == 15 && stub->InstructionName == "INSERTQ", "INSERTQ was not lowered through a stub"); const auto topAligned = match({0x66, 0x0F, 0x78, 0xC3, 0x18, 0x28}); @@ -466,7 +489,12 @@ void converterSha1() { requireFailure([&] { (void)converter->Convert(beforeReturn, {segmentHeader(text.size())}); }, "Short SHA-1 instruction followed by a return was relocated"); auto memoryForm = file; memoryForm[0x214] = 0x28; - require(failureOffset([&] { (void)converter->Convert(memoryForm, {segmentHeader(text.size())}); }, "SHA-1 memory form was accepted") == 0x211, "SHA-1 operand failure does not carry the file offset"); + const auto memoryResult = converter->Convert(memoryForm, {segmentHeader(text.size())}); + require(memoryResult.Trampolines.size() == 3 && memoryResult.Reports[2].InstructionName == "SHA1MSG2", "SHA-1 memory form was not lowered through a stub"); + auto ripRelative = file; + const Bytes ripMessage = {0x0F, 0x38, 0xCA, 0x2D, 0x00, 0x00, 0x00, 0x00, 0xC3}; + std::copy(ripMessage.begin(), ripMessage.end(), ripRelative.begin() + 0x211); + require(failureOffset([&] { (void)converter->Convert(ripRelative, {segmentHeader(text.size() + 3)}); }, "SHA-1 RIP-relative form was accepted") == 0x211, "SHA-1 operand failure does not carry the file offset"); } void converterMonitorWait() { @@ -1030,7 +1058,9 @@ void sha1Execution() { {0x0F, 0x3A, 0xCC, 0xD2, 0x02}, {0x45, 0x0F, 0x3A, 0xCC, 0xCE, 0x00}, {0x41, 0x0F, 0x3A, 0xCC, 0xC0, 0x01}, {0x0F, 0x38, 0xC8, 0xD5}, {0x0F, 0x38, 0xC8, 0xD2}, {0x44, 0x0F, 0x38, 0xC8, 0xC7}, {0x0F, 0x38, 0xC9, 0xD5}, {0x0F, 0x38, 0xC9, 0xD2}, {0x41, 0x0F, 0x38, 0xC9, 0xC7}, - {0x0F, 0x38, 0xCA, 0xD5}, {0x0F, 0x38, 0xCA, 0xD2}, {0x45, 0x0F, 0x38, 0xCA, 0xFF}}; + {0x0F, 0x38, 0xCA, 0xD5}, {0x0F, 0x38, 0xCA, 0xD2}, {0x45, 0x0F, 0x38, 0xCA, 0xFF}, + {0x0F, 0x3A, 0xCC, 0x10, 0x02}, {0x44, 0x0F, 0x3A, 0xCC, 0x40, 0x10, 0x01}, {0x0F, 0x3A, 0xCC, 0x80, 0x10, 0x00, 0x00, 0x00, 0x00}, {0x0F, 0x3A, 0xCC, 0x44, 0x20, 0x10, 0xFF}, + {0x0F, 0x38, 0xC8, 0x00}, {0x0F, 0x38, 0xC9, 0x48, 0x10}, {0x44, 0x0F, 0x38, 0xCA, 0xB8, 0x10, 0x00, 0x00, 0x00}}; for (const auto& site : sites) { const auto operands = Codegen::DecodeSha1(site.data(), site.size()); const auto match = matcher->Match(site.data(), site.size()); @@ -1041,19 +1071,24 @@ void sha1Execution() { reg[0] = random(); reg[1] = random(); } + alignas(16) const std::uint64_t memory[4] = {random(), random(), random(), random()}; + const auto rax = operands.Memory ? reinterpret_cast(memory) : 0x5A5A5A5A5A5A5A5Aull; std::uint32_t x[4]; std::uint32_t y[4]; std::memcpy(x, xmmIn[operands.Destination], sizeof(x)); - std::memcpy(y, xmmIn[operands.Source], sizeof(y)); + if (operands.Memory) + std::memcpy(y, reinterpret_cast(memory) + operands.Memory->Displacement, sizeof(y)); + else + std::memcpy(y, xmmIn[operands.Source], sizeof(y)); const auto expected = sha1Reference(operands, x, y); - const auto run = runStubBody(*match, 0x5A5A5A5A5A5A5A5Aull, xmmIn); + const auto run = runStubBody(*match, rax, xmmIn); for (unsigned reg = 0; reg < 16; ++reg) { if (reg == operands.Destination) require(std::memcmp(run.Xmm[reg], expected.data(), 16) == 0, "SHA-1 stub computed the wrong result"); else require(run.Xmm[reg][0] == xmmIn[reg][0] && run.Xmm[reg][1] == xmmIn[reg][1], "SHA-1 stub clobbered an xmm register"); } - require(run.Rax == 0x5A5A5A5A5A5A5A5Aull && run.Rcx == 0x1122334455667788ull && (run.Flags & 0x8D5) == (0x8D7 & 0x8D5), "SHA-1 stub changed a general register or RFLAGS"); + require(run.Rax == rax && run.Rcx == 0x1122334455667788ull && (run.Flags & 0x8D5) == (0x8D7 & 0x8D5), "SHA-1 stub changed a general register or RFLAGS"); } } } diff --git a/core/relinker/elfpatcher/tests/Amd64OnlyWindowsTests.cpp b/core/relinker/elfpatcher/tests/Amd64OnlyWindowsTests.cpp index 2a48ce66d..749aa1923 100644 --- a/core/relinker/elfpatcher/tests/Amd64OnlyWindowsTests.cpp +++ b/core/relinker/elfpatcher/tests/Amd64OnlyWindowsTests.cpp @@ -344,6 +344,150 @@ void executeSha256(const Harness& harness, const Codegen::IAmd64OnlyInstructionM } } +struct Sha1Memory { + std::uint8_t ModRm; + Bytes Tail; + std::optional Slot; + const char* Name; +}; + +const std::array kSha1Memory = {{ + {0x03, {}, std::uint8_t{0}, "[rbx]"}, + {0x43, {0x70}, std::uint8_t{7}, "[rbx+0x70]"}, + {0x83, {0xF0, 0x00, 0x00, 0x00}, std::uint8_t{15}, "[rbx+0xf0]"}, + {0x44, {0x24, 0xF0}, std::nullopt, "[rsp-0x10]"}}}; + +struct Sha1Step { + std::uint8_t Opcode; + std::uint8_t Destination; + std::uint8_t Source; + std::uint8_t Immediate = 0; + bool Memory = false; +}; + +Bytes encodeSha1(const Sha1Step& step) { + Bytes bytes; + const auto rex = static_cast(0x40 | (step.Destination >= 8 ? 4 : 0) | (!step.Memory && step.Source >= 8 ? 1 : 0)); + if (rex != 0x40) bytes.push_back(rex); + bytes.insert(bytes.end(), {0x0F, static_cast(step.Opcode == 0xCC ? 0x3A : 0x38), step.Opcode}); + if (step.Memory) { + const auto& memory = kSha1Memory[step.Source]; + bytes.push_back(static_cast(memory.ModRm | ((step.Destination & 7) << 3))); + bytes.insert(bytes.end(), memory.Tail.begin(), memory.Tail.end()); + } else { + bytes.push_back(static_cast(0xC0 | ((step.Destination & 7) << 3) | (step.Source & 7))); + } + if (step.Opcode == 0xCC) bytes.push_back(step.Immediate); + return bytes; +} + +std::uint32_t rotl(const std::uint32_t value, const unsigned count) { + return (value << count) | (value >> (32 - count)); +} + +void sha1Reference(const Sha1Step& step, const State& input, std::uint8_t (&xmm)[16][16]) { + std::uint32_t x[4]; + std::uint32_t y[4]; + std::uint32_t r[4]; + std::memcpy(x, xmm[step.Destination], sizeof(x)); + if (!step.Memory) { + std::memcpy(y, xmm[step.Source], sizeof(y)); + } else if (const auto slot = kSha1Memory[step.Source].Slot) { + std::memcpy(y, input.Xmm[*slot], sizeof(y)); + } else { + const std::uint64_t canary[2] = {kCanary, kCanary}; + std::memcpy(y, canary, sizeof(y)); + } + if (step.Opcode == 0xC8) { + r[0] = y[0]; + r[1] = y[1]; + r[2] = y[2]; + r[3] = y[3] + rotl(x[3], 30); + } else if (step.Opcode == 0xC9) { + r[0] = x[0] ^ y[2]; + r[1] = x[1] ^ y[3]; + r[2] = x[2] ^ x[0]; + r[3] = x[3] ^ x[1]; + } else if (step.Opcode == 0xCA) { + r[3] = rotl(x[3] ^ y[2], 1); + r[2] = rotl(x[2] ^ y[1], 1); + r[1] = rotl(x[1] ^ y[0], 1); + r[0] = rotl(x[0] ^ r[3], 1); + } else { + constexpr std::uint32_t keys[4] = {0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6}; + const auto function = step.Immediate & 3; + std::uint32_t a = x[3], b = x[2], c = x[1], d = x[0], e = 0; + for (int round = 0; round < 4; ++round) { + std::uint32_t f = b ^ c ^ d; + if (function == 0) f = (b & c) ^ (~b & d); + if (function == 2) f = (b & c) ^ (b & d) ^ (c & d); + const auto next = f + rotl(a, 5) + y[3 - round] + e + keys[function]; + e = d; d = c; c = rotl(b, 30); b = a; a = next; + } + r[0] = d; + r[1] = c; + r[2] = b; + r[3] = a; + } + std::memcpy(xmm[step.Destination], r, sizeof(r)); +} + +std::string describeSha1(const std::vector& steps) { + std::string text; + for (const auto& step : steps) { + text += std::string(step.Opcode == 0xCC ? "sha1rnds4" : step.Opcode == 0xC8 ? "sha1nexte" : step.Opcode == 0xC9 ? "sha1msg1" : "sha1msg2") + " xmm" + std::to_string(step.Destination) + ", "; + text += step.Memory ? std::string(kSha1Memory[step.Source].Name) : "xmm" + std::to_string(step.Source); + if (step.Opcode == 0xCC) text += ", " + std::to_string(step.Immediate); + text += "; "; + } + return text; +} + +void executeSha1(const Harness& harness, const Codegen::IAmd64OnlyInstructionMatcher& matcher, const std::vector& steps, std::mt19937_64& random, const int images) { + std::vector sites; + for (const auto& step : steps) sites.push_back(encodeSha1(step)); + const std::vector> spans(sites.begin(), sites.end()); + const auto match = steps.size() == 1 ? matcher.Match(sites[0].data(), sites[0].size()) : matcher.MatchSequence(spans, {}); + require(match.has_value() && match->Lowering == Codegen::Amd64OnlyLowering::Trampoline, "SHA-1 was not lowered through a stub: " + describeSha1(steps)); + std::uint16_t written = 0; + for (const auto& step : steps) written = static_cast(written | (1u << step.Destination)); + const std::array flags = {0x202, 0x203, 0x246, 0xAC7}; + for (int image = 0; image < images; ++image) { + State input{}; + for (auto& lane : input.Xmm) for (auto& byte : lane) byte = static_cast(random()); + input.FlagsIn = flags[static_cast(image) % flags.size()]; + State state = input; + harness.Run(match->StubBody, match->ReturnBranchOffset, state); + ++g_executions; + State expected = input; + for (const auto& step : steps) sha1Reference(step, input, expected.Xmm); + for (unsigned reg = 0; reg < 16; ++reg) + if (((written >> reg) & 1) != 0) require(std::memcmp(state.Xmm[reg], expected.Xmm[reg], 16) == 0, "Lowered SHA-1 computed the wrong xmm" + std::to_string(reg) + ": " + describeSha1(steps)); + requireEnvironment(input, state, written, describeSha1(steps)); + } +} + +void sha1Execution(const Harness& harness, const Codegen::IAmd64OnlyInstructionMatcher& matcher, std::mt19937_64& random) { + const std::array opcodes = {0xCC, 0xC8, 0xC9, 0xCA}; + for (const auto opcode : opcodes) { + for (std::uint8_t dst = 0; dst < 16; ++dst) { + for (std::uint8_t src = 0; src < 16; ++src) + executeSha1(harness, matcher, {{opcode, dst, src, static_cast(random())}}, random, 2); + for (std::uint8_t memory = 0; memory < kSha1Memory.size(); ++memory) + for (std::uint8_t function = 0; function < 4; ++function) + executeSha1(harness, matcher, {{opcode, dst, memory, static_cast((random() & 0xFC) | function), true}}, random, 2); + } + } + for (int sequence = 0; sequence < 256; ++sequence) { + std::vector steps(2 + random() % 2); + for (auto& step : steps) { + const bool memory = random() % 2 == 0; + step = {opcodes[random() % opcodes.size()], static_cast(random() % 16), static_cast(random() % (memory ? kSha1Memory.size() : 16)), static_cast(random()), memory}; + } + executeSha1(harness, matcher, steps, random, 2); + } +} + void clzeroExecution(const Harness& harness, const Codegen::IAmd64OnlyInstructionMatcher& matcher, std::mt19937_64& random) { const Bytes plain = {0x0F, 0x01, 0xFC}; const Bytes addressSize32 = {0x67, 0x0F, 0x01, 0xFC}; @@ -418,6 +562,7 @@ void cpuExecution() { executeSha256(harness, *matcher, steps, random, 2); } clzeroExecution(harness, *matcher, random); + sha1Execution(harness, *matcher, random); } Bytes elfFixture(const Bytes& text) { diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 5c87a3d98..0acc50fb9 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -65,7 +65,7 @@ Throughout the project, every function at every stage either **does exactly what - [v_fma_f64](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp) is fused exactly only for normal inputs: with a subnormal input it is a separate multiply and add, which rounds twice. - The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability. - [libSceJpegEnc](../../core/libs/prx/libSceJpegEnc/Export.cpp) encodes 4:2:2 sampling requests with 4:2:0 chroma subsampling, and grayscale requests as a 3-component JPEG with neutral chroma instead of a single-component one: the [JPEG encoder](../../core/Decoder/Jpeg/src/Jpeg.cpp) (stb) only produces 3-component 4:2:0 and 4:4:4 images. -- `--to-intel` does not lower RDPRU/MCOMMIT; the [matcher](../../core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp) fails the relink instead. SHA-1 instructions with a memory operand and SHA-256 instructions with a RIP-relative operand fail the relink. +- `--to-intel` does not lower RDPRU/MCOMMIT; the [matcher](../../core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp) fails the relink instead. SHA-1 and SHA-256 instructions with a RIP-relative operand fail the relink. - The length-changing path of the [instruction rewriter](../../core/relinker/codegen/src/x86/X64InstructionRewriter.cpp) is not used by the [converter](../../core/relinker/codegen/src/Amd64OnlyConverter.cpp): it does not adjust VEX/0F38/0F3A RIP-relative operands, data-to-code references (relocations, FDEs, jump tables) or segment sizes, so every substitution keeps the instruction length. - The Linux placement of the `--to-intel` stubs in the [ELF patcher](../../core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp) is covered only by a synthetic test. - The libc [SSE4a trap emulation](../../core/libs/prx/libc/src/specifics/windows/Sse4aEmulation.hpp) on Windows is superseded by `--to-intel` and remains only until the relinked title has been verified without it. From f1d8ec891344871a4fbf6400ca1485583090699e Mon Sep 17 00:00:00 2001 From: Nicolas Abosch <88494836+nicolasabosch@users.noreply.github.com> Date: Sun, 4 Oct 2026 20:59:05 -0300 Subject: [PATCH 029/164] fix(shader): isolate wave32 operations in wide host subgroups --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 8 + .../tests/execution/PixelHelperLanes.cpp | 13 +- .../tests/execution/Wave32Subgroup.cpp | 12 +- core/libs/tests/Wave32WideSubgroup.cpp | 201 ++++++++++++++++++ .../SpirvBackend/SpirvEmitterState.hpp | 1 + .../SpirvMemory/SpirvSubgroup.hpp | 2 + .../src/SpirvAlu/SpirvAluEmitterMath.cpp | 6 +- .../SpirvBackend/src/SpirvEmitter.cpp | 7 +- .../SpirvMemory/SpirvMemoryInstructions.cpp | 3 +- .../src/SpirvMemory/SpirvSubgroup.cpp | 34 ++- 10 files changed, 272 insertions(+), 15 deletions(-) create mode 100644 core/libs/tests/Wave32WideSubgroup.cpp diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index dd97d08ab..10da8fa12 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -681,6 +681,14 @@ if(BUILD_TESTING) set_tests_properties(ray_tracing_miss PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(wave32_wide_subgroup_tests ${LIBS_INCLUDE_DIR}/tests/Wave32WideSubgroup.cpp) + target_link_libraries(wave32_wide_subgroup_tests PRIVATE shader_recompiler_agc_driver libc) + configure_windows_unwind(wave32_wide_subgroup_tests) + add_test(NAME wave32_wide_subgroup COMMAND wave32_wide_subgroup_tests) + if(WIN32) + set_tests_properties(wave32_wide_subgroup PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(vector_compare_i16_u16_tests ${LIBS_INCLUDE_DIR}/tests/VectorCompareI16U16.cpp) target_link_libraries(vector_compare_i16_u16_tests PRIVATE shader_recompiler_agc_driver libc) configure_windows_unwind(vector_compare_i16_u16_tests) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PixelHelperLanes.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelHelperLanes.cpp index 482a0766e..0660b8036 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PixelHelperLanes.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelHelperLanes.cpp @@ -53,11 +53,10 @@ std::vector> Triangles() { return vertices; } -void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize) { +void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { static const auto triangles = Triangles(); Pixels.fill(std::byte{0}); Lanes.fill(Sentinel); - const auto target = device.Target(); std::vector vertexUserData(4, 0u); const auto vertexBuffer = BufferDescriptor(triangles.data(), 16u, static_cast(triangles.size())); @@ -156,10 +155,18 @@ int main() { } for (const auto waveSize : {64u, 32u}) { for (std::uint32_t pass = 0; pass < 4; ++pass) { - Draw(*device, waveSize); + Draw(*device, waveSize, device->Target()); Check(waveSize); } } + if (device->Target().subgroupSize == 32u) { + auto wide = device->Target(); + wide.subgroupSize = 64u; + for (std::uint32_t pass = 0; pass < 4; ++pass) { + Draw(*device, 32u, wide); + Check(32u); + } + } std::puts("pixel helper lane tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Wave32Subgroup.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Wave32Subgroup.cpp index b85dda5aa..afd510b19 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/Wave32Subgroup.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Wave32Subgroup.cpp @@ -70,12 +70,22 @@ int main() { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; const auto target = device->ComputeTarget(WaveSize); - if (target.subgroupSize != WaveSize) { + if (target.subgroupSize < WaveSize) { std::printf("skipped, the device runs wave32 programs on %u-wide subgroups\n", target.subgroupSize); return VulkanTestSkipped; } Run(*device, target); Check(); + if (device->Target().subgroupSize != target.subgroupSize && device->Target().subgroupSize >= WaveSize) { + Run(*device, device->Target()); + Check(); + } + if (device->Target().subgroupSize == WaveSize) { + auto wide = device->Target(); + wide.subgroupSize = 64; + Run(*device, wide); + Check(); + } std::puts("wave32 subgroup tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/tests/Wave32WideSubgroup.cpp b/core/libs/tests/Wave32WideSubgroup.cpp new file mode 100644 index 000000000..7635e864c --- /dev/null +++ b/core/libs/tests/Wave32WideSubgroup.cpp @@ -0,0 +1,201 @@ +#include "SpirvBackend/SpirvEmitter.hpp" +#include "SpirvBackend/SpirvEmitterInstructions.hpp" +#include "SpirvBackend/SpirvMemory/SpirvSubgroup.hpp" +#include "SpirvBackend/SpirvMemory/SpirvConstants.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ShaderRecompiler; + +namespace { + +using Value = std::array; +using Lanes = std::vector; + +void Require(bool condition, const char* reason) { + if (!condition) throw std::runtime_error(reason); +} + +class Subgroup { +public: + explicit Subgroup(std::uint32_t size) : size(size) {} + + void Set(std::uint32_t id, Lanes lanes) { + values[id] = std::move(lanes); + } + + const Lanes& Get(std::uint32_t id) const { + return values.at(id); + } + + void Execute(std::span code) { + for (std::size_t offset = 5; offset < code.size();) { + const auto count = code[offset] >> 16u; + Require(count != 0 && count <= code.size() - offset, "malformed SPIR-V instruction"); + const auto words = code.subspan(offset, count); + const auto op = static_cast(words[0] & 0xffffu); + offset += count; + switch (op) { + case spv::OpTypeInt: + case spv::OpTypeBool: + case spv::OpTypeVector: + continue; + case spv::OpStore: + values[words[1]] = values.at(words[2]); + continue; + default: + break; + } + auto& result = values[words[2]]; + result.resize(size); + for (std::uint32_t lane = 0; lane < size; ++lane) { + const auto arg = [&](std::size_t index) -> const Value& { return values.at(words[index]).at(lane); }; + auto& value = result[lane]; + switch (op) { + case spv::OpConstant: value[0] = words[3]; break; + case spv::OpConstantTrue: value[0] = 1; break; + case spv::OpConstantFalse: value[0] = 0; break; + case spv::OpLoad: value = arg(3); break; + case spv::OpBitwiseAnd: value[0] = arg(3)[0] & arg(4)[0]; break; + case spv::OpBitwiseOr: value[0] = arg(3)[0] | arg(4)[0]; break; + case spv::OpBitwiseXor: value[0] = arg(3)[0] ^ arg(4)[0]; break; + case spv::OpIAdd: value[0] = arg(3)[0] + arg(4)[0]; break; + case spv::OpIMul: value[0] = arg(3)[0] * arg(4)[0]; break; + case spv::OpShiftRightLogical: value[0] = arg(3)[0] >> arg(4)[0]; break; + case spv::OpShiftLeftLogical: value[0] = arg(3)[0] << arg(4)[0]; break; + case spv::OpINotEqual: value[0] = arg(3)[0] != arg(4)[0]; break; + case spv::OpIEqual: value[0] = arg(3)[0] == arg(4)[0]; break; + case spv::OpULessThan: value[0] = arg(3)[0] < arg(4)[0]; break; + case spv::OpLogicalAnd: value[0] = arg(3)[0] && arg(4)[0]; break; + case spv::OpSelect: value = arg(3)[0] ? arg(4) : arg(5); break; + case spv::OpCompositeExtract: value[0] = arg(3).at(words[4]); break; + case spv::OpVectorExtractDynamic: value[0] = arg(3).at(arg(4)[0]); break; + case spv::OpCompositeConstruct: + for (std::size_t component = 0; component < count - 3u; ++component) value.at(component) = arg(3u + component)[0]; + break; + case spv::OpGroupNonUniformBallot: + value.fill(0); + for (std::uint32_t source = 0; source < size; ++source) { + if (values.at(words[4])[source][0]) value[source / 32u] |= 1u << (source % 32u); + } + break; + case spv::OpGroupNonUniformBallotFindLSB: + value[0] = 0xffffffffu; + for (std::uint32_t component = 0; component < 4; ++component) { + if (arg(4)[component] != 0) { + value[0] = component * 32u + std::countr_zero(arg(4)[component]); + break; + } + } + break; + case spv::OpGroupNonUniformShuffle: + Require(arg(5)[0] < size, "shuffle escaped the host subgroup"); + value = values.at(words[4]).at(arg(5)[0]); + break; + default: + throw std::runtime_error("unexpected opcode in subgroup test: " + std::to_string(op)); + } + } + } + } + +private: + std::uint32_t size; + std::unordered_map values; +}; + +void Check(std::uint32_t size, const Value& masks) { + IrProgram program; + program.SetWaveSize(32); + SpirvEmitterState state(program, {}); + state.splitSubgroup = size > 32; + state.subgroupLocalInvocationIdVariable = state.module.AllocateId(); + SpirvValueEmitContext context(state); + context.scratchU32Variable = state.module.AllocateId(); + IrValue source(IrOpcode::GetAttribute, IrType::U32, 1); + IrValue predicate(IrOpcode::GetExec, IrType::Bool, 2); + IrValue requestedLane(IrOpcode::GetAttribute, IrType::U32, 3); + IrValue first(IrOpcode::ReadFirstLane, IrType::U32, 4); + first.AddArgument(&source); + first.AddArgument(&predicate); + IrValue read(IrOpcode::ReadLane, IrType::U32, 5); + read.AddArgument(&source); + read.AddArgument(&requestedLane); + IrValue control(IrOpcode::Void, IrType::U32, 6); + control.SetImmediateU32(0x7c1fu); + IrValue swizzle(IrOpcode::SwizzleU32, IrType::U32, 7); + swizzle.AddArgument(&source); + swizzle.AddArgument(&control); + swizzle.AddArgument(&predicate); + IrValue replacement(IrOpcode::Void, IrType::U32, 8); + replacement.SetImmediateU32(0x12345678u); + IrValue writeTarget(IrOpcode::Void, IrType::U32, 9); + writeTarget.SetImmediateU32(7u); + IrValue write(IrOpcode::WriteLane, IrType::U32, 10); + write.AddArgument(&replacement); + write.AddArgument(&writeTarget); + write.AddArgument(&source); + const auto sourceId = context.Def(&source); + const auto predicateId = context.Def(&predicate); + const auto requestedId = context.Def(&requestedLane); + const auto logicalId = EmitSubgroupLocalInvocationId(state); + const auto ballot = context.Ballot(&predicate); + const auto readFirst = EmitReadFirstLane(context, first); + const auto readLane = EmitReadLane(context, read); + const auto active = EmitBallotLaneActiveBool(state, ballot, requestedId); + const auto quad = EmitDppQuadPermTargetLane(state, logicalId, 0x1bu); + const auto quadValue = context.Shuffle(read, 0, quad.lane); + const auto hardwareActive = EmitSubgroupLaneActiveBool(state, requestedId); + const auto swizzled = EmitSwizzleU32(context, swizzle); + const auto written = EmitWriteLane(context, write); + Subgroup subgroup(size); + Lanes host(size), sources(size), predicates(size), requested(size); + for (std::uint32_t lane = 0; lane < size; ++lane) { + host[lane][0] = lane; + sources[lane][0] = 0x1000u + lane; + predicates[lane][0] = (masks[lane / 32u] >> (lane % 32u)) & 1u; + requested[lane][0] = 31u - lane % 32u; + } + subgroup.Set(state.subgroupLocalInvocationIdVariable, host); + subgroup.Set(sourceId, sources); + subgroup.Set(predicateId, predicates); + subgroup.Set(requestedId, requested); + subgroup.Execute(state.module.Finalize()); + for (std::uint32_t lane = 0; lane < size; ++lane) { + const auto mask = masks[lane / 32u]; + const auto base = lane / 32u * 32u; + Require(subgroup.Get(logicalId)[lane][0] == lane % 32u, "lane id escaped its wave32"); + Require(subgroup.Get(ballot)[lane] == Value{mask, 0, 0, 0}, "ballot mixed independent wave32s"); + Require(subgroup.Get(readFirst)[lane][0] == 0x1000u + base + (mask == 0 ? 0u : std::countr_zero(mask)), "readfirstlane read a different wave32"); + Require(subgroup.Get(readLane)[lane][0] == 0x1000u + base + requested[lane][0], "readlane read a different wave32"); + Require(subgroup.Get(active)[lane][0] == ((mask >> requested[lane][0]) & 1u), "source lane activity came from another wave32"); + Require(subgroup.Get(quadValue)[lane][0] == 0x1000u + (lane & ~3u) + 3u - lane % 4u, "DPP quad permutation crossed waves"); + Require(subgroup.Get(hardwareActive)[lane][0] == 1u, "upper wave32 lanes were treated as inactive"); + Require(subgroup.Get(swizzled)[lane][0] == (((mask >> requested[lane][0]) & 1u) != 0 ? 0x1000u + base + requested[lane][0] : 0u), "DS swizzle read another wave32 or ignored source EXEC"); + Require(subgroup.Get(written)[lane][0] == (lane % 32u == 7u ? 0x12345678u : 0x1000u + lane), "writelane missed the upper wave32"); + } +} + +} + +int main() { + try { + for (std::uint32_t size : {32u, 64u, 128u}) { + for (const Value& masks : {Value{0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu}, Value{1, 0x80000000u, 0x100, 0x4000}, Value{0x55555555u, 0xaaaaaaaau, 0x12345678u, 0xfedcba98u}, Value{0, 1, 0, 0x80000000u}, Value{1, 0, 0x80000000u, 0}, Value{0, 0, 0, 0}}) { + Check(size, masks); + } + } + std::puts("wave32 wide subgroup tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp index 863dd4559..1e761cf73 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp @@ -82,6 +82,7 @@ struct SpirvEmitterState { SpirvRequirements requirements; std::uint32_t laneCount = 1; + bool splitSubgroup = false; std::unordered_set sharedLaneValues; std::uint32_t laneHalf = 0; // The target's SPIR-V version and what the device accepts, for capabilities an emitter adds diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvSubgroup.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvSubgroup.hpp index 4910b7f24..3a5bc334e 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvSubgroup.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvSubgroup.hpp @@ -7,6 +7,8 @@ namespace ShaderRecompiler { std::uint32_t EmitSubgroupLocalInvocationId(SpirvEmitterState& state); +std::uint32_t EmitHostSubgroupLane(SpirvEmitterState& state, std::uint32_t lane); +std::uint32_t EmitWaveBallot(SpirvEmitterState& state, std::uint32_t ballot); DppTargetLane EmitDppQuadPermTargetLane(SpirvEmitterState& state, std::uint32_t subid, std::uint32_t control); DppTargetLane EmitDppRowShiftTargetLane(SpirvEmitterState& state, std::uint32_t subid, std::uint32_t amount, bool left); DppTargetLane EmitDppRowRotateRightTargetLane(SpirvEmitterState& state, std::uint32_t subid, std::uint32_t amount); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp index 8ed46f17e..13b9b7244 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp @@ -1,4 +1,5 @@ #include "SpirvBackend/SpirvEmitterInstructions.hpp" +#include "SpirvBackend/SpirvMemory/SpirvSubgroup.hpp" #include "IntermediateRepresentation/IrValue.hpp" #include #include @@ -201,10 +202,11 @@ std::uint32_t EmitDsMaskedLaneRead(SpirvEmitterState& state, std::uint32_t sourc if (state.laneCount == 2) { lane = Binary(state, spv::OpBitwiseAnd, TypeU32(state), lane, ConstantU32(state, 31u)); } + const auto physicalLane = EmitHostSubgroupLane(state, lane); const auto shuffled = state.module.AllocateId(); - state.module.AddFunction(spv::OpGroupNonUniformShuffle, TypeU32(state), shuffled, ConstantU32(state, spv::ScopeSubgroup), source, lane); + state.module.AddFunction(spv::OpGroupNonUniformShuffle, TypeU32(state), shuffled, ConstantU32(state, spv::ScopeSubgroup), source, physicalLane); const auto sourceExec = state.module.AllocateId(); - state.module.AddFunction(spv::OpGroupNonUniformShuffle, TypeBool(state), sourceExec, ConstantU32(state, spv::ScopeSubgroup), exec, lane); + state.module.AddFunction(spv::OpGroupNonUniformShuffle, TypeBool(state), sourceExec, ConstantU32(state, spv::ScopeSubgroup), exec, physicalLane); const auto sourceActive = Binary(state, spv::OpLogicalAnd, TypeBool(state), sourceExec, EmitSubgroupLaneActiveBool(state, lane)); return Select(state, TypeU32(state), sourceActive, shuffled, ConstantU32(state, 0u)); } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp index 3139d4ace..8dcc53b53 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp @@ -4,6 +4,7 @@ #include "SpirvBackend/SpirvEmitterState.hpp" #include "SpirvBackend/SpirvFlowEmitter.hpp" #include "SpirvBackend/SpirvMemory/SpirvModuleSetup.hpp" +#include "SpirvBackend/SpirvMemory/SpirvSubgroup.hpp" #include #include #include @@ -119,7 +120,7 @@ std::uint32_t SpirvValueEmitContext::Ballot(const IrValue* predicate) { const auto low = state.module.AllocateId(); state.module.AddFunction(spv::OpGroupNonUniformBallot, ballotType, low, scope, otherHalf == nullptr || half == 0u ? Def(predicate) : otherHalf->Def(predicate)); if (otherHalf == nullptr) { - return low; + return EmitWaveBallot(state, low); } const auto high = state.module.AllocateId(); const auto lowWord = state.module.AllocateId(); @@ -158,7 +159,7 @@ std::uint32_t SpirvValueEmitContext::Shuffle(const IrValue& inst, std::size_t in const auto scope = ConstantU32(state, spv::ScopeSubgroup); const auto low = state.module.AllocateId(); if (otherHalf == nullptr) { - state.module.AddFunction(spv::OpGroupNonUniformShuffle, type, low, scope, Arg(inst, index), lane); + state.module.AddFunction(spv::OpGroupNonUniformShuffle, type, low, scope, Arg(inst, index), EmitHostSubgroupLane(state, lane)); return low; } const auto physicalLane = EmitBinaryU32(state, spv::OpBitwiseAnd, lane, ConstantU32(state, 31u)); @@ -247,6 +248,8 @@ std::vector SpirvEmitter::Emit(const IrProgram& program, const Sh state.supportedCapabilities = target.supportedCapabilities; state.supportedExtensions = target.supportedExtensions; state.nonConstantImageOffsets = target.nonConstantImageOffsets; + state.splitSubgroup = program.WaveSize() == 32u && target.subgroupSize > 32u; + if (state.splitSubgroup && (state.requirements.subgroupBallot || state.requirements.subgroupShuffle)) state.requirements.subgroupLocalInvocationId = true; const auto* workgroup = ShaderWorkgroupInputFor(state); state.laneCount = workgroup != nullptr && program.WaveSize() == 64u && workgroup->hostSubgroupSize == 32u ? 2u : 1u; if (state.laneCount == 2u) state.sharedLaneValues = WaveUniformValues(program); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp index 23216880a..a3daca57c 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp @@ -1,5 +1,6 @@ #include "SpirvBackend/SpirvBda.hpp" #include "SpirvBackend/SpirvEmitterInstructions.hpp" +#include "SpirvBackend/SpirvMemory/SpirvSubgroup.hpp" #include "SpirvBackend/SpirvBufferFormat.hpp" #include #include @@ -1082,7 +1083,7 @@ std::uint32_t AppendConsume(SpirvValueEmitContext& ctx, const IrValue& inst, boo return value; }); const auto result = state.module.AllocateId(); - state.module.AddFunction(spv::OpGroupNonUniformShuffle, TypeU32(state), result, ConstantU32(state, spv::ScopeSubgroup), atomic, sourceLane); + state.module.AddFunction(spv::OpGroupNonUniformShuffle, TypeU32(state), result, ConstantU32(state, spv::ScopeSubgroup), atomic, EmitHostSubgroupLane(state, sourceLane)); return result; } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp index 314e04cd0..2311b3011 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp @@ -17,17 +17,39 @@ namespace ShaderRecompiler throw std::runtime_error("SPIR-V module emission failed: " + reason); } + std::uint32_t HostInvocationId(SpirvEmitterState& state) { + if (state.subgroupLocalInvocationIdVariable == 0) { + FailEmit("SubgroupLocalInvocationId was not declared before function emission"); + } + const auto value = state.module.AllocateId(); + state.module.AddFunction(spv::OpLoad, TypeU32(state), value, state.subgroupLocalInvocationIdVariable); + return value; + } + } std::uint32_t EmitSubgroupLocalInvocationId(SpirvEmitterState& state) { - if (state.subgroupLocalInvocationIdVariable == 0) { - FailEmit("SubgroupLocalInvocationId was not declared before function emission"); - } - const auto value = state.module.AllocateId(); - state.module.AddFunction(spv::OpLoad, TypeU32(state), value, state.subgroupLocalInvocationIdVariable); + const auto value = HostInvocationId(state); + if (state.splitSubgroup) return EmitBinaryU32(state, spv::OpBitwiseAnd, value, ConstantU32(state, 31u)); return state.laneHalf == 0 ? value : EmitAddU32(state, value, ConstantU32(state, 32)); } + std::uint32_t EmitHostSubgroupLane(SpirvEmitterState& state, std::uint32_t lane) { + if (!state.splitSubgroup) return lane; + const auto base = EmitBinaryU32(state, spv::OpBitwiseAnd, HostInvocationId(state), ConstantU32(state, ~31u)); + return EmitAddU32(state, base, lane); + } + + std::uint32_t EmitWaveBallot(SpirvEmitterState& state, std::uint32_t ballot) { + if (!state.splitSubgroup) return ballot; + const auto word = EmitBinaryU32(state, spv::OpShiftRightLogical, HostInvocationId(state), ConstantU32(state, 5u)); + const auto mask = state.module.AllocateId(); + const auto result = state.module.AllocateId(); + state.module.AddFunction(spv::OpVectorExtractDynamic, TypeU32(state), mask, ballot, word); + state.module.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4u), result, mask, ConstantU32(state, 0u), ConstantU32(state, 0u), ConstantU32(state, 0u)); + return result; + } + DppTargetLane EmitDppQuadPermTargetLane(SpirvEmitterState& state, std::uint32_t subid, std::uint32_t control) { constexpr std::uint32_t laneBits = 2u; constexpr std::uint32_t laneMask = (1u << laneBits) - 1u; @@ -157,6 +179,6 @@ namespace ShaderRecompiler std::uint32_t EmitSubgroupLaneActiveBool(SpirvEmitterState& state, std::uint32_t lane) { const auto activeBallot = state.module.AllocateId(); state.module.AddFunction(spv::OpGroupNonUniformBallot, TypeU32Vector(state, 4), activeBallot, ConstantU32(state, spv::ScopeSubgroup), ConstantBool(state, true)); - return EmitBallotLaneActiveBool(state, activeBallot, lane); + return EmitBallotLaneActiveBool(state, EmitWaveBallot(state, activeBallot), lane); } } From bfe9bf9af4a7efce71d4a85c2f9f193a17610831 Mon Sep 17 00:00:00 2001 From: DotDebian Date: Mon, 5 Oct 2026 02:08:15 +0200 Subject: [PATCH 030/164] fix(pngenc): restrict adaptive filtering to the requested filter subset A filter_type naming a subset of the PNG filters used only the first filter of the mask, because stb's writer is either fully adaptive or forced to one filter. Decoder::Png::Encode now takes the set of allowed filter types, filters the rows and writes the chunks itself, and keeps stb for deflate only. Output for the all-filters and single-filter cases is unchanged. Co-Authored-By: Claude Opus 5.5 --- core/Decoder/Png/CMakeLists.txt | 1 + core/Decoder/Png/include/Decoder/Png.hpp | 9 +- core/Decoder/Png/src/Png.cpp | 118 +++++++++++++++++++---- core/Decoder/Png/tests/Png.cpp | 68 ++++++++++++- core/libs/prx/libScePngEnc/Export.cpp | 18 ++-- core/libs/tests/GuestPngEnc.cpp | 28 ++++++ docs/dev/TechnicalDebt.md | 2 +- 7 files changed, 213 insertions(+), 31 deletions(-) diff --git a/core/Decoder/Png/CMakeLists.txt b/core/Decoder/Png/CMakeLists.txt index 2c4d268b3..6a86166c2 100644 --- a/core/Decoder/Png/CMakeLists.txt +++ b/core/Decoder/Png/CMakeLists.txt @@ -16,6 +16,7 @@ function(add_png_decoder target runtimeTarget) if(BUILD_TESTING) add_test_executable(${target}_tests ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/tests/Png.cpp) + target_include_directories(${target}_tests SYSTEM PRIVATE ${PROJECT_SOURCE_DIR}/3rdparty/stb) target_link_libraries(${target}_tests PRIVATE ${target}) configure_windows_unwind(${target}_tests) add_test(NAME ${target} COMMAND ${target}_tests) diff --git a/core/Decoder/Png/include/Decoder/Png.hpp b/core/Decoder/Png/include/Decoder/Png.hpp index 051b5f230..175341762 100644 --- a/core/Decoder/Png/include/Decoder/Png.hpp +++ b/core/Decoder/Png/include/Decoder/Png.hpp @@ -35,9 +35,16 @@ std::optional
ParseHeader(std::span png); std::optional Decode(std::span png); +inline constexpr std::uint8_t FILTER_NONE = 1 << 0; +inline constexpr std::uint8_t FILTER_SUB = 1 << 1; +inline constexpr std::uint8_t FILTER_UP = 1 << 2; +inline constexpr std::uint8_t FILTER_AVERAGE = 1 << 3; +inline constexpr std::uint8_t FILTER_PAETH = 1 << 4; +inline constexpr std::uint8_t FILTER_ALL = FILTER_NONE | FILTER_SUB | FILTER_UP | FILTER_AVERAGE | FILTER_PAETH; + struct EncodeOptions { int compressionLevel = 8; - int filter = -1; + std::uint8_t filters = FILTER_ALL; }; std::vector Encode(std::span pixels, std::uint32_t width, std::uint32_t height, diff --git a/core/Decoder/Png/src/Png.cpp b/core/Decoder/Png/src/Png.cpp index 7e8d40b8d..d137df164 100644 --- a/core/Decoder/Png/src/Png.cpp +++ b/core/Decoder/Png/src/Png.cpp @@ -4,7 +4,8 @@ #include #include #include -#include +#include +#include #include #define STB_IMAGE_WRITE_STATIC @@ -26,6 +27,17 @@ constexpr std::array SIGNATURE = {0x89, 0x50, 0x4E, 0x47, 0x0D, constexpr std::size_t CHUNK_OVERHEAD = 12; constexpr std::size_t IHDR_SIZE = 13; constexpr std::uint32_t MAX_DIMENSION = 0x7FFFFFFF; +constexpr std::uint8_t FILTER_TYPE_COUNT = 5; +constexpr std::array COLOR_TYPE_BY_CHANNELS = {0, 0, 4, 2, 6}; +constexpr std::array CRC_TABLE = [] { + std::array table{}; + for (std::uint32_t index = 0; index < table.size(); ++index) { + std::uint32_t value = index; + for (int bit = 0; bit < 8; ++bit) value = (value >> 1) ^ ((value & 1) != 0 ? 0xEDB88320u : 0u); + table[index] = value; + } + return table; +}(); std::uint32_t readBigEndian32(const std::uint8_t* bytes) { return static_cast(bytes[0]) << 24 | static_cast(bytes[1]) << 16 @@ -51,10 +63,57 @@ bool isValidFormat(std::uint8_t bitDepth, std::uint8_t colorType) { } } -void appendBytes(void* context, void* data, int size) { - auto* output = static_cast*>(context); - const auto* bytes = static_cast(data); - output->insert(output->end(), bytes, bytes + size); +void appendBigEndian32(std::vector& output, std::uint32_t value) { + for (int shift = 24; shift >= 0; shift -= 8) output.push_back(static_cast(value >> shift)); +} + +void appendChunk(std::vector& output, const char* type, std::span data) { + appendBigEndian32(output, static_cast(data.size())); + const std::size_t checkedFrom = output.size(); + output.insert(output.end(), type, type + 4); + output.insert(output.end(), data.begin(), data.end()); + std::uint32_t crc = 0xFFFFFFFFu; + for (std::size_t i = checkedFrom; i < output.size(); ++i) crc = (crc >> 8) ^ CRC_TABLE[(crc ^ output[i]) & 0xFF]; + appendBigEndian32(output, ~crc); +} + +int paethPredictor(int left, int up, int upLeft) { + const int estimate = left + up - upLeft; + const int leftDistance = std::abs(estimate - left); + const int upDistance = std::abs(estimate - up); + const int upLeftDistance = std::abs(estimate - upLeft); + if (leftDistance <= upDistance && leftDistance <= upLeftDistance) return left; + return upDistance <= upLeftDistance ? up : upLeft; +} + +std::uint64_t filterRow(const std::uint8_t* row, const std::uint8_t* previousRow, std::size_t rowSize, std::size_t pixelSize, + std::uint8_t filterType, std::uint8_t* filtered) { + std::uint64_t cost = 0; + for (std::size_t i = 0; i < rowSize; ++i) { + const int left = i >= pixelSize ? row[i - pixelSize] : 0; + const int up = previousRow ? previousRow[i] : 0; + const int upLeft = previousRow && i >= pixelSize ? previousRow[i - pixelSize] : 0; + int prediction = 0; + switch (filterType) { + case 1: + prediction = left; + break; + case 2: + prediction = up; + break; + case 3: + prediction = (left + up) >> 1; + break; + case 4: + prediction = paethPredictor(left, up, upLeft); + break; + default: + break; + } + filtered[i] = static_cast(row[i] - prediction); + cost += filtered[i] < 0x80 ? filtered[i] : 0x100u - filtered[i]; + } + return cost; } } // namespace @@ -105,25 +164,50 @@ std::optional Decode(std::span png) { std::vector Encode(std::span pixels, std::uint32_t width, std::uint32_t height, std::uint32_t channels, EncodeOptions options) { if (channels < 1 || channels > 4) throw std::invalid_argument("Png::Encode: channels must be 1-4"); - if (options.compressionLevel < 0 || options.compressionLevel > 9 || options.filter < -1 || options.filter > 4) { + if (options.compressionLevel < 0 || options.compressionLevel > 9 || options.filters == 0 || (options.filters & ~FILTER_ALL) != 0) { throw std::invalid_argument("Png::Encode: unsupported options"); } - if (width == 0 || height == 0 || static_cast(width) * channels > INT_MAX || height > INT_MAX) { + const std::uint64_t rowSize = static_cast(width) * channels; + if (width == 0 || height == 0 || rowSize > INT_MAX || (rowSize + 1) * height > INT_MAX) { throw std::invalid_argument("Png::Encode: unsupported image size"); } - if (pixels.size() < static_cast(width) * height * channels) { + if (pixels.size() < rowSize * height) { throw std::invalid_argument("Png::Encode: pixel buffer is too small"); } - std::vector output; - const int stride = static_cast(width * channels); - static std::mutex optionsMutex; - std::lock_guard lock(optionsMutex); - stbi_write_png_compression_level = options.compressionLevel; - stbi_write_force_png_filter = options.filter; - const int written = stbi_write_png_to_func(appendBytes, &output, static_cast(width), static_cast(height), - static_cast(channels), pixels.data(), stride); - if (written == 0) throw std::runtime_error("Png::Encode: encoding failed"); + std::vector filtered((rowSize + 1) * height); + std::vector candidate(rowSize); + for (std::uint32_t y = 0; y < height; ++y) { + const std::uint8_t* row = pixels.data() + rowSize * y; + const std::uint8_t* previousRow = y == 0 ? nullptr : row - rowSize; + std::uint8_t* filteredRow = filtered.data() + (rowSize + 1) * y; + std::uint64_t bestCost = UINT64_MAX; + for (std::uint8_t filterType = 0; filterType < FILTER_TYPE_COUNT; ++filterType) { + if ((options.filters & (1u << filterType)) == 0) continue; + const std::uint64_t cost =filterRow(row, previousRow, rowSize, channels, filterType, candidate.data()); + if (cost >= bestCost) continue; + bestCost = cost; + filteredRow[0] = filterType; + std::copy(candidate.begin(), candidate.end(), filteredRow + 1); + } + } + + int compressedSize = 0; + const std::unique_ptr compressed( + stbi_zlib_compress(filtered.data(), static_cast(filtered.size()), &compressedSize, options.compressionLevel), + [](unsigned char* memory) { STBIW_FREE(memory); }); + if (!compressed) throw std::runtime_error("Png::Encode: encoding failed"); + + std::vector header; + appendBigEndian32(header, width); + appendBigEndian32(header, height); + header.insert(header.end(), {8, COLOR_TYPE_BY_CHANNELS[channels], 0, 0, 0}); + + std::vector output(SIGNATURE.begin(), SIGNATURE.end()); + output.reserve(SIGNATURE.size() + 3 * CHUNK_OVERHEAD + IHDR_SIZE + static_cast(compressedSize)); + appendChunk(output, "IHDR", header); + appendChunk(output, "IDAT", std::span(compressed.get(), static_cast(compressedSize))); + appendChunk(output, "IEND", {}); return output; } diff --git a/core/Decoder/Png/tests/Png.cpp b/core/Decoder/Png/tests/Png.cpp index 9129f03dc..7287ed5b3 100644 --- a/core/Decoder/Png/tests/Png.cpp +++ b/core/Decoder/Png/tests/Png.cpp @@ -1,10 +1,17 @@ #include "Decoder/Png.hpp" +#include #include #include #include #include +#define STB_IMAGE_STATIC +#define STB_IMAGE_IMPLEMENTATION +#define STBI_ONLY_PNG +#define STBI_NO_STDIO +#include "stb_image.h" + static void Require(bool value) { if (!value) std::abort(); } template @@ -84,11 +91,63 @@ static void RequireRoundTrip(std::uint32_t channels, Decoder::Png::ColorType col } } +static std::vector RowFilters(const std::vector& png, std::uint32_t height, std::uint32_t rowSize) { + std::vector stream; + for (std::size_t offset = 8; offset + 12 <= png.size();) { + const std::size_t length = static_cast(png[offset]) << 24 | static_cast(png[offset + 1]) << 16 + | static_cast(png[offset + 2]) << 8 | png[offset + 3]; + Require(length <= png.size() - offset - 12); + const std::uint8_t* data = png.data() + offset + 8; + if (std::equal(data - 4, data, "IDAT")) stream.insert(stream.end(), data, data + length); + offset += 12 + length; + } + + int size = 0; + char* rows = stbi_zlib_decode_malloc(reinterpret_cast(stream.data()), static_cast(stream.size()), &size); + Require(rows && static_cast(size) == static_cast(height) * (rowSize + 1)); + std::vector filters; + for (std::uint32_t y = 0; y < height; ++y) filters.push_back(static_cast(rows[y * (rowSize + 1)])); + stbi_image_free(rows); + return filters; +} + +static void RequireFilterSets(std::uint32_t channels) { + constexpr std::uint32_t width = 16; + constexpr std::uint32_t height = 6; + const std::uint32_t rowSize = width * channels; + std::vector pixels(rowSize * height); + std::uint32_t noise = 12345; + for (std::uint32_t y = 0; y < height; ++y) { + for (std::uint32_t i = 0; i < rowSize; ++i) { + noise = noise * 1103515245u + 12345u; + pixels[y * rowSize + i] = y == 2 ? static_cast(noise >> 16) : static_cast(i / channels * 8); + } + } + std::copy_n(pixels.begin() + 2 * rowSize, rowSize, pixels.begin() + 3 * rowSize); + + for (std::uint8_t filters = 1; filters <= Decoder::Png::FILTER_ALL; ++filters) { + const std::vector png = Decoder::Png::Encode(pixels, width, height, channels, {6, filters}); + for (const std::uint8_t filter : RowFilters(png, height, rowSize)) Require(filter < 5 && (filters >> filter & 1) != 0); + + const auto image = Decoder::Png::Decode(png); + Require(image.has_value() && image->width == width && image->height == height); + for (std::size_t pixel = 0; pixel < width * height; ++pixel) { + Require(image->pixels[pixel * 4] == pixels[pixel * channels]); + Require(image->pixels[pixel * 4 + 3] == (channels % 2 == 0 ? pixels[pixel * channels + channels - 1] : 255)); + } + } + + const std::vector subOrUp = RowFilters( + Decoder::Png::Encode(pixels, width, height, channels, {6, Decoder::Png::FILTER_SUB | Decoder::Png::FILTER_UP}), height, rowSize); + Require(subOrUp[0] == 1 && subOrUp[1] == 2 && subOrUp[3] == 2 && subOrUp[4] == 1 && subOrUp[5] == 2); +} + int main() { RequireRoundTrip(1, Decoder::Png::ColorType::Grayscale); RequireRoundTrip(2, Decoder::Png::ColorType::GrayscaleAlpha); RequireRoundTrip(3, Decoder::Png::ColorType::Rgb); RequireRoundTrip(4, Decoder::Png::ColorType::Rgba); + for (const std::uint32_t channels : {1, 2, 3, 4}) RequireFilterSets(channels); const auto paletteHeader = Decoder::Png::ParseHeader(PALETTE_TRNS); Require(paletteHeader.has_value()); @@ -140,13 +199,14 @@ int main() { Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 4, 4, 5); })); Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 0, 4, 4); })); Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 4, 5, 4); })); - Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 4, 4, 4, {10, -1}); })); - Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 4, 4, 4, {8, 5}); })); + Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 4, 4, 4, {10, Decoder::Png::FILTER_ALL}); })); + Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 4, 4, 4, {8, 0}); })); + Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 4, 4, 4, {8, Decoder::Png::FILTER_ALL + 1}); })); std::vector gradient(64 * 64 * 3); for (std::size_t i = 0; i < gradient.size(); ++i) gradient[i] = static_cast(i / 3 % 64 * 4); - const auto stored = Decoder::Png::Encode(gradient, 64, 64, 3, {0, 0}); - const auto compressed = Decoder::Png::Encode(gradient, 64, 64, 3, {9, 1}); + const auto stored = Decoder::Png::Encode(gradient, 64, 64, 3, {0, Decoder::Png::FILTER_NONE}); + const auto compressed = Decoder::Png::Encode(gradient, 64, 64, 3, {9, Decoder::Png::FILTER_SUB}); Require(compressed.size() < stored.size()); for (const auto& png : {stored, compressed}) { const auto image = Decoder::Png::Decode(png); diff --git a/core/libs/prx/libScePngEnc/Export.cpp b/core/libs/prx/libScePngEnc/Export.cpp index 11aa6731b..4ff0af04e 100644 --- a/core/libs/prx/libScePngEnc/Export.cpp +++ b/core/libs/prx/libScePngEnc/Export.cpp @@ -52,13 +52,15 @@ PngEncContext* context(void* handle) { return ctx && ctx->magic == CONTEXT_MAGIC ? ctx : nullptr; } -int filterMode(std::uint16_t filterType) { - if (filterType == FILTER_ALL) return -1; - if (filterType & FILTER_SUB) return 1; - if (filterType & FILTER_UP) return 2; - if (filterType & FILTER_AVERAGE) return 3; - if (filterType & FILTER_PAETH) return 4; - return 0; +std::uint8_t filterSet(std::uint16_t filterType) { + if (filterType == FILTER_ALL) return Decoder::Png::FILTER_ALL; + if (filterType == 0) return Decoder::Png::FILTER_NONE; + std::uint8_t filters = 0; + if (filterType & FILTER_SUB) filters |= Decoder::Png::FILTER_SUB; + if (filterType & FILTER_UP) filters |= Decoder::Png::FILTER_UP; + if (filterType & FILTER_AVERAGE) filters |= Decoder::Png::FILTER_AVERAGE; + if (filterType & FILTER_PAETH) filters |= Decoder::Png::FILTER_PAETH; + return filters; } } @@ -120,7 +122,7 @@ int APS5_VABI scePngEncEncode(void* handle, const PngEncEncodeParam* param, PngE if (channels == 4) *destination++ = source[3]; } } - png = Decoder::Png::Encode(pixels, width, height, channels, {param->compression_level, filterMode(param->filter_type)}); + png = Decoder::Png::Encode(pixels, width, height, channels, {param->compression_level, filterSet(param->filter_type)}); } catch (const std::exception&) { return PNG_ENC_ERROR_FATAL; } diff --git a/core/libs/tests/GuestPngEnc.cpp b/core/libs/tests/GuestPngEnc.cpp index 9208ae7f0..7535dae85 100644 --- a/core/libs/tests/GuestPngEnc.cpp +++ b/core/libs/tests/GuestPngEnc.cpp @@ -1,8 +1,10 @@ #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "Decoder/Png.hpp" +#include #include #include +#include #include extern "C" { @@ -84,6 +86,32 @@ int main() { } } + constexpr std::uint32_t rowSize = 16 * 4; + std::vector rows(rowSize * 6); + std::uint32_t noise = 12345; + for (std::uint32_t y = 0; y < 6; ++y) { + for (std::uint32_t i = 0; i < rowSize; ++i) { + noise = noise * 1103515245u + 12345u; + rows[y * rowSize + i] = y == 2 ? static_cast(noise >> 16) : static_cast(i / 4 * 8); + } + } + std::copy_n(rows.begin() + 2 * rowSize, rowSize, rows.begin() + 3 * rowSize); + PngEncEncodeParam filtered{rows.data(), png.data(), static_cast(rows.size()), static_cast(png.size()), + 16, 6, rowSize, 0, 19, 8, 0, 0, 6}; + const auto encodeWith = [&](std::uint16_t filterType) { + filtered.filter_type = filterType; + const int size = scePngEncEncode(handle, &filtered, nullptr); + Require(size > 0); + return std::vector(png.begin(), png.begin() + size); + }; + using namespace Decoder::Png; + const std::pair filterSets[] = { + {0, FILTER_NONE}, {1, FILTER_SUB}, {2, FILTER_UP}, {4, FILTER_AVERAGE}, {8, FILTER_PAETH}, + {3, FILTER_SUB | FILTER_UP}, {6, FILTER_UP | FILTER_AVERAGE}, {9, FILTER_SUB | FILTER_PAETH}, + {14, FILTER_UP | FILTER_AVERAGE | FILTER_PAETH}, {15, FILTER_ALL}}; + for (const auto& [filterType, filters] : filterSets) Require(encodeWith(filterType) == Encode(rows, 16, 6, 4, {6, filters})); + Require(encodeWith(3) != encodeWith(1) && encodeWith(3) != encodeWith(2) && encodeWith(15) != encodeWith(14)); + encode.png_mem_size = 20; PngEncOutputInfo info{1, 1}; Require(scePngEncEncode(handle, &encode, &info) == dataOverflow && info.data_size == 0 && info.processed_height == 0); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 5c87a3d98..807f61048 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -50,6 +50,7 @@ Throughout the project, every function at every stage either **does exactly what - [AOWqIYsgVHs](../../core/libs/prx/libSceContentExport/Export.cpp) (libSceContentExport) - unknown name, signature - [GQTObcITIXI](../../core/libs/prx/libSceShare/Export.cpp) (libSceShare) - unknown name, signature - [BnMAMrsfVWo](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libc) - unknown name, signature +- [scePngEncEncode](../../core/libs/prx/libScePngEnc/Export.cpp) (libScePngEnc) - whether rows may stay unfiltered when `filter_type` names a subset of the filters is unknown; as in shadPS4, they may only for 0 and for the all-filters mask - AudioIn, NpSessionSignaling and PlayerInvitationDialog exports added without an implementation have assumed signatures - [libSceAudioPropagation](../../core/libs/prx/libSceAudioPropagation/Export.cpp) (libSceAudioPropagation) - signatures and struct descriptors recovered from PPSA21567's calls (#481), not from documentation. The system options layout is unknown (only checked for non-null); the fourth argument of `sceAudioPropagationSourceCalculateAudioPaths` is unknown; `sceAudioPropagationSourceGetAudioPath` is assumed to output an 8-byte path handle. The meaning of the `RenderInfo` word at +0x28 is unknown (PPSA21567 and PPSA21564 pass 2); only 2 is accepted. Both titles pass one `RenderInfo` per call: a count of 0 throws, and larger counts are handled element by element without proof. No error code is known, so invalid input throws instead of returning one - [sceAvPlayerStartEx](../../core/libs/prx/libSceAvPlayer/Export.cpp) (libSceAvPlayer) - start info layout unknown; it is ignored and playback starts as with `sceAvPlayerStart` @@ -74,5 +75,4 @@ Throughout the project, every function at every stage either **does exactly what - [sceKeyboardGetKey2Char](../../core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp) (libSceKeyboard) translates only the 101-key (US) arrangement and throws for the 106-key (Japanese) one; Ctrl and Alt do not change the character. - [libSceAudiodec](../../core/libs/prx/libSceAudiodec/Export.cpp) throws for the 24-bit PCM word size (`iBwPcm` 0): its sample layout is unknown. ATRAC9 decoding is covered only by configuration and error tests, as no ATRAC9 encoder is available for a fixture. - [sceAjmDecMp3ParseFrame](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) throws when the original file length is requested (`parse_ofl` non-zero): reading it from the frame's Xing/Info, VBRI or Fraunhofer data is not implemented. -- [libScePngEnc](../../core/libs/prx/libScePngEnc/Export.cpp) honours `filter_type` only as all filters (adaptive) or a single filter: the [PNG encoder](../../core/Decoder/Png/src/Png.cpp) (stb) cannot restrict adaptive filtering to a subset, so the first filter in the mask is used. - [libScePlayerInvitationDialog](../../core/libs/prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.cpp) simulates dialog completion without displaying UI or sending invitations; its parameter ABI remains unverified. From 3b2edec285e56abb9ba455eea9bc995a1e5276fc Mon Sep 17 00:00:00 2001 From: hamb3r <48532909+hamb3r@users.noreply.github.com> Date: Mon, 5 Oct 2026 03:08:42 +0300 Subject: [PATCH 031/164] fix(video): set per-monitor v2 DPI awareness on Windows Avoid Windows DPI virtualization so SDL, the drawable and the swapchain use physical pixels. Co-Authored-By: saulo benigno --- core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp index b6d5471d2..b61d35d41 100644 --- a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp +++ b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp @@ -199,6 +199,7 @@ VideoOutDriver& VideoOutDriver::Get() { } VideoOutDriver::VideoOutDriver() { + SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitorv2"); if (SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) < 0) { throw std::runtime_error(std::string("SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: ") + SDL_GetError()); } From e017894bb415315c4b130cdb3bb920f5f2c52009 Mon Sep 17 00:00:00 2001 From: Chad Date: Sun, 4 Oct 2026 19:08:54 -0500 Subject: [PATCH 032/164] fix(agc): free per-thread driver state at thread exit without emutls On MinGW, thread_local storage is emutls: libgcc frees a thread's blocks from a winpthread key destructor when the thread function returns, and the CRT runs the thread_local destructors later, from the module's TLS callback at thread detach. The destructors of the driver's non-trivial thread_locals (the deferred label queue, packet profile, capture and revalidation scratch vectors, build profile, memory counters, disk cache buffers) therefore ran on freed storage. Under the debug heap the flip test failed on every run at the queue worker's exit after the injected flip failure; without it the use-after-free showed up as intermittent heap corruption or a stack overflow on the Windows CI job. ShaderRecompiler::ThreadOwned keeps such state behind a trivially destructible thread_local pointer and deletes it from its own pthread key destructor, which receives the objects directly instead of reading emutls. The driver no longer registers any thread_local destructor. Co-Authored-By: Claude Opus 5.5 --- .../src/Driver/Memory/BufferFill.cpp | 4 +- .../src/Driver/Memory/CaptureValidation.cpp | 19 ++++-- .../src/Driver/Packets/PacketExecution.cpp | 4 +- .../Driver/Synchronization/DeferredLabels.cpp | 8 ++- .../Execution/src/GuestMemory.cpp | 5 +- .../Graphics/src/GuestBufferMemory.cpp | 4 +- .../Graphics/src/ShaderResources.cpp | 49 ++++++++++----- core/shader/recompiler/CMakeLists.txt | 1 + core/shader/recompiler/ShaderDiskCache.cpp | 7 ++- core/shader/recompiler/ThreadOwned.hpp | 59 +++++++++++++++++++ 10 files changed, 128 insertions(+), 32 deletions(-) create mode 100644 core/shader/recompiler/ThreadOwned.hpp diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp index baa341ee2..2d614b5b4 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "ThreadOwned.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include @@ -192,7 +193,8 @@ bool Driver::fillBuffer(QueueState& queue, std::uint32_t queueId, std::spanWaitIdle(); - static thread_local std::vector block; + static thread_local std::vector* blockSlot = nullptr; + auto& block = ShaderRecompiler::ThreadOwned(blockSlot); const auto chunk = std::min(bytes, 1u << 20u); block.resize(chunk); for (std::size_t at = 0; at < chunk; at += 16) std::memcpy(block.data() + at, pattern.data(), 16); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/CaptureValidation.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/CaptureValidation.cpp index 852c8d12c..2eb194129 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/CaptureValidation.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/CaptureValidation.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "ThreadOwned.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include #include @@ -14,7 +15,8 @@ bool Driver::captureStable(std::span captu double waited = 0; PendingView pending; pending.Load(); - thread_local std::vector known; + thread_local std::vector* knownSlot = nullptr; + auto& known = ShaderRecompiler::ThreadOwned(knownSlot); for (const auto& region : captured) { if (!stable) break; known.resize(region.bytes.size()); @@ -58,7 +60,8 @@ bool Driver::captureStable(std::span captu bool Driver::validateCaptured(std::uint64_t program, std::uint32_t queue, std::span captured, const ShaderRecompiler::RecompileResult& compiled, bool inPlace, const PendingView& view, bool* unmapped, std::optional* sampling, const DataMask* data) { using Policy = ShaderMemory::PendingWrite; - thread_local std::vector pending; + thread_local std::vector* pendingSlot = nullptr; + auto& pending = ShaderRecompiler::ThreadOwned(pendingSlot); pending.clear(); for (std::size_t i = 0; i < captured.size(); ++i) { const auto& region = captured[i]; @@ -66,7 +69,8 @@ bool Driver::validateCaptured(std::uint64_t program, std::uint32_t queue, std::s } const auto compareMasked = [&](const ShaderRecompiler::MemoryRegion& region, std::size_t first) { - thread_local std::vector live; + thread_local std::vector* liveSlot = nullptr; + auto& live = ShaderRecompiler::ThreadOwned(liveSlot); live.resize(region.bytes.size()); const auto copied = GuestMemory::CopyMapped(region.guestAddress, live); if (copied != GuestMemory::Compare::Equal) return copied; @@ -84,9 +88,12 @@ bool Driver::validateCaptured(std::uint64_t program, std::uint32_t queue, std::s }; static constexpr std::size_t NoKnownValue = std::numeric_limits::max(); - thread_local std::vector policies; - thread_local std::vector knownOffsets; - thread_local std::vector knownBytes; + thread_local std::vector* policiesSlot = nullptr; + thread_local std::vector* knownOffsetsSlot = nullptr; + thread_local std::vector* knownBytesSlot = nullptr; + auto& policies = ShaderRecompiler::ThreadOwned(policiesSlot); + auto& knownOffsets = ShaderRecompiler::ThreadOwned(knownOffsetsSlot); + auto& knownBytes = ShaderRecompiler::ThreadOwned(knownBytesSlot); policies.assign(pending.size(), Policy::None); knownOffsets.assign(pending.size(), NoKnownValue); knownBytes.clear(); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp index 3ebdecd90..80355fc7e 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "ThreadOwned.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/DeferredLabels.hpp" @@ -97,7 +98,8 @@ void Driver::execute(const Submission& submission) { static const bool profilePackets = std::getenv("APS5_PROFILE_DRAW") != nullptr; - thread_local PacketProfile packetProfile; + thread_local PacketProfile* packetProfileSlot = nullptr; + auto& packetProfile = ShaderRecompiler::ThreadOwned(packetProfileSlot); ++packetProfile.submissions; bumpEpoch(&EpochBumps::submissions); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp index e8be75566..395c2ef2e 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp @@ -2,13 +2,14 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/DeferredLabels.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "ThreadOwned.hpp" #include namespace AgcDriver::DriverDetail { DeferredLabels& deferredLabels() { - static thread_local DeferredLabels deferred; - return deferred; + static thread_local DeferredLabels* deferred = nullptr; + return ShaderRecompiler::ThreadOwned(deferred); } bool DeferLabels() { @@ -91,7 +92,8 @@ void Driver::recordDeferredLabels(VulkanDevice* localDevice, std::uint32_t queue auto& deferred = deferredLabels(); if (deferred.labels.empty()) return; - static thread_local std::vector labels; + static thread_local std::vector* recording = nullptr; + auto& labels = ShaderRecompiler::ThreadOwned(recording); labels.clear(); labels.swap(deferred.labels); struct Clear { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp index 0af8293e2..291369842 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "ThreadOwned.hpp" #include "prx/libc/include/GuestAllocations.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/GuestWriteWatch.hpp" @@ -169,8 +170,8 @@ struct ThreadMemoryCounters { MemoryCounter& CounterFor(MemoryCounterKind kind) { if (SharedMemoryCounters()) return Profile().shared.counters[kind]; - thread_local ThreadMemoryCounters thread; - return thread.counters.counters[kind]; + thread_local ThreadMemoryCounters* thread = nullptr; + return ShaderRecompiler::ThreadOwned(thread).counters.counters[kind]; } // Every thread's counters, the retired and the shared ones summed: calls, bytes, nanoseconds per kind. diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index da3e6332a..9c08d1a48 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include "ThreadOwned.hpp" #include "prx/libSceAgcDriver/Graphics/include/BdaResources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" @@ -671,7 +672,8 @@ void refreshMirror(ImageMirror& mirror, std::uint64_t address, std::uint64_t byt void refreshHeapMirrors(std::vector& mirrors, std::vector& blocks) { constexpr std::uint64_t block = 65536; std::sort(mirrors.begin(), mirrors.end(), [](const ImageMirror* left, const ImageMirror* right) { return left->base < right->base; }); - thread_local std::vector changed; + thread_local std::vector* changedSlot = nullptr; + auto& changed = ShaderRecompiler::ThreadOwned(changedSlot); for (std::size_t first = 0; first < mirrors.size();) { auto last = first + 1; while (last < mirrors.size() && mirrors[last]->base == mirrors[last - 1]->base + mirrors[last - 1]->bytes) ++last; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index 5169e3ef8..847cd80a5 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" +#include "ThreadOwned.hpp" #include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" @@ -186,14 +187,19 @@ struct LookupRecord { const StorageTexture* source; }; -thread_local std::vector lookupLog; +thread_local std::vector* lookupLogSlot = nullptr; + +std::vector& lookupLog() { + return ShaderRecompiler::ThreadOwned(lookupLogSlot); +} void logLookup(const LookupRecord& record) { // The bound drops the oldest half, never everything: one build's lookups (a few dozen at most) // are the newest records, and a capture must find them even when the bound trips mid-build. constexpr std::size_t bound = 1024; - if (lookupLog.size() >= bound) lookupLog.erase(lookupLog.begin(), lookupLog.begin() + bound / 2); - lookupLog.push_back(record); + auto& log = lookupLog(); + if (log.size() >= bound) log.erase(log.begin(), log.begin() + bound / 2); + log.push_back(record); } std::shared_ptr cachedStorageTexture(const Context& context, std::span words, const GuestTextureResource& resource, std::uint32_t mip, std::uint64_t guestBytes = 0); @@ -793,7 +799,8 @@ struct ThreadBuildProfile { }; ThreadBuildProfile& ThreadBuilds() { - thread_local ThreadBuildProfile profile; + thread_local ThreadBuildProfile* profileSlot = nullptr; + auto& profile = ShaderRecompiler::ThreadOwned(profileSlot); return profile; } @@ -1310,7 +1317,8 @@ bool EpochRevalidate() { // ValidatedSurface); the last record of an object is the freshest. void ShaderResources::captureValidation() { const auto record = [](const void* object) -> const LookupRecord* { - for (auto it = lookupLog.rbegin(); it != lookupLog.rend(); ++it) { + const auto& log = lookupLog(); + for (auto it = log.rbegin(); it != log.rend(); ++it) { if (it->object == object) return &*it; } return nullptr; @@ -1319,7 +1327,7 @@ void ShaderResources::captureValidation() { for (std::size_t i = 0; i < textures.size(); ++i) { if (const auto* found = record(textures[i].get())) validatedTextures[i] = {found->resource, found->bytes, found->keys, found->generation, 0, found->source, true}; } - lookupLog.clear(); + lookupLog().clear(); } // Proves every texture and storage image still current without repeating its lookup: exactly the @@ -1350,12 +1358,17 @@ bool ShaderResources::fastRevalidate(std::uint64_t serialBefore, std::span queries; - thread_local std::vector pending; + thread_local std::vector* queriesSlot = nullptr; + auto& queries = ShaderRecompiler::ThreadOwned(queriesSlot); + thread_local std::vector* pendingSlot = nullptr; + auto& pending = ShaderRecompiler::ThreadOwned(pendingSlot); // The element each pending query stands for, in query order. - thread_local std::vector owners; - thread_local std::vector images; - thread_local std::vector scannedKeys; + thread_local std::vector* ownersSlot = nullptr; + auto& owners = ShaderRecompiler::ThreadOwned(ownersSlot); + thread_local std::vector* imagesSlot = nullptr; + auto& images = ShaderRecompiler::ThreadOwned(imagesSlot); + thread_local std::vector* scannedKeysSlot = nullptr; + auto& scannedKeys = ShaderRecompiler::ThreadOwned(scannedKeysSlot); queries.clear(); pending.clear(); owners.clear(); @@ -1628,7 +1641,7 @@ bool ShaderResources::Revalidate(std::span shaders, ProofR // they must hand back the very objects the set's views belong to. The build appended them stage // by stage, binding by binding, so the walk repeats that order. const auto fullWalk = [&] { - lookupLog.clear(); + lookupLog().clear(); std::size_t textureIndex = 0; std::size_t storageIndex = 0; for (const auto& shader : shaders) { @@ -1664,8 +1677,10 @@ bool ShaderResources::Revalidate(std::span shaders, ProofR captureValidation(); return true; }; - thread_local std::vector overlapping; - thread_local std::vector refreshed; + thread_local std::vector* overlappingSlot = nullptr; + auto& overlapping = ShaderRecompiler::ThreadOwned(overlappingSlot); + thread_local std::vector* refreshedSlot = nullptr; + auto& refreshed = ShaderRecompiler::ThreadOwned(refreshedSlot); refreshed.clear(); FastFail reason = FastFail::Count; bool accepted = false; @@ -1711,7 +1726,8 @@ bool ShaderResources::Revalidate(std::span shaders, ProofR // The walk after the refresh, or beside a proof that accepted a foreign overlap, must // find every object in place and current: another object, or an upload (a moved // content version), is a decision the proof got wrong. - thread_local std::vector> versions; + thread_local std::vector>* versionsSlot = nullptr; + auto& versions = ShaderRecompiler::ThreadOwned(versionsSlot); versions.clear(); for (const auto& surface : validatedTextures) { if (surface.source != nullptr) versions.emplace_back(surface.source, surface.source->Version()); @@ -1744,7 +1760,8 @@ bool ShaderResources::Revalidate(std::span shaders, ProofR }; if (EpochRevalidate()) { if (!directRegions.empty() && !(pendingSerialSeen != 0 && pendingSerialSeen == StorageTexture::PendingSerial())) { - thread_local std::vector regions; + thread_local std::vector* regionsSlot = nullptr; + auto& regions = ShaderRecompiler::ThreadOwned(regionsSlot); regions.clear(); for (const auto& region : directRegions) regions.push_back({region.begin, region.end, nullptr, nullptr, false}); StorageTexture::ScanPending(regions); diff --git a/core/shader/recompiler/CMakeLists.txt b/core/shader/recompiler/CMakeLists.txt index 0082a7142..29d4bf176 100644 --- a/core/shader/recompiler/CMakeLists.txt +++ b/core/shader/recompiler/CMakeLists.txt @@ -218,6 +218,7 @@ function(add_shader_recompiler target runtimeTarget) ResourceMaterializer::SpecializationNanoseconds SetDebugProbeActive ShaderCacheDirectory + ThreadOwned WriteFileAtomically ) set(shaderCacheManifest) diff --git a/core/shader/recompiler/ShaderDiskCache.cpp b/core/shader/recompiler/ShaderDiskCache.cpp index e7ed428b8..fd57be587 100644 --- a/core/shader/recompiler/ShaderDiskCache.cpp +++ b/core/shader/recompiler/ShaderDiskCache.cpp @@ -1,6 +1,7 @@ #include "ShaderDiskCache.hpp" #include "CacheKey.hpp" #include "ShaderCacheDirectory.hpp" +#include "ThreadOwned.hpp" #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX @@ -603,7 +604,8 @@ public: bool Load(std::span key, CompiledVariant& variant) { const auto started = std::chrono::steady_clock::now(); const auto name = EntryName(key); - thread_local std::vector file; + thread_local std::vector* fileSlot = nullptr; + auto& file = ThreadOwned(fileSlot); bool loaded = false; if (!ReadWholeFile(directory / name, file)) { misses.fetch_add(1, std::memory_order_relaxed); @@ -778,7 +780,8 @@ void BuildKey(const RecompileRequest& request, std::uint32_t hostSubgroupSize, c writer.Value(FileMagic); writer.Value(FormatVersion); writer.Value(SourceVersion()); - thread_local std::vector memoryKey; + thread_local std::vector* memoryKeySlot = nullptr; + auto& memoryKey = ThreadOwned(memoryKeySlot); RecompileCacheKey::Build(request, memoryKey); writer.Values(std::span(memoryKey)); writer.Values(request.shader.code); diff --git a/core/shader/recompiler/ThreadOwned.hpp b/core/shader/recompiler/ThreadOwned.hpp new file mode 100644 index 000000000..94a0d084b --- /dev/null +++ b/core/shader/recompiler/ThreadOwned.hpp @@ -0,0 +1,59 @@ +#ifndef CORE_SHADER_RECOMPILER_THREADOWNED_HPP +#define CORE_SHADER_RECOMPILER_THREADOWNED_HPP + +#include +#include +#include +#include + +namespace ShaderRecompiler { + +namespace ThreadOwnedDetail { + +struct Owned { + void* object; + void (*destroy)(void*); +}; + +inline void DestroyAll(void* value) { + auto* owned = static_cast*>(value); + for (auto it = owned->rbegin(); it != owned->rend(); ++it) it->destroy(it->object); + delete owned; +} + +inline pthread_key_t Key() { + static const pthread_key_t key = [] { + pthread_key_t created{}; + if (pthread_key_create(&created, DestroyAll) != 0) throw std::runtime_error("ThreadOwned: pthread_key_create failed"); + return created; + }(); + return key; +} + +inline void DestroyAtThreadExit(void* object, void (*destroy)(void*)) { + auto* owned = static_cast*>(pthread_getspecific(Key())); + if (owned == nullptr) { + owned = new std::vector(); + if (pthread_setspecific(Key(), owned) != 0) { + delete owned; + throw std::runtime_error("ThreadOwned: pthread_setspecific failed"); + } + } + owned->push_back({object, destroy}); +} + +} + +template +T& ThreadOwned(T*& slot) { + if (slot == nullptr) { + auto object = std::make_unique(); + ThreadOwnedDetail::DestroyAtThreadExit(object.get(), [](void* value) { delete static_cast(value); }); + slot = object.release(); + } + return *slot; +} + +} + +#endif From c2fa7fd97fe952397770255b0e015fe71f5ca04d Mon Sep 17 00:00:00 2001 From: hamb3r <48532909+hamb3r@users.noreply.github.com> Date: Mon, 5 Oct 2026 03:10:07 +0300 Subject: [PATCH 033/164] fix(avplayer): add decode-ahead buffers for application video output When an application requests only 2 video buffers and holds one during playback, decode-ahead frames allow the decoder to stay ahead and prevent stalls under SyncModeNone. Co-Authored-By: saulo benigno --- core/libs/prx/libSceAvPlayer/src/Source.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceAvPlayer/src/Source.cpp b/core/libs/prx/libSceAvPlayer/src/Source.cpp index c453be1bf..2b93a2eee 100644 --- a/core/libs/prx/libSceAvPlayer/src/Source.cpp +++ b/core/libs/prx/libSceAvPlayer/src/Source.cpp @@ -32,6 +32,7 @@ constexpr std::uint32_t VideoBufferAlignment = 0x100; constexpr std::uint32_t AudioBufferAlignment = 0x10; constexpr std::uint32_t AudioChunkSamples = 1024; constexpr std::uint32_t AudioMaxChannels = 8; +constexpr std::uint32_t VideoDecodeAheadFrames = 4; constexpr std::size_t VideoPacketLimit = 30; constexpr std::size_t AudioPacketLimit = 8; constexpr std::size_t AudioOnlyPacketLimit = 30; @@ -604,8 +605,8 @@ private: pitch = AlignUp(static_cast(decoder.context->width), VideoPitchAlignment); bufferHeight = AlignUp(static_cast(decoder.context->height), VideoHeightAlignment); decoder.bufferSize = pitch * bufferHeight * 3 / 2; - count = settings.videoBuffers; - decoder.retained = count > 3 ? count - 3 : 0; + count = settings.videoBuffers + VideoDecodeAheadFrames; + decoder.retained = settings.videoBuffers > 3 ? settings.videoBuffers - 3 : 0; texture = true; alignment = VideoBufferAlignment; } else { From 66a95ada207e9b2cd1f246a36665fe6e1fdd45dc Mon Sep 17 00:00:00 2001 From: Pouare514 Date: Fri, 2 Oct 2026 11:38:00 +0200 Subject: [PATCH 034/164] feat(prx): unknown loader tripwires for TLOU --- core/libs/prx/libSceHttp/Export.cpp | 13 +++++++++++- core/libs/prx/libSceHttp2/Export.cpp | 6 ++++++ core/libs/prx/libSceNet/Export.cpp | 6 ++++++ .../prx/libSceNpSessionSignaling/Export.cpp | 21 ++++++++++--------- core/libs/prx/libSceNpTrophy2/src/Context.cpp | 6 ++++++ core/libs/prx/libSceSystemService/Export.cpp | 12 +++++++++++ core/libs/prx/libSceVoice/Export.cpp | 6 ++++++ core/libs/prx/libc/src/RuntimeSupport.cpp | 6 ++++++ docs/dev/TechnicalDebt.md | 10 +++++++++ 9 files changed, 75 insertions(+), 11 deletions(-) diff --git a/core/libs/prx/libSceHttp/Export.cpp b/core/libs/prx/libSceHttp/Export.cpp index ef0874b74..9470bae8a 100644 --- a/core/libs/prx/libSceHttp/Export.cpp +++ b/core/libs/prx/libSceHttp/Export.cpp @@ -245,7 +245,6 @@ int APS5_VABI sceHttpSetChunkedTransferEnabled(int id, int enable) { return 0; } - APS5_EXPORT("i9mhafzkEi8", sceHttpUnknown00); int APS5_VABI sceHttpUnknown00(void) { NotImplemented_nid_no_patch("i9mhafzkEi8"); @@ -258,6 +257,18 @@ int APS5_VABI sceHttpUnknown01(void) { return 0; } +APS5_EXPORT("vO4B-42ef-k", sceHttpUnknown01); +int APS5_VABI sceHttpUnknown01(void) { + NotImplemented_nid_no_patch("vO4B-42ef-k"); + return 0; +} + +APS5_EXPORT("hPTXo3bICzI", sceHttpUnknown02); +int APS5_VABI sceHttpUnknown02(void) { + NotImplemented_nid_no_patch("hPTXo3bICzI"); + return 0; +} + int APS5_VABI sceHttpCreateRequest2(int conn_id, const char* method, const char* path, uint64_t content_length) { (void)conn_id; (void)method; diff --git a/core/libs/prx/libSceHttp2/Export.cpp b/core/libs/prx/libSceHttp2/Export.cpp index cadaea011..f0c2dd9b4 100644 --- a/core/libs/prx/libSceHttp2/Export.cpp +++ b/core/libs/prx/libSceHttp2/Export.cpp @@ -209,4 +209,10 @@ int APS5_VABI sceHttp2AbortRequest(int req_id) { return 0; } +APS5_EXPORT("otUQuZa-mv0", sceHttp2Unknown00); +int APS5_VABI sceHttp2Unknown00() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index eb0b51b35..6e399e44a 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -650,6 +650,12 @@ int APS5_VABI sceNetEpollCreate(const char* name, int flags) { return id; } +APS5_EXPORT("rMyh97BU5pY", sceNetUnknown01); +int APS5_VABI sceNetUnknown01() { + NotImplemented_nid_no_patch("rMyh97BU5pY"); + return 0; +} + int APS5_VABI sceNetEpollDestroy(int eid) { std::lock_guard lk(g_mutex); if (g_epolls.erase(eid) == 0) { diff --git a/core/libs/prx/libSceNpSessionSignaling/Export.cpp b/core/libs/prx/libSceNpSessionSignaling/Export.cpp index ecdaae31b..482d89913 100644 --- a/core/libs/prx/libSceNpSessionSignaling/Export.cpp +++ b/core/libs/prx/libSceNpSessionSignaling/Export.cpp @@ -57,16 +57,6 @@ int APS5_VABI sceNpSessionSignalingTerminate(void) { return 0; } -int32_t APS5_VABI sceNpSessionSignalingGetConnectionStatus(int32_t context_id, int32_t connection_id, int32_t* status, void* peer_address, uint16_t* peer_port) { - (void)context_id; - (void)connection_id; - (void)status; - (void)peer_address; - (void)peer_port; - NotImplemented_nid_no_patch(__func__); - return 0; -} - int32_t APS5_VABI sceNpSessionSignalingGetLocalNetInfo(int32_t context_id, void* info) { (void)context_id; (void)info; @@ -74,4 +64,15 @@ int32_t APS5_VABI sceNpSessionSignalingGetLocalNetInfo(int32_t context_id, void* return 0; } +APS5_EXPORT("lbXTXRG5nyM", sceNpSessionSignalingUnknown00); +int APS5_VABI sceNpSessionSignalingUnknown00(void) { + NotImplemented_nid_no_patch("lbXTXRG5nyM"); + return 0; +} + +APS5_EXPORT("+Q++Q49a9z8", sceNpSessionSignalingUnknown01); +int APS5_VABI sceNpSessionSignalingUnknown01(void) { + NotImplemented_nid_no_patch("+Q++Q49a9z8"); + return 0; +} } diff --git a/core/libs/prx/libSceNpTrophy2/src/Context.cpp b/core/libs/prx/libSceNpTrophy2/src/Context.cpp index 65a97045b..8af74ba2f 100644 --- a/core/libs/prx/libSceNpTrophy2/src/Context.cpp +++ b/core/libs/prx/libSceNpTrophy2/src/Context.cpp @@ -30,4 +30,10 @@ int APS5_VABI sceNpTrophy2RegisterContext(int context, int handle, uint64_t opti return SCE_NP_TROPHY2_OK; } +APS5_EXPORT("wVqxM58sIKs", sceNpTrophy2Unknown00); +int APS5_VABI sceNpTrophy2Unknown00() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/core/libs/prx/libSceSystemService/Export.cpp b/core/libs/prx/libSceSystemService/Export.cpp index 261f91556..f1d610952 100644 --- a/core/libs/prx/libSceSystemService/Export.cpp +++ b/core/libs/prx/libSceSystemService/Export.cpp @@ -115,6 +115,18 @@ int APS5_VABI sceSystemServiceInitializePlayerDialogParam(void* param) { return SYSTEM_SERVICE_OK; } +APS5_EXPORT("64nkF7LGk8w", sceSystemServiceUnknown00); +int APS5_VABI sceSystemServiceUnknown00() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +APS5_EXPORT("uInYhy7xa+U", sceSystemServiceUnknown01); +int APS5_VABI sceSystemServiceUnknown01() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceSystemServiceLaunchPlayerDialog(const void* param) { if (param == nullptr) return SYSTEM_SERVICE_ERROR_PARAMETER; return SYSTEM_SERVICE_OK; diff --git a/core/libs/prx/libSceVoice/Export.cpp b/core/libs/prx/libSceVoice/Export.cpp index 735f0697e..9324f2405 100644 --- a/core/libs/prx/libSceVoice/Export.cpp +++ b/core/libs/prx/libSceVoice/Export.cpp @@ -204,4 +204,10 @@ int APS5_VABI sceVoiceWriteToIPort(uint32_t input_port_id, const void* data, uin return 0; } +APS5_EXPORT("gwUynkEgNFY", sceVoiceUnknown00); +int APS5_VABI sceVoiceUnknown00() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/core/libs/prx/libc/src/RuntimeSupport.cpp b/core/libs/prx/libc/src/RuntimeSupport.cpp index ef00dd455..7c297c1f3 100644 --- a/core/libs/prx/libc/src/RuntimeSupport.cpp +++ b/core/libs/prx/libc/src/RuntimeSupport.cpp @@ -138,4 +138,10 @@ void APS5_VABI _Unlocksyslock_nid_postfix() { g_sysLock.unlock(); } +APS5_EXPORT("jbj2wBoiCyg", sceLibcUnknown00); +int APS5_VABI sceLibcUnknown00() { + NotImplemented_nid_no_patch("jbj2wBoiCyg"); + return 0; +} + } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 5c87a3d98..8b2a18b7f 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -57,6 +57,16 @@ Throughout the project, every function at every stage either **does exactly what - [SceAvPlayerVideoEx](../../core/libs/SceTypes.hpp) (libSceAvPlayer) - frame rate field and encoding unknown; it is left zero in frame and stream info - [sceAjmDecMp3ParseFrame](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - signature, result layout and rate tables taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation of the PS4 library, not confirmed on a PS5 title: the layer field is not checked and MPEG-2.5 stops at 64 kbps - [AJM Opus decoder](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native, codec 24) - the initialize parameter layout (`u32 channels, u32 sample rate, u32`, seen as `2, 48000, 0`) and the little-endian `u16` byte count before each packet come from one title (The Smurfs Dreams); only 48000 Hz and a zero third word are accepted +- [wVqxM58sIKs](../../core/libs/prx/libSceNpTrophy2/src/Context.cpp) (libSceNpTrophy2) - unknown name, signature +- [gwUynkEgNFY](../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown name, signature +- [hPTXo3bICzI](../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown name, signature +- [otUQuZa-mv0](../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - unknown name, signature +- [64nkF7LGk8w](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown name, signature +- [uInYhy7xa+U](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown name, signature +- [rMyh97BU5pY](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown name, signature +- [jbj2wBoiCyg](../../core/libs/prx/libc/src/RuntimeSupport.cpp) (libc) - unknown name, signature +- [lbXTXRG5nyM](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown name, signature +- [+Q++Q49a9z8](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown name, signature ### Functional From ac7bc704c353c998a64d08993662edf2662f5873 Mon Sep 17 00:00:00 2001 From: Pouare514 Date: Mon, 5 Oct 2026 01:15:06 +0200 Subject: [PATCH 035/164] fix(prx): export tripwire NIDs under recomputed real names, restore main's hunks --- core/libs/prx/libSceHttp/Export.cpp | 11 ++-------- core/libs/prx/libSceHttp2/Export.cpp | 3 +-- core/libs/prx/libSceNet/Export.cpp | 5 ++--- .../prx/libSceNpSessionSignaling/Export.cpp | 20 +++++++++++++------ core/libs/prx/libSceNpTrophy2/src/Context.cpp | 3 +-- core/libs/prx/libSceSystemService/Export.cpp | 6 ++---- core/libs/prx/libSceVoice/Export.cpp | 3 +-- core/libs/prx/libc/src/RuntimeSupport.cpp | 5 ++--- docs/dev/TechnicalDebt.md | 20 +++++++++---------- 9 files changed, 35 insertions(+), 41 deletions(-) diff --git a/core/libs/prx/libSceHttp/Export.cpp b/core/libs/prx/libSceHttp/Export.cpp index 9470bae8a..97a91ff51 100644 --- a/core/libs/prx/libSceHttp/Export.cpp +++ b/core/libs/prx/libSceHttp/Export.cpp @@ -257,15 +257,8 @@ int APS5_VABI sceHttpUnknown01(void) { return 0; } -APS5_EXPORT("vO4B-42ef-k", sceHttpUnknown01); -int APS5_VABI sceHttpUnknown01(void) { - NotImplemented_nid_no_patch("vO4B-42ef-k"); - return 0; -} - -APS5_EXPORT("hPTXo3bICzI", sceHttpUnknown02); -int APS5_VABI sceHttpUnknown02(void) { - NotImplemented_nid_no_patch("hPTXo3bICzI"); +int APS5_VABI sceHttpParseResponseHeader(void) { + NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceHttp2/Export.cpp b/core/libs/prx/libSceHttp2/Export.cpp index f0c2dd9b4..911b65730 100644 --- a/core/libs/prx/libSceHttp2/Export.cpp +++ b/core/libs/prx/libSceHttp2/Export.cpp @@ -209,8 +209,7 @@ int APS5_VABI sceHttp2AbortRequest(int req_id) { return 0; } -APS5_EXPORT("otUQuZa-mv0", sceHttp2Unknown00); -int APS5_VABI sceHttp2Unknown00() { +int APS5_VABI sceHttp2GetMemoryPoolStats() { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index 6e399e44a..19a4b22bd 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -650,9 +650,8 @@ int APS5_VABI sceNetEpollCreate(const char* name, int flags) { return id; } -APS5_EXPORT("rMyh97BU5pY", sceNetUnknown01); -int APS5_VABI sceNetUnknown01() { - NotImplemented_nid_no_patch("rMyh97BU5pY"); +int APS5_VABI sceNetGetMemoryPoolStats() { + NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceNpSessionSignaling/Export.cpp b/core/libs/prx/libSceNpSessionSignaling/Export.cpp index 482d89913..0738a6d64 100644 --- a/core/libs/prx/libSceNpSessionSignaling/Export.cpp +++ b/core/libs/prx/libSceNpSessionSignaling/Export.cpp @@ -57,6 +57,16 @@ int APS5_VABI sceNpSessionSignalingTerminate(void) { return 0; } +int32_t APS5_VABI sceNpSessionSignalingGetConnectionStatus(int32_t context_id, int32_t connection_id, int32_t* status, void* peer_address, uint16_t* peer_port) { + (void)context_id; + (void)connection_id; + (void)status; + (void)peer_address; + (void)peer_port; + NotImplemented_nid_no_patch(__func__); + return 0; +} + int32_t APS5_VABI sceNpSessionSignalingGetLocalNetInfo(int32_t context_id, void* info) { (void)context_id; (void)info; @@ -64,15 +74,13 @@ int32_t APS5_VABI sceNpSessionSignalingGetLocalNetInfo(int32_t context_id, void* return 0; } -APS5_EXPORT("lbXTXRG5nyM", sceNpSessionSignalingUnknown00); -int APS5_VABI sceNpSessionSignalingUnknown00(void) { - NotImplemented_nid_no_patch("lbXTXRG5nyM"); +int APS5_VABI sceNpSessionSignalingGetMemoryInfo(void) { + NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("+Q++Q49a9z8", sceNpSessionSignalingUnknown01); -int APS5_VABI sceNpSessionSignalingUnknown01(void) { - NotImplemented_nid_no_patch("+Q++Q49a9z8"); +int APS5_VABI sceNpSessionSignalingGetConnectionStatistics(void) { + NotImplemented_nid_no_patch(__func__); return 0; } } diff --git a/core/libs/prx/libSceNpTrophy2/src/Context.cpp b/core/libs/prx/libSceNpTrophy2/src/Context.cpp index 8af74ba2f..097a8fe0d 100644 --- a/core/libs/prx/libSceNpTrophy2/src/Context.cpp +++ b/core/libs/prx/libSceNpTrophy2/src/Context.cpp @@ -30,8 +30,7 @@ int APS5_VABI sceNpTrophy2RegisterContext(int context, int handle, uint64_t opti return SCE_NP_TROPHY2_OK; } -APS5_EXPORT("wVqxM58sIKs", sceNpTrophy2Unknown00); -int APS5_VABI sceNpTrophy2Unknown00() { +int APS5_VABI sceNpTrophy2UnregisterUnlockCallback() { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceSystemService/Export.cpp b/core/libs/prx/libSceSystemService/Export.cpp index f1d610952..01a6365a5 100644 --- a/core/libs/prx/libSceSystemService/Export.cpp +++ b/core/libs/prx/libSceSystemService/Export.cpp @@ -115,14 +115,12 @@ int APS5_VABI sceSystemServiceInitializePlayerDialogParam(void* param) { return SYSTEM_SERVICE_OK; } -APS5_EXPORT("64nkF7LGk8w", sceSystemServiceUnknown00); -int APS5_VABI sceSystemServiceUnknown00() { +int APS5_VABI sceSystemServiceDisableMediaPlay() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("uInYhy7xa+U", sceSystemServiceUnknown01); -int APS5_VABI sceSystemServiceUnknown01() { +int APS5_VABI sceSystemServiceReenableMediaPlay() { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceVoice/Export.cpp b/core/libs/prx/libSceVoice/Export.cpp index 9324f2405..0f824d139 100644 --- a/core/libs/prx/libSceVoice/Export.cpp +++ b/core/libs/prx/libSceVoice/Export.cpp @@ -204,8 +204,7 @@ int APS5_VABI sceVoiceWriteToIPort(uint32_t input_port_id, const void* data, uin return 0; } -APS5_EXPORT("gwUynkEgNFY", sceVoiceUnknown00); -int APS5_VABI sceVoiceUnknown00() { +int APS5_VABI sceVoiceSetMuteFlag() { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libc/src/RuntimeSupport.cpp b/core/libs/prx/libc/src/RuntimeSupport.cpp index 7c297c1f3..3fe661094 100644 --- a/core/libs/prx/libc/src/RuntimeSupport.cpp +++ b/core/libs/prx/libc/src/RuntimeSupport.cpp @@ -138,9 +138,8 @@ void APS5_VABI _Unlocksyslock_nid_postfix() { g_sysLock.unlock(); } -APS5_EXPORT("jbj2wBoiCyg", sceLibcUnknown00); -int APS5_VABI sceLibcUnknown00() { - NotImplemented_nid_no_patch("jbj2wBoiCyg"); +int APS5_VABI snwprintf_s() { + NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 8b2a18b7f..855dc2957 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -57,16 +57,16 @@ Throughout the project, every function at every stage either **does exactly what - [SceAvPlayerVideoEx](../../core/libs/SceTypes.hpp) (libSceAvPlayer) - frame rate field and encoding unknown; it is left zero in frame and stream info - [sceAjmDecMp3ParseFrame](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - signature, result layout and rate tables taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation of the PS4 library, not confirmed on a PS5 title: the layer field is not checked and MPEG-2.5 stops at 64 kbps - [AJM Opus decoder](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native, codec 24) - the initialize parameter layout (`u32 channels, u32 sample rate, u32`, seen as `2, 48000, 0`) and the little-endian `u16` byte count before each packet come from one title (The Smurfs Dreams); only 48000 Hz and a zero third word are accepted -- [wVqxM58sIKs](../../core/libs/prx/libSceNpTrophy2/src/Context.cpp) (libSceNpTrophy2) - unknown name, signature -- [gwUynkEgNFY](../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown name, signature -- [hPTXo3bICzI](../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown name, signature -- [otUQuZa-mv0](../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - unknown name, signature -- [64nkF7LGk8w](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown name, signature -- [uInYhy7xa+U](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown name, signature -- [rMyh97BU5pY](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown name, signature -- [jbj2wBoiCyg](../../core/libs/prx/libc/src/RuntimeSupport.cpp) (libc) - unknown name, signature -- [lbXTXRG5nyM](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown name, signature -- [+Q++Q49a9z8](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown name, signature +- [sceHttpParseResponseHeader](../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature +- [sceHttp2GetMemoryPoolStats](../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - unknown signature +- [sceNetGetMemoryPoolStats](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceNpSessionSignalingGetMemoryInfo](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature +- [sceNpSessionSignalingGetConnectionStatistics](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature +- [sceNpTrophy2UnregisterUnlockCallback](../../core/libs/prx/libSceNpTrophy2/src/Context.cpp) (libSceNpTrophy2) - unknown signature +- [sceSystemServiceDisableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature +- [sceSystemServiceReenableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature +- [sceVoiceSetMuteFlag](../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature +- [snwprintf_s](../../core/libs/prx/libc/src/RuntimeSupport.cpp) (libc) - unknown signature ### Functional From da9752bd5fc873f65f8408c5123654011d4d7ed6 Mon Sep 17 00:00:00 2001 From: DotDebian Date: Mon, 5 Oct 2026 02:09:50 +0200 Subject: [PATCH 036/164] feat(shader): implement buffer_atomic_csub MUBUF opcode 0x34 subtracts the data register from a buffer dword and clamps the result to zero, returning the previous value. The form without glc throws: no GFX10 target is known to provide it. Co-Authored-By: Claude Opus 5.5 --- .../tests/execution/BufferAtomicCsub.cpp | 200 ++++++++++++++++++ .../IntermediateRepresentation/IrOpcode.hpp | 1 + .../src/IrOpcode.cpp | 2 + .../include/RdnaDecoder/RdnaOpcode.hpp | 1 + .../RdnaDecoder/src/RdnaMemoryOpDecoder.cpp | 1 + .../recompiler/RdnaDecoder/src/RdnaOpcode.cpp | 1 + .../SpirvBackend/SpirvEmitterInstructions.hpp | 1 + .../SpirvBackend/src/SpirvFlowEmitter.cpp | 1 + .../SpirvMemory/SpirvMemoryInstructions.cpp | 8 + .../MemoryInstructionDispatch.cpp | 5 + 10 files changed, 221 insertions(+) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicCsub.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicCsub.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicCsub.cpp new file mode 100644 index 000000000..aab4cf664 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicCsub.cpp @@ -0,0 +1,200 @@ +#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 MaxThreads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 8; +constexpr std::uint32_t Written = 5; +constexpr std::uint32_t SharedWords = 2; +constexpr std::uint32_t Untouched = 0xdeadbeefu; +constexpr std::uint32_t MaskedExecLo = 0x55555555u; +constexpr std::uint32_t SharedPartial = 0x800186a0u; +constexpr std::uint32_t SharedExhausted = 1000u; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34040085, 0xe0301000, 0x80000401, 0xe0301004, 0x80000501, 0xe0301008, 0x80000601, + 0xbf8c3f70, 0x7e140280, 0x461414c1, 0x481414c1, 0x7e0e0304, 0xe0d05000, 0x80010702, 0x7e100304, + 0xbefe03ff, 0x55555555, 0xe0d05004, 0x80010802, 0xbefe03c1, 0x7e120305, 0xe0d04800, 0x80010900, + 0x7e160306, 0xe0d04804, 0x80010b00, 0xbf8c3f70, 0xe0701008, 0x80010702, 0xe070100c, 0x80010802, + 0xe0701010, 0x80010a02, 0xbf810000, +}; + +alignas(256) constexpr std::array NoReturnCode{ + 0xe0d01000, 0x80010702, 0xbf810000, +}; + +struct Workgroup { + std::uint32_t threads; + std::uint32_t waveSize; + + [[nodiscard]] std::string Name() const { + return "wave" + std::to_string(waveSize) + " " + std::to_string(threads) + " threads"; + } +}; + +constexpr std::array Workgroups{{{32, 32}, {64, 32}, {64, 64}}}; + +struct Row { + std::uint32_t memory; + std::uint32_t subtrahend; +}; + +constexpr std::array Edges{{ + {0u, 0u}, {0u, 1u}, {1u, 0u}, {1u, 1u}, + {5u, 7u}, {7u, 5u}, {0xffffffffu, 1u}, {1u, 0xffffffffu}, + {0x80000000u, 0x7fffffffu}, {0x7fffffffu, 0x80000000u}, {0xffffffffu, 0xffffffffu}, {0x80000000u, 0x80000000u}, + {0x80000001u, 0x80000000u}, {0u, 0xffffffffu}, {0xffffffffu, 0u}, {0x12345678u, 0x12345679u}, +}}; + +Row RowOf(std::uint32_t tid) { + if (tid < Edges.size()) return Edges[tid]; + const std::uint32_t memory = (tid + 1u) * 0x9e3779b9u; + const std::uint32_t subtrahend = (tid + 1u) * 0x85ebca6bu; + return {memory, (tid & 3u) == 0u ? subtrahend >> 8u : subtrahend}; +} + +std::uint32_t SharedPartialStep(std::uint32_t tid) { return tid + 1u; } +std::uint32_t SharedExhaustedStep(std::uint32_t tid) { return tid + 100u; } + +std::uint32_t SubtractClamped(std::uint32_t memory, std::uint32_t subtrahend) { + return memory >= subtrahend ? memory - subtrahend : 0u; +} + +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; +} + +std::vector UserData() { + 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); + return userData; +} + +ShaderRecompiler::RecompileResult Recompile(const AgcDriver::VulkanDevice& device, std::span code, const Workgroup& workgroup, std::span userData) { + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{workgroup.threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {workgroup.waveSize, 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, const Workgroup& workgroup) { + Input.fill(0u); + Output.fill(Untouched); + for (std::uint32_t tid = 0; tid < MaxThreads; ++tid) { + const Row row = RowOf(tid); + Input[tid * Inputs] = row.subtrahend; + Input[tid * Inputs + 1u] = SharedPartialStep(tid); + Input[tid * Inputs + 2u] = SharedExhaustedStep(tid); + Output[tid * Results] = row.memory; + Output[tid * Results + 1u] = row.memory; + } + Output[MaxThreads * Results] = SharedPartial; + Output[MaxThreads * Results + 1u] = SharedExhausted; + const auto userData = UserData(); + const std::span code(Code); + const auto result = Recompile(device, code, workgroup, userData); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check(const Workgroup& workgroup) { + constexpr std::array names{"memory", "memory under a partial exec mask", "returned value", "data register under a partial exec mask"}; + std::uint32_t partialTotal = 0; + std::uint32_t exhaustedTotal = 0; + for (std::uint32_t tid = 0; tid < MaxThreads; ++tid) { + const Row row = RowOf(tid); + const auto* out = &Output[tid * Results]; + const auto where = "buffer atomic csub: " + workgroup.Name() + " thread " + std::to_string(tid); + if (tid >= workgroup.threads) { + Require(out[0] == row.memory && out[1] == row.memory, where + " is outside the workgroup but its memory changed"); + for (std::uint32_t j = 2; j < Results; ++j) { + Require(out[j] == Untouched, where + " is outside the workgroup but result " + std::to_string(j) + " was written"); + } + continue; + } + partialTotal += SharedPartialStep(tid); + exhaustedTotal += SharedExhaustedStep(tid); + const auto lane = out[4]; + Require(lane < workgroup.waveSize, where + " reports lane " + std::to_string(lane)); + const bool active = lane >= 32u || ((MaskedExecLo >> lane) & 1u) != 0u; + const std::uint32_t clamped = SubtractClamped(row.memory, row.subtrahend); + const std::array expected{clamped, active ? clamped : row.memory, row.memory, active ? row.memory : row.subtrahend}; + for (std::uint32_t j = 0; j < expected.size(); ++j) { + Require(out[j] == expected[j], where + " lane " + std::to_string(lane) + " " + names[j] + " is " + Hex(out[j]) + ", expected " + Hex(expected[j])); + } + for (std::uint32_t j = Written; j < Results; ++j) { + Require(out[j] == Untouched, where + " result " + std::to_string(j) + " was written"); + } + } + const std::array shared{SubtractClamped(SharedPartial, partialTotal), SubtractClamped(SharedExhausted, exhaustedTotal)}; + constexpr std::array sharedNames{"dword shared by all lanes", "dword exhausted by all lanes"}; + Require(shared[0] != 0u && shared[1] == 0u, "buffer atomic csub: " + workgroup.Name() + " does not cover both shared outcomes"); + for (std::uint32_t j = 0; j < SharedWords; ++j) { + const auto actual = Output[MaxThreads * Results + j]; + Require(actual == shared[j], "buffer atomic csub: " + workgroup.Name() + " " + sharedNames[j] + " is " + Hex(actual) + ", expected " + Hex(shared[j])); + } +} + +void CheckNoReturnRejected(const AgcDriver::VulkanDevice& device) { + const auto userData = UserData(); + const std::string reason = "buffer_atomic_csub without glc is not supported"; + std::string failure; + try { + static_cast(Recompile(device, NoReturnCode, Workgroups[0], userData)); + } catch (const std::exception& error) { + failure = error.what(); + } + Require(failure.find(reason) != std::string::npos, "buffer atomic csub: expected '" + reason + "', got '" + failure + "'"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + for (const auto& workgroup : Workgroups) { + Run(*device, workgroup); + Check(workgroup); + } + CheckNoReturnRejected(*device); + std::puts("buffer atomic csub tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp index fefa0c3cb..0cc18d8dd 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp @@ -317,6 +317,7 @@ enum class IrOpcode : std::uint16_t { BufferAtomicFMax32, BufferAtomicInc32, BufferAtomicDec32, + BufferAtomicUSubSat32, BufferAtomicIAdd64, BufferAtomicISub64, BufferAtomicSMin64, diff --git a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp index c9b3e81ab..acc719613 100644 --- a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp +++ b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp @@ -358,6 +358,7 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("BufferAtomicFMax32", U32, BufferResource, U32, U32, U32, U32, U1), makeMeta("BufferAtomicInc32", U32, BufferResource, U32, U32, U32, U32, U1), makeMeta("BufferAtomicDec32", U32, BufferResource, U32, U32, U32, U32, U1), + makeMeta("BufferAtomicUSubSat32", U32, BufferResource, U32, U32, U32, U32, U1), makeMeta("BufferAtomicIAdd64", U64, BufferResource, U32, U32, U32, U64, U1), makeMeta("BufferAtomicISub64", U64, BufferResource, U32, U32, U32, U64, U1), makeMeta("BufferAtomicSMin64", U64, BufferResource, U32, U32, U32, U64, U1), @@ -504,6 +505,7 @@ BufferAccess BufferAccessOf(IrOpcode opcode) { case IrOpcode::BufferAtomicFMax32: case IrOpcode::BufferAtomicInc32: case IrOpcode::BufferAtomicDec32: + case IrOpcode::BufferAtomicUSubSat32: case IrOpcode::BufferAtomicIAdd64: case IrOpcode::BufferAtomicISub64: case IrOpcode::BufferAtomicSMin64: diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp index 5ed43f202..a0f42298c 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -744,6 +744,7 @@ enum class RdnaOpcode : std::uint16_t { BufferAtomicXor, BufferAtomicFmin, BufferAtomicFmax, + BufferAtomicCsub, FlatLoadUbyte, FlatLoadSbyte, FlatLoadUshort, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp index 3d40b4cf9..cf8a77861 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp @@ -102,6 +102,7 @@ constexpr MemoryOpcodeInfo mubufOpcodes[] = { {0x31u, RdnaOpcode::BufferAtomicCmpswap, 1, 32, false, false, false}, {0x32u, RdnaOpcode::BufferAtomicAdd, 1, 32, false, false, false}, {0x33u, RdnaOpcode::BufferAtomicSub, 1, 32, false, false, false}, + {0x34u, RdnaOpcode::BufferAtomicCsub, 1, 32, false, false, false}, {0x35u, RdnaOpcode::BufferAtomicSmin, 1, 32, false, false, false}, {0x36u, RdnaOpcode::BufferAtomicUmin, 1, 32, false, false, false}, {0x37u, RdnaOpcode::BufferAtomicSmax, 1, 32, false, false, false}, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp index 4b0069c92..e6c01972a 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp @@ -712,6 +712,7 @@ bool IsBufferMemoryOpcode(RdnaOpcode opcode) { case RdnaOpcode::BufferAtomicXor: case RdnaOpcode::BufferAtomicFmin: case RdnaOpcode::BufferAtomicFmax: + case RdnaOpcode::BufferAtomicCsub: case RdnaOpcode::TbufferLoadFormatX: case RdnaOpcode::TbufferLoadFormatXy: case RdnaOpcode::TbufferLoadFormatXyz: diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp index d89f236b3..47c5a8a19 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp @@ -263,6 +263,7 @@ std::uint32_t EmitBufferAtomicOr32(SpirvValueEmitContext& ctx, const IrValue& in std::uint32_t EmitBufferAtomicOr64(SpirvValueEmitContext& ctx, const IrValue& inst); std::uint32_t EmitBufferAtomicInc32(SpirvValueEmitContext& ctx, const IrValue& inst); std::uint32_t EmitBufferAtomicDec32(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitBufferAtomicUSubSat32(SpirvValueEmitContext& ctx, const IrValue& inst); std::uint32_t EmitBufferAtomicIAdd64(SpirvValueEmitContext& ctx, const IrValue& inst); std::uint32_t EmitBufferAtomicISub64(SpirvValueEmitContext& ctx, const IrValue& inst); std::uint32_t EmitBufferAtomicSMin64(SpirvValueEmitContext& ctx, const IrValue& inst); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp index eac7c878b..6395f472c 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp @@ -488,6 +488,7 @@ void EmitDirectInstruction(SpirvValueEmitContext& ctx, const IrValue& inst) { case IrOpcode::BufferAtomicOr64: return Invoke(EmitBufferAtomicOr64, ctx, inst); case IrOpcode::BufferAtomicInc32: return Invoke(EmitBufferAtomicInc32, ctx, inst); case IrOpcode::BufferAtomicDec32: return Invoke(EmitBufferAtomicDec32, ctx, inst); + case IrOpcode::BufferAtomicUSubSat32: return Invoke(EmitBufferAtomicUSubSat32, ctx, inst); case IrOpcode::BufferAtomicIAdd64: return Invoke(EmitBufferAtomicIAdd64, ctx, inst); case IrOpcode::BufferAtomicISub64: return Invoke(EmitBufferAtomicISub64, ctx, inst); case IrOpcode::BufferAtomicSMin64: return Invoke(EmitBufferAtomicSMin64, ctx, inst); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp index 23216880a..906172f2a 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp @@ -1507,6 +1507,14 @@ std::uint32_t EmitBufferAtomicDec32(SpirvValueEmitContext& ctx, const IrValue& i return EmitAtomicUpdate(ctx, inst, BufferMemory(ctx, inst), AtomicDecrement); } +std::uint32_t EmitBufferAtomicUSubSat32(SpirvValueEmitContext& ctx, const IrValue& inst) { + return EmitAtomicUpdate(ctx, inst, BufferMemory(ctx, inst), [](SpirvEmitterState& state, std::uint32_t old, std::uint32_t value) { + const auto fits = Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), old, value); + const auto difference = Binary(state, spv::OpISub, TypeU32(state), old, value); + return Select(state, TypeU32(state), fits, difference, ConstantU32(state, 0u)); + }); +} + std::uint32_t EmitBufferAtomicIAdd64(SpirvValueEmitContext& ctx, const IrValue& inst) { return BufferAtomic64(ctx, inst); } diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp index 1031f7aa8..dd22a7b5c 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp @@ -117,6 +117,11 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) { return bufferAtomic(inst, IrOpcode::BufferAtomicIAdd32); case RdnaOpcode::BufferAtomicSub: return bufferAtomic(inst, IrOpcode::BufferAtomicISub32); + case RdnaOpcode::BufferAtomicCsub: + if (!inst.glc) { + throw std::runtime_error("buffer_atomic_csub without glc is not supported, at pc " + std::to_string(inst.programCounter)); + } + return bufferAtomic(inst, IrOpcode::BufferAtomicUSubSat32); case RdnaOpcode::BufferAtomicSmin: return bufferAtomic(inst, IrOpcode::BufferAtomicSMin32); case RdnaOpcode::BufferAtomicUmin: From 20cbc8e306cc1db8e9b8368155daf0ff81e263df Mon Sep 17 00:00:00 2001 From: Pouare514 Date: Mon, 5 Oct 2026 02:18:52 +0200 Subject: [PATCH 037/164] fix(prx): keep main's GetConnectionStatus body and Http blank line untouched --- core/libs/prx/libSceHttp/Export.cpp | 1 + core/libs/prx/libSceNpSessionSignaling/Export.cpp | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceHttp/Export.cpp b/core/libs/prx/libSceHttp/Export.cpp index 97a91ff51..d84ed5a4c 100644 --- a/core/libs/prx/libSceHttp/Export.cpp +++ b/core/libs/prx/libSceHttp/Export.cpp @@ -245,6 +245,7 @@ int APS5_VABI sceHttpSetChunkedTransferEnabled(int id, int enable) { return 0; } + APS5_EXPORT("i9mhafzkEi8", sceHttpUnknown00); int APS5_VABI sceHttpUnknown00(void) { NotImplemented_nid_no_patch("i9mhafzkEi8"); diff --git a/core/libs/prx/libSceNpSessionSignaling/Export.cpp b/core/libs/prx/libSceNpSessionSignaling/Export.cpp index 0738a6d64..d140181b6 100644 --- a/core/libs/prx/libSceNpSessionSignaling/Export.cpp +++ b/core/libs/prx/libSceNpSessionSignaling/Export.cpp @@ -58,13 +58,13 @@ int APS5_VABI sceNpSessionSignalingTerminate(void) { } int32_t APS5_VABI sceNpSessionSignalingGetConnectionStatus(int32_t context_id, int32_t connection_id, int32_t* status, void* peer_address, uint16_t* peer_port) { - (void)context_id; - (void)connection_id; - (void)status; - (void)peer_address; - (void)peer_port; - NotImplemented_nid_no_patch(__func__); - return 0; + (void)context_id; + (void)connection_id; + (void)status; + (void)peer_address; + (void)peer_port; + NotImplemented_nid_no_patch(__func__); + return 0; } int32_t APS5_VABI sceNpSessionSignalingGetLocalNetInfo(int32_t context_id, void* info) { From 2771eeea775a9a14656269817603a6d856bf5838 Mon Sep 17 00:00:00 2001 From: raghav314 Date: Mon, 5 Oct 2026 05:13:03 +0530 Subject: [PATCH 038/164] feat(ajm): implement DecodeSingle, GetGaplessDecode and GetCodecInfo sceAjmBatchJobDecodeSingle runs a single-frame decode job with the stream sideband. sceAjmBatchJobGetGaplessDecode and sceAjmBatchJobGetCodecInfo append run jobs without buffers that return the gapless-decode and codec-info sidebands. A run job without input or output buffers no longer decodes: it reports the instance state with a zero result instead of PARTIAL_INPUT on MP3 and Opus. RUN_GET_CODEC_INFO (bit 11) writes the ATRAC9 codec info after the gapless sideband. Codec info for other codecs, and codec info together with RUN_MULTIPLE_FRAMES, throw. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAjm.native/Export.cpp | 29 ----- core/libs/prx/libSceAjm.native/src/Ajm.cpp | 57 ++++++++- core/libs/tests/GuestAjm.cpp | 129 +++++++++++++++++++++ 3 files changed, 180 insertions(+), 35 deletions(-) diff --git a/core/libs/prx/libSceAjm.native/Export.cpp b/core/libs/prx/libSceAjm.native/Export.cpp index 11bc7e1b7..7fe1d9502 100644 --- a/core/libs/prx/libSceAjm.native/Export.cpp +++ b/core/libs/prx/libSceAjm.native/Export.cpp @@ -33,18 +33,6 @@ int APS5_VABI sceAjmBatchJobControl(AjmBatchInfo* info, uint32_t instance, uint6 return 0; } -int APS5_VABI sceAjmBatchJobDecodeSingle(AjmBatchInfo* info, uint32_t instance, const void* bitstream_input, size_t bitstream_input_size, void* pcm_output, size_t pcm_output_size, void* result) { - (void)info; - (void)instance; - (void)bitstream_input; - (void)bitstream_input_size; - (void)pcm_output; - (void)pcm_output_size; - (void)result; - AjmStub(__func__); - return 0; -} - int APS5_VABI sceAjmBatchJobDecodeSplit(AjmBatchInfo* info, uint32_t instance, const AjmBuffer* input_buffers, size_t input_buffers_num, const AjmBuffer* output_buffers, size_t output_buffers_num, void* result) { (void)info; (void)instance; @@ -69,23 +57,6 @@ int APS5_VABI sceAjmBatchJobEncode(AjmBatchInfo* info, uint32_t instance, const return 0; } -int APS5_VABI sceAjmBatchJobGetCodecInfo(AjmBatchInfo* info, uint32_t instance, void* result, size_t result_size) { - (void)info; - (void)instance; - (void)result; - (void)result_size; - AjmStub(__func__); - return 0; -} - -int APS5_VABI sceAjmBatchJobGetGaplessDecode(AjmBatchInfo* info, uint32_t instance, void* result) { - (void)info; - (void)instance; - (void)result; - AjmStub(__func__); - return 0; -} - int APS5_VABI sceAjmBatchJobGetInfo(AjmBatchInfo* info, uint32_t instance, void* result) { (void)info; (void)instance; diff --git a/core/libs/prx/libSceAjm.native/src/Ajm.cpp b/core/libs/prx/libSceAjm.native/src/Ajm.cpp index 5678a519b..50cbee420 100644 --- a/core/libs/prx/libSceAjm.native/src/Ajm.cpp +++ b/core/libs/prx/libSceAjm.native/src/Ajm.cpp @@ -51,6 +51,7 @@ bool TraceEnabled() { if (TraceEnabled()) std::fprintf(stderr, __VA_ARGS__); \ } while (0) +constexpr std::uint64_t RUN_GET_CODEC_INFO = 1ull << 11; constexpr std::uint64_t RUN_MULTIPLE_FRAMES = 1ull << 12; constexpr std::uint64_t SIDEBAND_GAPLESS_DECODE = 1ull << 45; constexpr std::uint64_t SIDEBAND_FORMAT = 1ull << 46; @@ -87,6 +88,13 @@ struct SidebandMultipleFrames { std::uint32_t reserved; }; +struct SidebandAt9CodecInfo { + std::uint32_t superframeSize; + std::uint32_t framesInSuperframe; + std::uint32_t nextFrameSize; + std::uint32_t frameSamples; +}; + struct Instance { std::uint32_t codec = 0; std::uint64_t flags = 0; @@ -337,6 +345,20 @@ void EmitFrame(Instance& instance, PcmOutputs& outputs, const std::uint8_t* pcm, instance.totalDecodedSamples += count; } +SidebandFormat CurrentFormat(const Instance& instance) { + switch (instance.codec) { + case CODEC_AT9: return {static_cast(instance.info.channels), ChannelMask(static_cast(instance.info.channels)), static_cast(instance.info.samplingRate), PcmEncoding(instance), 0, 0}; + case CODEC_MP3: return {instance.mp3Channels, ChannelMask(instance.mp3Channels), instance.mp3SampleRate, PcmEncoding(instance), instance.mp3Bitrate, 0}; + case CODEC_OPUS: return {instance.opusChannels, ChannelMask(instance.opusChannels), instance.opusSampleRate, PcmEncoding(instance), 0, 0}; + default: throw std::runtime_error("AJM: the format of codec " + std::to_string(instance.codec) + " is not implemented"); + } +} + +SidebandAt9CodecInfo At9CodecInfo(const Instance& instance) { + const auto superframeSize = static_cast(instance.info.superframeSize); + return {superframeSize, static_cast(instance.info.framesInSuperframe), instance.superframeRemaining == 0 ? superframeSize : instance.superframeRemaining, static_cast(instance.info.frameSamples)}; +} + void WriteRunSideband(const JobHeader& job, const Instance& instance, std::int32_t result, std::size_t consumed, std::size_t produced, std::uint32_t frames, const SidebandFormat& format) { auto* sideband = static_cast(job.sideband); std::size_t offset = 0; @@ -353,6 +375,10 @@ void WriteRunSideband(const JobHeader& job, const Instance& instance, std::int32 } if (job.flags & SIDEBAND_FORMAT) write(&format, sizeof(format)); if (job.flags & SIDEBAND_GAPLESS_DECODE) write(&instance.gapless, sizeof(instance.gapless)); + if (job.flags & RUN_GET_CODEC_INFO) { + const SidebandAt9CodecInfo codecInfo = At9CodecInfo(instance); + write(&codecInfo, sizeof(codecInfo)); + } if (job.flags & RUN_MULTIPLE_FRAMES) { const SidebandMultipleFrames multiple{frames, 0}; write(&multiple, sizeof(multiple)); @@ -431,7 +457,7 @@ void RunAt9(Instance& instance, const JobHeader& job, const AjmBuffer* inputs, c for (std::size_t i = 0; i < input.size() && i < 16; ++i) std::fprintf(stderr, " %02x", input[i]); std::fprintf(stderr, "\n"); } - WriteRunSideband(job, instance, result, consumed, pcmOutputs.produced, frames, SidebandFormat{static_cast(channels), ChannelMask(channels), static_cast(instance.info.samplingRate), encoding, 0, 0}); + WriteRunSideband(job, instance, result, consumed, pcmOutputs.produced, frames, CurrentFormat(instance)); } struct Mp3Frame { @@ -546,7 +572,7 @@ void RunMp3(Instance& instance, const JobHeader& job, const AjmBuffer* inputs, c AJM_TRACE("[ajm] instance %u mp3 run flags 0x%llx: %zu input bytes, %zu output bytes -> result 0x%x, %u frames, consumed %zu, produced %zu, %u channels, %u Hz, %u bps, total samples %llu\n", job.instance, static_cast(job.flags), input.size(), pcmOutputs.capacity, static_cast(result), frames, consumed, pcmOutputs.produced, instance.mp3Channels, instance.mp3SampleRate, instance.mp3Bitrate, static_cast(instance.totalDecodedSamples)); - WriteRunSideband(job, instance, result, consumed, pcmOutputs.produced, frames, SidebandFormat{instance.mp3Channels, ChannelMask(instance.mp3Channels), instance.mp3SampleRate, encoding, instance.mp3Bitrate, 0}); + WriteRunSideband(job, instance, result, consumed, pcmOutputs.produced, frames, CurrentFormat(instance)); } void OpenOpus(Instance& instance) { static const bool quiet = (av_log_set_level(TraceEnabled() ? AV_LOG_WARNING : AV_LOG_QUIET), true); @@ -682,7 +708,7 @@ void RunOpus(Instance& instance, const JobHeader& job, const AjmBuffer* inputs, if (consumed == input.size() && frames == 0 && pcmOutputs.produced == 0 && result == 0) result |= AJM_RESULT_PARTIAL_INPUT; AJM_TRACE("[ajm] instance %u opus run flags 0x%llx: %zu input bytes, %zu output bytes -> result 0x%x, %u frames, consumed %zu, produced %zu, total samples %llu\n", job.instance, static_cast(job.flags), input.size(), pcmOutputs.capacity, static_cast(result), frames, consumed, pcmOutputs.produced, static_cast(instance.totalDecodedSamples)); - WriteRunSideband(job, instance, result, consumed, pcmOutputs.produced, frames, SidebandFormat{instance.opusChannels, ChannelMask(instance.opusChannels), instance.opusSampleRate, encoding, 0, 0}); + WriteRunSideband(job, instance, result, consumed, pcmOutputs.produced, frames, CurrentFormat(instance)); } void Execute(const JobHeader& job, const AjmBuffer* inputs, const AjmBuffer* outputs) { @@ -729,14 +755,21 @@ void Execute(const JobHeader& job, const AjmBuffer* inputs, const AjmBuffer* out if (!instance->initialized) { AJM_TRACE("[ajm] instance %u run before initialize\n", job.instance); WriteResult(job.sideband, job.sidebandSize, AJM_RESULT_NOT_INITIALIZED); + } else if (instance->codec != CODEC_AT9 && instance->codec != CODEC_MP3 && instance->codec != CODEC_OPUS) { + throw std::runtime_error("AJM: decoding codec " + std::to_string(instance->codec) + " is not implemented"); + } else if ((job.flags & RUN_GET_CODEC_INFO) && (job.flags & RUN_MULTIPLE_FRAMES)) { + NotImplemented_nid_no_patch("AJM run job with both RUN_GET_CODEC_INFO and RUN_MULTIPLE_FRAMES (sideband order)"); + } else if ((job.flags & RUN_GET_CODEC_INFO) && instance->codec != CODEC_AT9) { + NotImplemented_nid_no_patch("AJM RUN_GET_CODEC_INFO for a codec other than ATRAC9"); + } else if (job.inputCount == 0 && job.outputCount == 0) { + AJM_TRACE("[ajm] instance %u run flags 0x%llx without buffers, sideband %llu bytes\n", job.instance, static_cast(job.flags), static_cast(job.sidebandSize)); + WriteRunSideband(job, *instance, 0, 0, 0, 0, CurrentFormat(*instance)); } else if (instance->codec == CODEC_AT9) { RunAt9(*instance, job, inputs, outputs); } else if (instance->codec == CODEC_MP3) { RunMp3(*instance, job, inputs, outputs); - } else if (instance->codec == CODEC_OPUS) { - RunOpus(*instance, job, inputs, outputs); } else { - throw std::runtime_error("AJM: decoding codec " + std::to_string(instance->codec) + " is not implemented"); + RunOpus(*instance, job, inputs, outputs); } break; default: @@ -907,6 +940,18 @@ int APS5_VABI sceAjmBatchJobDecode(AjmBatchInfo* info, uint32_t instance, const return sceAjmBatchJobRun(info, instance, SIDEBAND_STREAM, bitstream_input, bitstream_input_size, pcm_output, pcm_output_size, result, sizeof(SidebandResult) + sizeof(SidebandStream)); } +int APS5_VABI sceAjmBatchJobDecodeSingle(AjmBatchInfo* info, uint32_t instance, const void* bitstream_input, size_t bitstream_input_size, void* pcm_output, size_t pcm_output_size, void* result) { + return sceAjmBatchJobRun(info, instance, SIDEBAND_STREAM, bitstream_input, bitstream_input_size, pcm_output, pcm_output_size, result, sizeof(SidebandResult) + sizeof(SidebandStream)); +} + +int APS5_VABI sceAjmBatchJobGetGaplessDecode(AjmBatchInfo* info, uint32_t instance, void* result) { + return sceAjmBatchJobRunSplit(info, instance, SIDEBAND_GAPLESS_DECODE, nullptr, 0, nullptr, 0, result, sizeof(SidebandResult) + sizeof(SidebandGaplessDecode)); +} + +int APS5_VABI sceAjmBatchJobGetCodecInfo(AjmBatchInfo* info, uint32_t instance, void* result, size_t result_size) { + return sceAjmBatchJobRunSplit(info, instance, RUN_GET_CODEC_INFO, nullptr, 0, nullptr, 0, result, result_size); +} + int APS5_VABI sceAjmBatchJobGetStatistics(AjmBatchInfo* info, float interval, void* result) { (void)interval; return Append(info, MakeHeader(JobKind::GetStatistics, 0, result, 24), nullptr, nullptr); diff --git a/core/libs/tests/GuestAjm.cpp b/core/libs/tests/GuestAjm.cpp index 590e6b9ab..8d8504257 100644 --- a/core/libs/tests/GuestAjm.cpp +++ b/core/libs/tests/GuestAjm.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include extern "C" { @@ -17,6 +18,11 @@ int APS5_VABI sceAjmInstanceDestroy(std::uint32_t, std::uint32_t); int APS5_VABI sceAjmBatchInitialize(void*, std::size_t, AjmBatchInfo*); int APS5_VABI sceAjmBatchJobInitialize(AjmBatchInfo*, std::uint32_t, const void*, std::size_t, void*); int APS5_VABI sceAjmBatchJobDecode(AjmBatchInfo*, std::uint32_t, const void*, std::size_t, void*, std::size_t, void*); +int APS5_VABI sceAjmBatchJobDecodeSingle(AjmBatchInfo*, std::uint32_t, const void*, std::size_t, void*, std::size_t, void*); +int APS5_VABI sceAjmBatchJobRun(AjmBatchInfo*, std::uint32_t, std::uint64_t, const void*, std::size_t, void*, std::size_t, void*, std::size_t); +int APS5_VABI sceAjmBatchJobSetGaplessDecode(AjmBatchInfo*, std::uint32_t, const void*, int, void*); +int APS5_VABI sceAjmBatchJobGetGaplessDecode(AjmBatchInfo*, std::uint32_t, void*); +int APS5_VABI sceAjmBatchJobGetCodecInfo(AjmBatchInfo*, std::uint32_t, void*, std::size_t); int APS5_VABI sceAjmBatchStart(std::uint32_t, const AjmBatchInfo*, int, AjmBatchError*, std::uint32_t*); int APS5_VABI sceAjmBatchWait(std::uint32_t, std::uint32_t, std::uint32_t, AjmBatchError*); } @@ -195,6 +201,126 @@ void TestMp3ParseFrame() { Require(sceAjmDecMp3ParseFrame(mpeg25Above64Kbps, 4, 0, &frame) == invalidParameter); } +struct GaplessDecode { + std::uint32_t totalSamples; + std::uint16_t skipSamples; + std::uint16_t skippedSamples; +}; + +struct GaplessSideband { + std::int32_t result; + std::int32_t internalResult; + std::uint32_t totalSamples; + std::uint16_t skipSamples; + std::uint16_t skippedSamples; +}; + +struct At9CodecInfoSideband { + std::int32_t result; + std::int32_t internalResult; + std::uint32_t superframeSize; + std::uint32_t framesInSuperframe; + std::uint32_t nextFrameSize; + std::uint32_t frameSamples; +}; + +void Submit(std::uint32_t context, const AjmBatchInfo& info) { + std::uint32_t id = 0; + AjmBatchError error{}; + Require(sceAjmBatchStart(context, &info, 0, &error, &id) == 0 && sceAjmBatchWait(context, id, 0, &error) == 0); +} + +bool Refused(std::uint32_t context, const AjmBatchInfo& info) { + std::uint32_t id = 0; + AjmBatchError error{}; + try { + static_cast(sceAjmBatchStart(context, &info, 0, &error, &id)); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +void TestDecodeSingle(std::uint32_t context) { + std::uint32_t instance = 0; + Require(sceAjmInstanceCreate(context, 0, 0, &instance) == 0); + std::vector batch(0x40); + std::vector pcm(1152 * 6); + AjmBatchInfo info{}; + DecodeSideband sideband{}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobDecodeSingle(&info, instance, MP3_MONO, sizeof(MP3_MONO), pcm.data(), pcm.size() * sizeof(std::int16_t), &sideband) == 0); + Submit(context, info); + Require(sideband.result == 0 && sideband.inputConsumed == 96 && sideband.outputWritten == 1152 * 2 && sideband.totalDecodedSamples == 1152); + Require(sceAjmInstanceDestroy(context, instance) == 0); +} + +void TestGaplessDecode(std::uint32_t context) { + std::uint32_t instance = 0; + Require(sceAjmInstanceCreate(context, 0, 0, &instance) == 0); + std::vector batch(4096); + const GaplessDecode gapless{2000, 100, 0}; + AjmBatchInfo info{}; + std::int32_t setResult[2] = {-1, -1}; + GaplessSideband before{-1, -1, 0, 0, 0xffff}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobSetGaplessDecode(&info, instance, &gapless, 1, setResult) == 0); + Require(sceAjmBatchJobGetGaplessDecode(&info, instance, &before) == 0); + Submit(context, info); + Require(setResult[0] == 0); + Require(before.result == 0 && before.internalResult == 0 && before.totalSamples == 2000 && before.skipSamples == 100 && before.skippedSamples == 0); + + std::vector pcm(1152); + DecodeSideband decoded{}; + GaplessSideband after{-1, -1, 0, 0, 0}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobDecodeSingle(&info, instance, MP3_MONO, sizeof(MP3_MONO), pcm.data(), pcm.size() * sizeof(std::int16_t), &decoded) == 0); + Require(sceAjmBatchJobGetGaplessDecode(&info, instance, &after) == 0); + Submit(context, info); + Require(decoded.result == 0 && decoded.inputConsumed == 96 && decoded.outputWritten == (1152 - 100) * 2); + Require(after.result == 0 && after.skippedSamples == 100); + Require(sceAjmInstanceDestroy(context, instance) == 0); +} + +void TestCodecInfo(std::uint32_t context) { + std::uint32_t instance = 0; + Require(sceAjmInstanceCreate(context, 1, 0, &instance) == 0); + std::vector batch(4096); + AjmBatchInfo info{}; + At9CodecInfoSideband early{-1, -1, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobGetCodecInfo(&info, instance, &early, sizeof(early)) == 0); + Submit(context, info); + Require(early.result == 1 && early.superframeSize == 0xaaaaaaaau); + + const std::uint8_t config[8] = {0xFE, 0x72, 0x1F, 0xF0}; + std::int32_t initResult[2] = {-1, -1}; + At9CodecInfoSideband codec{-1, -1, 0, 0, 0, 0}; + At9CodecInfoSideband bounded{-1, -1, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobInitialize(&info, instance, config, sizeof(config), initResult) == 0); + Require(sceAjmBatchJobGetCodecInfo(&info, instance, &codec, sizeof(codec)) == 0); + Require(sceAjmBatchJobGetCodecInfo(&info, instance, &bounded, 2 * sizeof(std::int32_t)) == 0); + Submit(context, info); + Require(initResult[0] == 0); + Require(codec.result == 0 && codec.internalResult == 0 && codec.superframeSize == 1024 && codec.framesInSuperframe == 4 && codec.nextFrameSize == 1024 && codec.frameSamples == 256); + Require(bounded.result == 0 && bounded.superframeSize == 0xaaaaaaaau && bounded.frameSamples == 0xaaaaaaaau); + + constexpr std::uint64_t runGetCodecInfo = 1ull << 11; + constexpr std::uint64_t runMultipleFrames = 1ull << 12; + std::uint8_t sideband[64] = {}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobRun(&info, instance, runGetCodecInfo | runMultipleFrames, nullptr, 0, nullptr, 0, sideband, sizeof(sideband)) == 0); + Require(Refused(context, info)); + Require(sceAjmInstanceDestroy(context, instance) == 0); + + Require(sceAjmInstanceCreate(context, 0, 0, &instance) == 0); + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobGetCodecInfo(&info, instance, sideband, sizeof(sideband)) == 0); + Require(Refused(context, info)); + Require(sceAjmInstanceDestroy(context, instance) == 0); +} + } int main() { @@ -214,5 +340,8 @@ int main() { TestMp3ParseFrame(); TestMp3(context); TestOpus(context); + TestDecodeSingle(context); + TestGaplessDecode(context); + TestCodecInfo(context); Require(sceAjmFinalize(context) == 0); } From 774b34986cd0b75dbcf6a35662ccb85b4f6cdad7 Mon Sep 17 00:00:00 2001 From: DotDebian Date: Fri, 2 Oct 2026 15:28:01 +0200 Subject: [PATCH 039/164] feat(libkernel): implement sceKernelMlock Lock the guest pages of the range with VirtualLock or mlock, following FreeBSD mlock(2): the range is widened to guest pages, a zero length succeeds, a wrapping range is EINVAL and a range that is not mapped and accessible is ENOMEM. The Windows working set limits and the Linux RLIMIT_MEMLOCK soft limit are raised when the lock needs it. Co-Authored-By: Claude Opus 5.5 --- .../prx/libkernel/DirectMemory/Export.cpp | 73 ++++++++++++++++++- core/libs/tests/GuestMemory.cpp | 53 ++++++++++++++ 2 files changed, 122 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libkernel/DirectMemory/Export.cpp b/core/libs/prx/libkernel/DirectMemory/Export.cpp index 5f26acc79..373de6c2a 100644 --- a/core/libs/prx/libkernel/DirectMemory/Export.cpp +++ b/core/libs/prx/libkernel/DirectMemory/Export.cpp @@ -10,12 +10,17 @@ #define NOMINMAX #include #else +#include #include #include +#include +#include #endif #include +#include #include #include +#include #include #include #include @@ -405,13 +410,73 @@ int APS5_VABI sceKernelBatchMap2(KernelBatchMapEntry* entries, int num_entries, } +namespace { + +#ifdef _WIN32 +std::mutex g_workingSetLock; + +bool HostRangeAccessible(std::uintptr_t start, std::uintptr_t end) { + for (auto cursor = start; cursor < end;) { + MEMORY_BASIC_INFORMATION region{}; + if (VirtualQuery(reinterpret_cast(cursor), ®ion, sizeof(region)) != sizeof(region)) return false; + if (region.State != MEM_COMMIT || (region.Protect & (PAGE_NOACCESS | PAGE_GUARD)) != 0) return false; + cursor = reinterpret_cast(region.BaseAddress) + region.RegionSize; + } + return true; +} + +int LockHostPages(std::uintptr_t start, std::uintptr_t end) { + if (!HostRangeAccessible(start, end)) return SCE_KERNEL_ERROR_ENOMEM; + auto* const address = reinterpret_cast(start); + const SIZE_T bytes = end - start; + std::lock_guard lock(g_workingSetLock); + if (VirtualLock(address, bytes)) return 0; + if (GetLastError() != ERROR_WORKING_SET_QUOTA) throw std::system_error(static_cast(GetLastError()), std::system_category(), "VirtualLock failed"); + SIZE_T minimum = 0; + SIZE_T maximum = 0; + DWORD flags = 0; + if (!GetProcessWorkingSetSizeEx(GetCurrentProcess(), &minimum, &maximum, &flags)) throw std::system_error(static_cast(GetLastError()), std::system_category(), "GetProcessWorkingSetSizeEx failed"); + if (bytes > std::numeric_limits::max() - maximum) return SCE_KERNEL_ERROR_EAGAIN; + if (!SetProcessWorkingSetSizeEx(GetCurrentProcess(), minimum + bytes, maximum + bytes, flags)) return SCE_KERNEL_ERROR_EAGAIN; + if (VirtualLock(address, bytes)) return 0; + if (GetLastError() != ERROR_WORKING_SET_QUOTA) throw std::system_error(static_cast(GetLastError()), std::system_category(), "VirtualLock failed"); + return SCE_KERNEL_ERROR_EAGAIN; +} +#else +int LockHostPages(std::uintptr_t start, std::uintptr_t end) { + auto* const address = reinterpret_cast(start); + const std::size_t bytes = end - start; + if (::mlock(address, bytes) == 0) return 0; + int error = errno; + rlimit limit{}; + if ((error == ENOMEM || error == EPERM || error == EAGAIN) && getrlimit(RLIMIT_MEMLOCK, &limit) == 0 && limit.rlim_cur < limit.rlim_max) { + limit.rlim_cur = limit.rlim_max; + if (setrlimit(RLIMIT_MEMLOCK, &limit) == 0) { + if (::mlock(address, bytes) == 0) return 0; + error = errno; + } + } + switch (error) { + case ENOMEM: return SCE_KERNEL_ERROR_ENOMEM; + case EPERM: return SCE_KERNEL_ERROR_EPERM; + case EAGAIN: return SCE_KERNEL_ERROR_EAGAIN; + default: throw std::system_error(error, std::generic_category(), "mlock failed"); + } +} +#endif + +} + extern "C" { int APS5_VABI sceKernelMlock_nid_postfix(void* address, std::uint64_t length) { - (void)address; - (void)length; - NotImplemented_nid_no_patch(__func__); - return 0; + constexpr std::uintptr_t pageMask = PS5_PAGE_SIZE - 1; + const auto first = reinterpret_cast(address); + if (length > UINTPTR_MAX - first || first + length > UINTPTR_MAX - pageMask) return SCE_KERNEL_ERROR_EINVAL; + const auto start = first & ~pageMask; + const auto end = (first + length + pageMask) & ~pageMask; + if (start == end) return 0; + return LockHostPages(start, end); } } diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp index d7dd2ff78..1b8990cdf 100644 --- a/core/libs/tests/GuestMemory.cpp +++ b/core/libs/tests/GuestMemory.cpp @@ -28,6 +28,7 @@ #include #endif #ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN #include #endif @@ -57,6 +58,7 @@ int APS5_VABI sceKernelAioInitializeImpl(void*, std::int32_t); int APS5_VABI sceKernelAioSubmitReadCommands(KernelAioRwRequest*, std::int32_t, std::int32_t, std::int32_t*); int APS5_VABI sceKernelAioWaitRequest(std::int32_t, std::int32_t*, std::uint32_t*); int APS5_VABI sceKernelAioDeleteRequest(std::int32_t, std::int32_t*); +int APS5_VABI sceKernelMlock_nid_postfix(void*, std::uint64_t); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -205,6 +207,56 @@ static void CheckNoOverwriteRejectsHostOccupiedMapping() { } #endif +#if defined(__linux__) +static std::size_t LockedKilobytes() { + std::ifstream status("/proc/self/status"); + std::string line; + while (std::getline(status, line)) { + if (line.rfind("VmLck:", 0) == 0) return std::strtoull(line.c_str() + 6, nullptr, 10); + } + return 0; +} +#endif + +static void CheckMlock() { + constexpr std::size_t page = 0x4000; +#ifdef _WIN32 + constexpr std::size_t length = 0x400000; +#else + constexpr std::size_t length = 0x10000; +#endif + constexpr int outOfMemory = static_cast(0x8002000cu); + constexpr int invalid = static_cast(0x80020016u); + void* mapped = nullptr; + Require(sceKernelMapFlexibleMemory(&mapped, length, 3, 0) == 0); + auto* bytes = static_cast(mapped); + Require(sceKernelMlock_nid_postfix(mapped, 0) == 0); +#if defined(__linux__) + const auto lockedBefore = LockedKilobytes(); +#endif + Require(sceKernelMlock_nid_postfix(bytes + 1, length - page) == 0); +#ifdef _WIN32 + SIZE_T minimum = 0; + SIZE_T maximum = 0; + DWORD limits = 0; + Require(GetProcessWorkingSetSizeEx(GetCurrentProcess(), &minimum, &maximum, &limits) && minimum >= length && maximum > minimum); + Require(VirtualUnlock(mapped, length)); + Require(!VirtualUnlock(mapped, length) && GetLastError() == ERROR_NOT_LOCKED); +#elif defined(__linux__) + Require(LockedKilobytes() - lockedBefore == length / 1024); +#endif + Require(sceKernelMlock_nid_postfix(mapped, length) == 0); + Require(sceKernelMlock_nid_postfix(mapped, length) == 0); + bytes[length - 1] = 7; + Require(sceKernelMlock_nid_postfix(reinterpret_cast(std::numeric_limits::max() - page + 1), page * 2) == invalid); + void* reserved = nullptr; + Require(sceKernelReserveVirtualRange(&reserved, page, 0, 0) == 0); + Require(sceKernelMlock_nid_postfix(reserved, page) == outOfMemory); + Require(sceKernelMunmap(reserved, page) == 0); + Require(sceKernelMunmap(mapped, length) == 0); + Require(sceKernelMlock_nid_postfix(mapped, page) == outOfMemory); +} + static void CheckSharedDirectMemoryLifecycle() { constexpr std::size_t page = 0x4000; std::int64_t phys = 0; @@ -586,6 +638,7 @@ int main() { CheckInternalNamedFlexibleMapping(); CheckDirectMemoryFollowsPhysicalPages(); CheckFixedVirtualReservation(); + CheckMlock(); CheckSharedDirectMemoryLifecycle(); CheckHeapAfterMappingReuse(); #ifdef _WIN32 From 4dea441f01a9b2b72ef24a4ccb481125059dc85e Mon Sep 17 00:00:00 2001 From: DotDebian Date: Fri, 2 Oct 2026 17:24:12 +0200 Subject: [PATCH 040/164] fix(libkernel): do not expose stale thread-specific values through a reused pthread key scePthreadKeyDelete frees a key slot and the next scePthreadKeyCreate hands the same index out again, but every thread kept the value it had stored under that index. The new key read the previous key's value instead of NULL, and at thread exit its destructor was called on that stale pointer. Each slot now carries a sequence number, as in FreeBSD libthr. A thread value is visible to scePthreadGetspecific and to the exit destructors only while its sequence matches the slot's, so a new key starts as NULL in every thread, as POSIX requires, and a deleted key reads NULL. scePthreadGetspecific stays lock-free and scePthreadSetspecific no longer takes the key lock. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libkernel/CMakeLists.txt | 10 ++ core/libs/prx/libkernel/Pthread/src/Tsd.cpp | 48 ++++++---- core/libs/tests/GuestPthreadKey.cpp | 100 ++++++++++++++++++++ 3 files changed, 138 insertions(+), 20 deletions(-) create mode 100644 core/libs/tests/GuestPthreadKey.cpp diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 81792a6ad..9127d300d 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -216,6 +216,16 @@ if(BUILD_TESTING) set_tests_properties(guest_pthread_rwlock PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_pthread_key_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadKey.cpp) + target_include_directories(guest_pthread_key_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_pthread_key_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_pthread_key_tests) + add_test(NAME guest_pthread_key COMMAND guest_pthread_key_tests) + set_tests_properties(guest_pthread_key PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_pthread_key PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_signal_tests ${LIBS_INCLUDE_DIR}/tests/GuestSignals.cpp) target_include_directories(guest_signal_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_signal_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libkernel/Pthread/src/Tsd.cpp b/core/libs/prx/libkernel/Pthread/src/Tsd.cpp index 8f6442f8a..b737b2f16 100644 --- a/core/libs/prx/libkernel/Pthread/src/Tsd.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Tsd.cpp @@ -1,6 +1,8 @@ #include "../include/Pthread.hpp" #include "prx/libc/include/General.hpp" #include +#include +#include #include #include @@ -9,34 +11,41 @@ static constexpr int SCE_KERNEL_ERROR_EINVAL = 0x80020016; static constexpr int SCE_KERNEL_ERROR_EAGAIN = 0x80020023; static constexpr int MAX_KEYS = 512; static constexpr int DESTRUCTOR_ITERATIONS = 4; +static constexpr std::uint64_t FREE_KEY = 0; using GuestKeyDestructor = void (APS5_VABI*)(void*); struct KeySlot { - bool used = false; + std::atomic sequence{FREE_KEY}; GuestKeyDestructor destructor = nullptr; }; +struct ThreadValue { + void* value = nullptr; + std::uint64_t sequence = FREE_KEY; +}; + static std::mutex g_keyLock; static std::array g_keys; +static std::uint64_t g_lastSequence = FREE_KEY; struct ThreadValues { - std::array values{}; + std::array values{}; ~ThreadValues() { for (int round = 0; round < DESTRUCTOR_ITERATIONS; ++round) { bool called = false; for (int key = 0; key < MAX_KEYS; ++key) { - void* value = values[key]; - if (!value) continue; + const ThreadValue entry = values[key]; + if (!entry.value) continue; GuestKeyDestructor destructor; { std::lock_guard lock(g_keyLock); - destructor = g_keys[key].used ? g_keys[key].destructor : nullptr; + destructor = g_keys[key].sequence.load(std::memory_order_relaxed) == entry.sequence ? g_keys[key].destructor : nullptr; } - values[key] = nullptr; + values[key] = {}; if (destructor) { - destructor(value); + destructor(entry.value); called = true; } } @@ -47,20 +56,15 @@ struct ThreadValues { static thread_local ThreadValues g_values; -static bool IsValidKey(PthreadKey key) { - if (key < 0 || key >= MAX_KEYS) return false; - std::lock_guard lock(g_keyLock); - return g_keys[key].used; -} - extern "C" { int APS5_VABI scePthreadKeyCreate(PthreadKey* key, pthread_key_destructor_func_t destructor) { if (!key) throw std::runtime_error("scePthreadKeyCreate: null key"); std::lock_guard lock(g_keyLock); for (int index = 0; index < MAX_KEYS; ++index) { - if (!g_keys[index].used) { - g_keys[index] = {true, reinterpret_cast(destructor)}; + if (g_keys[index].sequence.load(std::memory_order_relaxed) == FREE_KEY) { + g_keys[index].destructor = reinterpret_cast(destructor); + g_keys[index].sequence.store(++g_lastSequence, std::memory_order_release); *key = index; return SCE_OK; } @@ -71,19 +75,23 @@ int APS5_VABI scePthreadKeyCreate(PthreadKey* key, pthread_key_destructor_func_t int APS5_VABI scePthreadKeyDelete(PthreadKey key) { if (key < 0 || key >= MAX_KEYS) return SCE_KERNEL_ERROR_EINVAL; std::lock_guard lock(g_keyLock); - if (!g_keys[key].used) return SCE_KERNEL_ERROR_EINVAL; - g_keys[key] = {}; + if (g_keys[key].sequence.load(std::memory_order_relaxed) == FREE_KEY) return SCE_KERNEL_ERROR_EINVAL; + g_keys[key].sequence.store(FREE_KEY, std::memory_order_release); + g_keys[key].destructor = nullptr; return SCE_OK; } void* APS5_VABI scePthreadGetspecific(PthreadKey key) { if (key < 0 || key >= MAX_KEYS) return nullptr; - return g_values.values[key]; + const ThreadValue& entry = g_values.values[key]; + return entry.sequence == g_keys[key].sequence.load(std::memory_order_acquire) ? entry.value : nullptr; } int APS5_VABI scePthreadSetspecific(PthreadKey key, void* value) { - if (!IsValidKey(key)) return SCE_KERNEL_ERROR_EINVAL; - g_values.values[key] = value; + if (key < 0 || key >= MAX_KEYS) return SCE_KERNEL_ERROR_EINVAL; + const std::uint64_t sequence = g_keys[key].sequence.load(std::memory_order_acquire); + if (sequence == FREE_KEY) return SCE_KERNEL_ERROR_EINVAL; + g_values.values[key] = {value, sequence}; return SCE_OK; } diff --git a/core/libs/tests/GuestPthreadKey.cpp b/core/libs/tests/GuestPthreadKey.cpp new file mode 100644 index 000000000..e9bdd1653 --- /dev/null +++ b/core/libs/tests/GuestPthreadKey.cpp @@ -0,0 +1,100 @@ +#include "SceTypes.hpp" +#include +#include +#include + +extern "C" { +int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); +int APS5_VABI scePthreadJoin(Pthread thread, void** retval); +int APS5_VABI scePthreadKeyCreate(PthreadKey* key, pthread_key_destructor_func_t destructor); +int APS5_VABI scePthreadKeyDelete(PthreadKey key); +void* APS5_VABI scePthreadGetspecific(PthreadKey key); +int APS5_VABI scePthreadSetspecific(PthreadKey key, void* value); +} + +static constexpr int SCE_OK = 0; +static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); + +static void Require(bool value) { if (!value) std::abort(); } + +static std::atomic deletedCalls{0}, reusedCalls{0}, liveCalls{0}; +static std::atomic liveValue{nullptr}; +static std::atomic stage{0}; +static PthreadKey sharedKey = -1; +static int first = 1, second = 2, third = 3; + +static void APS5_VABI Deleted(void*) { ++deletedCalls; } +static void APS5_VABI Reused(void*) { ++reusedCalls; } +static void APS5_VABI Live(void* value) { ++liveCalls; liveValue = value; } + +static pthread_key_destructor_func_t Destructor(void (APS5_VABI* function)(void*)) { + return reinterpret_cast(function); +} + +static void WaitFor(int value) { + while (stage.load() != value) std::this_thread::yield(); +} + +static void* APS5_VABI ReuseBeforeExit(void*) { + PthreadKey live = -1; + Require(scePthreadKeyCreate(&live, Destructor(Live)) == SCE_OK); + Require(scePthreadSetspecific(live, &third) == SCE_OK); + PthreadKey deleted = -1; + Require(scePthreadKeyCreate(&deleted, Destructor(Deleted)) == SCE_OK); + Require(scePthreadSetspecific(deleted, &first) == SCE_OK); + Require(scePthreadKeyDelete(deleted) == SCE_OK); + PthreadKey reused = -1; + Require(scePthreadKeyCreate(&reused, Destructor(Reused)) == SCE_OK); + Require(reused == deleted); + sharedKey = live; + return nullptr; +} + +static void* APS5_VABI HoldAcrossReuse(void*) { + Require(scePthreadSetspecific(sharedKey, &first) == SCE_OK); + Require(scePthreadGetspecific(sharedKey) == &first); + stage = 1; + WaitFor(2); + Require(scePthreadGetspecific(sharedKey) == nullptr); + Require(scePthreadSetspecific(sharedKey, &second) == SCE_OK); + Require(scePthreadGetspecific(sharedKey) == &second); + return nullptr; +} + +int main() { + PthreadKey key = -1; + Require(scePthreadKeyCreate(&key, nullptr) == SCE_OK); + Require(scePthreadGetspecific(key) == nullptr); + Require(scePthreadSetspecific(key, &first) == SCE_OK); + Require(scePthreadGetspecific(key) == &first); + Require(scePthreadKeyDelete(key) == SCE_OK); + Require(scePthreadGetspecific(key) == nullptr); + Require(scePthreadSetspecific(key, &second) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadKeyDelete(key) == SCE_KERNEL_ERROR_EINVAL); + + PthreadKey reused = -1; + Require(scePthreadKeyCreate(&reused, nullptr) == SCE_OK); + Require(reused == key); + Require(scePthreadGetspecific(reused) == nullptr); + Require(scePthreadSetspecific(reused, &second) == SCE_OK); + Require(scePthreadGetspecific(reused) == &second); + Require(scePthreadKeyDelete(reused) == SCE_OK); + + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, nullptr, ReuseBeforeExit, nullptr, nullptr) == SCE_OK); + Require(scePthreadJoin(thread, nullptr) == SCE_OK); + Require(liveCalls == 1 && liveValue == &third); + Require(deletedCalls == 0 && reusedCalls == 0); + Require(scePthreadKeyDelete(sharedKey) == SCE_OK); + + Require(scePthreadKeyCreate(&sharedKey, nullptr) == SCE_OK); + const PthreadKey held = sharedKey; + Require(scePthreadCreate(&thread, nullptr, HoldAcrossReuse, nullptr, nullptr) == SCE_OK); + WaitFor(1); + Require(scePthreadKeyDelete(sharedKey) == SCE_OK); + Require(scePthreadKeyCreate(&sharedKey, nullptr) == SCE_OK); + Require(sharedKey == held); + stage = 2; + Require(scePthreadJoin(thread, nullptr) == SCE_OK); + Require(scePthreadGetspecific(sharedKey) == nullptr); +} From bf635fae68d6896896095aba502b100a24c71adc Mon Sep 17 00:00:00 2001 From: DotDebian Date: Sun, 4 Oct 2026 16:45:17 +0200 Subject: [PATCH 041/164] feat(shader): implement image_bvh64_intersect_ray MIMG 0xe7 is the node test of image_bvh_intersect_ray with a 64-bit node pointer: the pointer takes the first two address dwords (low, high), so the instruction addresses 12 dwords, or 9 with A16. The node index and address are computed from the whole pointer. A zero base in the T# is a null BVH only for the 32-bit form: with 64-bit pointers the T# may cover all of memory from base 0 and the pointer then carries the node address, so only the T# size bounds the node. Co-Authored-By: Claude Opus 5.5 --- .../tests/execution/Bvh64IntersectRay.cpp | 304 ++++++++++++++++++ core/libs/tests/RayTracingMiss.cpp | 26 +- .../ControlFlow/src/GraphBuilder.cpp | 1 + .../include/RdnaDecoder/RdnaOpcode.hpp | 1 + .../RdnaDecoder/src/RdnaImageOpDecoder.cpp | 6 +- .../recompiler/RdnaDecoder/src/RdnaOpcode.cpp | 1 + .../SpirvBackend/src/SpirvImageEmitter.cpp | 30 +- .../MemoryInstructions/ImageInstructions.cpp | 1 + .../MemoryInstructionDispatch.cpp | 1 + 9 files changed, 351 insertions(+), 20 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Bvh64IntersectRay.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Bvh64IntersectRay.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Bvh64IntersectRay.cpp new file mode 100644 index 000000000..d0a599877 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Bvh64IntersectRay.cpp @@ -0,0 +1,304 @@ +#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 +#else +#include +#endif +#include +#include +#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 Record = 16; +constexpr std::uint32_t Forms = 4; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint64_t NodeBytes = 64; +constexpr std::uint32_t LastNode = 6; +constexpr std::uint32_t Invalid = 0xffffffffu; +constexpr std::uint32_t BoxSort = 0x80000000u; +constexpr std::uint32_t TriangleId = 0x00abc123u; +constexpr std::array Box32Children{0x1000u, 0x1001u, 0x1002u, 0x1003u}; +constexpr std::array Box16Children{0x2000u, 0x2001u, 0x2002u, 0x2003u}; +constexpr std::array PastEndChildren{0x3000u, 0x3001u, 0x3002u, 0x3003u}; +constexpr std::array FormNames{"image_bvh64_intersect_ray", "image_bvh_intersect_ray", "image_bvh64_intersect_ray a16", "image_bvh_intersect_ray a16"}; + +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +constexpr std::array MakeCode() { + std::array code{}; + std::size_t at = 0; + code[at++] = 0x34020086u; + for (std::uint32_t dword = 0; dword < Record; ++dword) { + code[at++] = 0xe0301000u | (dword * 4u); + code[at++] = 0x80000001u | ((4u + dword) << 8u); + } + code[at++] = 0xbf8c3f70u; + code[at++] = 0xf19c9f01u; + code[at++] = 0x00031405u; + code[at++] = 0xf1989f07u; + code[at++] = 0x00021804u; + code[at++] = 0x0a090807u; + code[at++] = 0x0e0d0c0bu; + code[at++] = 0x0000100fu; + code[at++] = 0xf19c9f05u; + code[at++] = 0x40031c05u; + code[at++] = 0x09080706u; + code[at++] = 0x1312110au; + code[at++] = 0xf1989f05u; + code[at++] = 0x40022004u; + code[at++] = 0x0a090807u; + code[at++] = 0x00131211u; + for (std::uint32_t dword = 0; dword < Record; ++dword) { + code[at++] = 0xe0701000u | (dword * 4u); + code[at++] = 0x80010001u | ((20u + dword) << 8u); + } + code[at++] = 0xbf810000u; + return code; +} + +alignas(256) constexpr std::array Code = MakeCode(); + +struct Ray { + float extent; + std::array origin; + std::array direction; + std::array inverse; +}; + +struct Case { + std::uint32_t node; + Ray ray; + std::array expected; +}; + +constexpr float Infinity = std::numeric_limits::infinity(); +constexpr std::array Diagonal{0.25f, 0.5f, 1.0f}; +constexpr std::array DiagonalInverse{4.0f, 2.0f, 1.0f}; +constexpr Ray Long{100.0f, {0.0f, 0.0f, 0.0f}, Diagonal, DiagonalInverse}; +constexpr Ray Short{2.0f, {0.0f, 0.0f, 0.0f}, Diagonal, DiagonalInverse}; +constexpr Ray Forward{100.0f, {0.25f, 0.25f, 0.0f}, {0.0f, 0.0f, 1.0f}, {Infinity, Infinity, 1.0f}}; + +constexpr std::uint32_t Pointer(std::uint32_t node, std::uint32_t type) { + return (node << 3u) | type; +} + +constexpr std::array Cases{{ + {Pointer(2, 5), Long, {Box32Children[2], Box32Children[0], Invalid, Invalid}}, + {Pointer(4, 4), Long, {Box16Children[2], Box16Children[0], Invalid, Invalid}}, + {Pointer(1, 0), Forward, {0xc0a00000u, 0xbf800000u, TriangleId, 1u}}, + {Pointer(1, 1), Forward, {0x7f800000u, 0x3f800000u, TriangleId, 0u}}, + {Pointer(1, 7), Long, {Invalid, Invalid, Invalid, Invalid}}, + {Pointer(6, 5), Long, {Invalid, Invalid, Invalid, Invalid}}, + {Pointer(7, 0), Forward, {Invalid, Invalid, Invalid, Invalid}}, + {Pointer(2, 5), Short, {Box32Children[2], Invalid, Invalid, Invalid}}, +}}; + +constexpr std::array, 4> Boxes{{ + {1.0f, 2.0f, 4.0f, 2.0f, 4.0f, 8.0f}, + {10.0f, 0.0f, 0.0f, 11.0f, 1.0f, 1.0f}, + {0.25f, 0.5f, 1.0f, 0.5f, 1.0f, 2.0f}, + {-2.0f, -4.0f, -8.0f, -1.0f, -2.0f, -4.0f}, +}}; + +constexpr std::array TriangleVertices{0.0f, 0.0f, 5.0f, 1.0f, 0.0f, 5.0f, 0.0f, 1.0f, 5.0f, 1.0f, 1.0f, 5.0f}; + +std::uint32_t Bits(float value) { + return std::bit_cast(value); +} + +std::uint32_t Half(float value) { + const auto bits = Bits(value); + const auto sign = (bits >> 16u) & 0x8000u; + const auto exponent = (bits >> 23u) & 0xffu; + const auto mantissa = bits & 0x7fffffu; + if (exponent == 0u && mantissa == 0u) return sign; + if (exponent == 0xffu && mantissa == 0u) return sign | 0x7c00u; + Require(exponent >= 113u && exponent <= 142u && (mantissa & 0x1fffu) == 0u, "bvh64 intersect ray: a test value is not a float16"); + return sign | ((exponent - 112u) << 10u) | (mantissa >> 13u); +} + +class GuestBlock { +public: + GuestBlock() { + constexpr std::uintptr_t hint = std::uintptr_t{1} << 40u; +#ifdef _WIN32 + for (std::uintptr_t attempt = 0; block == nullptr && attempt < 16u; ++attempt) { + block = static_cast(VirtualAlloc(reinterpret_cast(hint + (attempt << 32u)), BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + } + if (block == nullptr) block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + void* mapped = mmap(reinterpret_cast(hint), BlockBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + block = mapped == MAP_FAILED ? nullptr : static_cast(mapped); +#endif + Require(block != nullptr, "bvh64 intersect ray: cannot allocate the guest block"); + std::memset(block, 0, BlockBytes); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + munmap(block, BlockBytes); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint64_t Address() const { return static_cast(reinterpret_cast(block)); } + + void Write(std::uint32_t node, std::uint32_t dword, std::uint32_t value) { + std::memcpy(block + node * NodeBytes + dword * 4u, &value, sizeof(value)); + } + +private: + std::uint8_t* block = nullptr; +}; + +void WriteBox32(GuestBlock& guest, std::uint32_t node, const std::array& children) { + for (std::uint32_t child = 0; child < 4u; ++child) { + guest.Write(node, child, children[child]); + for (std::uint32_t index = 0; index < 6u; ++index) guest.Write(node, 4u + child * 6u + index, Bits(Boxes[child][index])); + } +} + +void WriteBox16(GuestBlock& guest, std::uint32_t node, const std::array& children) { + for (std::uint32_t child = 0; child < 4u; ++child) { + guest.Write(node, child, children[child]); + for (std::uint32_t index = 0; index < 6u; index += 2u) { + const auto position = child * 6u + index; + guest.Write(node, 4u + position / 2u, Half(Boxes[child][index]) | (Half(Boxes[child][index + 1u]) << 16u)); + } + } +} + +void WriteTriangles(GuestBlock& guest, std::uint32_t node) { + for (std::uint32_t index = 0; index < TriangleVertices.size(); ++index) guest.Write(node, index, Bits(TriangleVertices[index])); + guest.Write(node, 15u, TriangleId); +} + +void FillNodes(GuestBlock& guest) { + WriteTriangles(guest, 1u); + WriteBox32(guest, 2u, Box32Children); + WriteBox16(guest, 4u, Box16Children); + WriteBox32(guest, 6u, PastEndChildren); + WriteTriangles(guest, 7u); +} + +void FillInput(std::uint64_t nodeBias) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& test = Cases[tid % Cases.size()]; + const auto& ray = test.ray; + const std::uint64_t node64 = nodeBias + test.node; + auto* words = &Input[tid * Record]; + words[0] = test.node; + words[1] = static_cast(node64); + words[2] = static_cast(node64 >> 32u); + words[3] = Bits(ray.extent); + for (std::uint32_t axis = 0; axis < 3u; ++axis) { + words[4u + axis] = Bits(ray.origin[axis]); + words[7u + axis] = Bits(ray.direction[axis]); + words[10u + axis] = Bits(ray.inverse[axis]); + } + words[13] = Half(ray.direction[0]) | (Half(ray.direction[1]) << 16u); + words[14] = Half(ray.direction[2]) | (Half(ray.inverse[0]) << 16u); + words[15] = Half(ray.inverse[1]) | (Half(ray.inverse[2]) << 16u); + } +} + +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::array BvhDescriptor(std::uint64_t base, std::uint64_t lastNode) { + return {static_cast(base >> 8u), static_cast((base >> 40u) & 0xffu) | BoxSort, static_cast(lastNode), static_cast((lastNode >> 32u) & 0x3ffu)}; +} + +void Run(AgcDriver::VulkanDevice& device, const GuestBlock& guest, bool addressInPointer) { + const auto address = guest.Address(); + FillInput(addressInPointer ? address >> 3u : 0u); + Output.fill(0xdeadbeefu); + std::vector userData(16, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + const auto narrow = BvhDescriptor(address, LastNode); + const auto wide = addressInPointer ? BvhDescriptor(0u, address / NodeBytes + LastNode) : narrow; + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + std::copy(narrow.begin(), narrow.end(), userData.begin() + 8); + std::copy(wide.begin(), wide.end(), userData.begin() + 12); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check(bool addressInPointer) { + const std::string mode = addressInPointer ? "address in the node pointer" : "base in the descriptor"; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Cases[tid % Cases.size()].expected; + for (std::uint32_t form = 0; form < Forms; ++form) { + for (std::uint32_t dword = 0; dword < 4u; ++dword) { + const auto actual = Output[tid * Record + form * 4u + dword]; + Require(actual == expected[dword], std::string(FormNames[form]) + ", " + mode + ": thread " + std::to_string(tid) + " dword " + std::to_string(dword) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[dword])); + } + } + } +} + +} + +int main() { + try { + if (ShaderRecompiler::RayTracingStrict() || ShaderRecompiler::RayTracingMiss()) { + std::puts("skipped, APS5_RAYTRACING replaces the node test"); + return VulkanTestSkipped; + } + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + Require((guest.Address() >> 35u) != 0u, "bvh64 intersect ray: the guest block is not above 32 GiB, the node pointer would fit 32 bits"); + FillNodes(guest); + for (const bool addressInPointer : {true, false}) { + Run(*device, guest, addressInPointer); + Check(addressInPointer); + } + std::puts("bvh64 intersect ray tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/tests/RayTracingMiss.cpp b/core/libs/tests/RayTracingMiss.cpp index 803f62a95..5ca726847 100644 --- a/core/libs/tests/RayTracingMiss.cpp +++ b/core/libs/tests/RayTracingMiss.cpp @@ -6,12 +6,20 @@ using namespace ShaderRecompiler; static void Require(bool value) { if (!value) throw std::runtime_error("ray tracing miss regression"); } -int main() { - const std::array code{0xF1981F01u, 0x00000000u}; +struct Form { + std::uint32_t word0; + std::uint32_t word1; + RdnaOpcode opcode; + std::uint32_t addressDwords; +}; +static void Check(const Form& form) { + const std::array code{form.word0, form.word1}; const RdnaInstruction instruction = DecodeRdnaMimg(0u, code, 0u); - Require(instruction.op == RdnaOpcode::ImageBvhIntersectRay); + Require(instruction.op == form.opcode); Require(instruction.family == RdnaInstructionFamily::MIMG); Require(IsImageOpcode(instruction.op)); + Require(instruction.imageAddressComponents == form.addressDwords); + Require(GetRdnaImageAddressDwordCount(instruction.imageSampleFlags, instruction.imageAddressComponents) == form.addressDwords); IrProgram program; auto& block = program.CreateBlock(); program.SetEntryBlock(block); @@ -26,8 +34,14 @@ int main() { Require(thrown); } else { context.TranslateInstruction(instruction); - if (RayTracingMiss()) { - for (auto* value : block.Instructions()) Require(value->Opcode() != IrOpcode::ImageBvhIntersectRay); - } + bool emitted = false; + for (auto* value : block.Instructions()) emitted = emitted || value->Opcode() == IrOpcode::ImageBvhIntersectRay; + Require(emitted != RayTracingMiss()); } } +int main() { + Check({0xF1981F01u, 0x00000000u, RdnaOpcode::ImageBvhIntersectRay, 11u}); + Check({0xF1981F01u, 0x40000000u, RdnaOpcode::ImageBvhIntersectRay, 8u}); + Check({0xF19C1F01u, 0x00000000u, RdnaOpcode::ImageBvh64IntersectRay, 12u}); + Check({0xF19C1F01u, 0x40000000u, RdnaOpcode::ImageBvh64IntersectRay, 9u}); +} diff --git a/core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp b/core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp index 9d9ef50cb..e646263f8 100644 --- a/core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp +++ b/core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp @@ -56,6 +56,7 @@ std::uint32_t estimatedSpirvWords(const RdnaInstruction& instruction) { case RdnaOpcode::DsPermuteB32: return 1200u; case RdnaOpcode::ImageBvhIntersectRay: + case RdnaOpcode::ImageBvh64IntersectRay: return 2000u; default: break; diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp index 5ed43f202..0eae47c8d 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -892,6 +892,7 @@ enum class RdnaOpcode : std::uint16_t { ImageGather4LO, ImageGather4O, ImageBvhIntersectRay, + ImageBvh64IntersectRay, VInterpP1F32, VInterpP2F32, VInterpMovF32, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp index a019dd480..0a44d6361 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp @@ -111,6 +111,7 @@ constexpr ImageOpcodeInfo imageOpcodes[] = { {0x0eu, RdnaOpcode::ImageGetResinfo, nullptr, 0, false, false, false}, {0x60u, RdnaOpcode::ImageGetLod, nullptr, 0, false, false, false}, {0xe6u, RdnaOpcode::ImageBvhIntersectRay, "image_bvh_intersect_ray", 0, false, false, false}, + {0xe7u, RdnaOpcode::ImageBvh64IntersectRay, "image_bvh64_intersect_ray", 0, false, false, false}, }; const ImageOpcodeInfo& lookupOpcode(std::uint32_t opcode) { @@ -306,8 +307,9 @@ RdnaInstruction DecodeRdnaMimg(std::uint32_t programCounter, std::span origin{}; std::array direction{}; std::array inverse{}; for (std::uint32_t axis = 0; axis < 3u; ++axis) { - origin[axis] = asFloat(component(2u + axis)); + origin[axis] = asFloat(component(ray + 1u + axis)); if (!a16) { - direction[axis] = asFloat(component(5u + axis)); - inverse[axis] = asFloat(component(8u + axis)); + direction[axis] = asFloat(component(ray + 4u + axis)); + inverse[axis] = asFloat(component(ray + 7u + axis)); continue; } const auto half = [&](std::uint32_t index) { - const auto packed = component(5u + index / 2u); + const auto packed = component(ray + 4u + index / 2u); return EmitF16BitsToF32(state, index % 2u == 0u ? op(spv::OpBitwiseAnd, u32, packed, uint(0xffffu)) : op(spv::OpShiftRightLogical, u32, packed, uint(16u))); }; direction[axis] = half(axis); diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/ImageInstructions.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/ImageInstructions.cpp index 76cc041ea..2f0bf639b 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/ImageInstructions.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/ImageInstructions.cpp @@ -61,6 +61,7 @@ bool TranslationContext::imageBvhIntersectRay(const RdnaInstruction& inst) { memory.kind = ResourceKind::Global; memory.dataDwords = 4u; memory.imageSampleFlags = inst.imageA16 ? RdnaImageSampleFlagA16 : 0u; + memory.addressIsFull = inst.op == RdnaOpcode::ImageBvh64IntersectRay; IrValue* descriptor = constructU32x4(inst.source1, 4u); IrValue* address = makeImageAddress(inst, inst.source0); IrValue& result = ir.Emit(IrOpcode::ImageBvhIntersectRay, IrOpcodeType(IrOpcode::ImageBvhIntersectRay), {descriptor, address, &ir.GetExec()}, addMemoryInfo(memory, inst.programCounter)); diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp index 1031f7aa8..e5dee40b3 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp @@ -432,6 +432,7 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) { case RdnaOpcode::ImageGetLod: return imageGetLod(inst); case RdnaOpcode::ImageBvhIntersectRay: + case RdnaOpcode::ImageBvh64IntersectRay: return imageBvhIntersectRay(inst); default: From 15a4b73e6cb1931d8561b88309958a2e1f028e30 Mon Sep 17 00:00:00 2001 From: Pouare514 Date: Mon, 5 Oct 2026 02:31:17 +0200 Subject: [PATCH 042/164] fix(crashreport): single-dump timed watchdog with positive-only timeout --- .../libc/src/specifics/windows/CrashReport.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp b/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp index 2e06061f3..099d6392a 100644 --- a/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp +++ b/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp @@ -424,6 +424,15 @@ void ReportAllThreads() { if (skippedCount != 0) Report(" %llu further thread(s) left out\n", static_cast(skippedCount)); } +DWORD WINAPI HangWatchdog(LPVOID param) { + const auto seconds = static_cast(reinterpret_cast(param)); + Sleep(static_cast(seconds * 1000ull)); + Report("\nFATAL: timed dump after %llu s (APS5_HANG_DUMP_SECS); aborting\n", seconds); + std::fflush(stderr); + std::abort(); + return 0; +} + // abort() and the UCRT's invalid-parameter path end the process with a silent fast fail // (0xc0000409); both are reported with the calling thread's stack first. void ReportBacktrace(const char* what, bool reportThreads) { @@ -462,6 +471,15 @@ const bool g_crashReportInstalled = [] { std::signal(SIGABRT, AbortSignalHandler); _set_invalid_parameter_handler(InvalidParameterHandler); InstallWatch(); + if (const char* hang = std::getenv("APS5_HANG_DUMP_SECS")) { + unsigned long long seconds = 0; + if (hang[0] >= '0' && hang[0] <= '9') { + char* end = nullptr; + const auto value = std::strtoull(hang, &end, 10); + if (end && *end == 0) seconds = value; + } + if (seconds > 0) CreateThread(nullptr, 0, HangWatchdog, reinterpret_cast(static_cast(seconds)), 0, nullptr); + } return true; }(); From d32eb5d3096a86cd9af87afef728bce8831fac8b Mon Sep 17 00:00:00 2001 From: Nahuel Genchi <65604505+NahuelGenchi@users.noreply.github.com> Date: Sun, 4 Oct 2026 21:47:22 -0300 Subject: [PATCH 043/164] fix(shader): apply the DX9 zero rule to v_fma_legacy_f32 VOP3 0x140 was translated as a plain fma, so a zero source times an infinity or a NaN gave NaN instead of the addend. A zero source now gives +0 + src2, the same rule v_mul_legacy_f32 and v_mac_legacy_f32 already follow. The opcode also takes neg and abs on its sources. --- .../tests/execution/FmaLegacy.cpp | 144 ++++++++++++++++++ .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 1 + .../Translation/TranslationContext.hpp | 1 + .../Translation/src/FloatInstructions.cpp | 11 ++ .../Translation/src/VectorInstructions.cpp | 3 +- 5 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/FmaLegacy.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FmaLegacy.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FmaLegacy.cpp new file mode 100644 index 000000000..e9c867787 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FmaLegacy.cpp @@ -0,0 +1,144 @@ +#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 +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 8; +constexpr std::uint32_t Results = 8; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array FmaLegacyCode{ + 0x34020083, 0x34060083, 0xe0302000, 0x80000401, 0xe0302004, 0x80000501, 0xe0302008, 0x80000601, + 0xbf8c3f70, 0xd540000a, 0x041a0b04, 0xd540000b, 0x241a0b04, 0xd540010c, 0x841a0b04, 0xd540000d, + 0x041a0a80, 0xd540020e, 0x641a0b04, 0xe0702000, 0x80010a03, 0xe0702004, 0x80010b03, 0xe0702008, + 0x80010c03, 0xe070200c, 0x80010d03, 0xe0702010, 0x80010e03, 0xbf810000, +}; + +struct Row { + std::uint32_t a, b, c; +}; + +constexpr std::array Rows{{ + {0x00000000u, 0x7f800000u, 0x3f800000u}, + {0x80000000u, 0x7f800000u, 0xc0200000u}, + {0x7f800000u, 0x00000000u, 0x3f400000u}, + {0xff800000u, 0x80000000u, 0x40400000u}, + {0x00000000u, 0x7fc00000u, 0x3fc00000u}, + {0x7fc00000u, 0x80000000u, 0xc0800000u}, + {0x00000000u, 0x40000000u, 0x7f800000u}, + {0x80000000u, 0xc0400000u, 0xff800000u}, + {0x00000000u, 0x3f800000u, 0x7fc00000u}, + {0x3fc00000u, 0x40100000u, 0xbf400000u}, + {0xc0c00000u, 0x3f000000u, 0x41200000u}, + {0x7f800000u, 0x40000000u, 0x3f800000u}, + {0x7f800000u, 0xbf800000u, 0x7f800000u}, + {0x7fc00000u, 0x3f800000u, 0x3f800000u}, + {0x40400000u, 0x40800000u, 0xc1400000u}, + {0x7f7fffffu, 0x40000000u, 0x3f800000u}, + {0x3ec00000u, 0xc1480000u, 0x42c88000u}, + {0xc4800000u, 0xbd800000u, 0xc2800000u}, + {0x40e00000u, 0x41100000u, 0x3f000000u}, + {0x80000000u, 0x80000000u, 0x40a00000u}, +}}; + +std::array BufferDescriptor(const void* data, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (4u << 16u), count, 0x01016facu}; +} + +void Run(AgcDriver::VulkanDevice& device) { + Input.fill(0u); + for (std::uint32_t tid = 0; tid < Rows.size(); ++tid) { + const auto& row = Rows[tid]; + const std::array words{row.a, row.b, row.c}; + std::copy(words.begin(), words.end(), Input.begin() + tid * Inputs); + } + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size())); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size())); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(FmaLegacyCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +bool IsNan(std::uint32_t bits) { + return (bits & 0x7fffffffu) > 0x7f800000u; +} + +std::uint32_t FmaLegacy(float a, float b, float c) { + if (a == 0.0f || b == 0.0f) { + return std::bit_cast(0.0f + c); + } + return std::bit_cast(std::fma(a, b, c)); +} + +void Check() { + constexpr std::array names{ + "v_fma_legacy_f32", "v_fma_legacy_f32 -src0", "v_fma_legacy_f32 |src0| -src2", "v_fma_legacy_f32 src0=0", "v_fma_legacy_f32 -src0 -|src1|", + }; + for (std::uint32_t tid = 0; tid < Rows.size(); ++tid) { + const auto& row = Rows[tid]; + const float a = std::bit_cast(row.a); + const float b = std::bit_cast(row.b); + const float c = std::bit_cast(row.c); + const std::array expected{ + FmaLegacy(a, b, c), FmaLegacy(-a, b, c), FmaLegacy(std::fabs(a), b, -c), FmaLegacy(0.0f, b, c), FmaLegacy(-a, -std::fabs(b), c), + }; + for (std::uint32_t j = 0; j < expected.size(); ++j) { + const auto actual = Output[tid * Results + j]; + const bool matches = actual == expected[j] || (IsNan(actual) && IsNan(expected[j])); + Require(matches, std::string("fma legacy: lane ") + std::to_string(tid) + " (" + Hex(row.a) + ", " + Hex(row.b) + ", " + Hex(row.c) + ") " + names[j] + " is " + Hex(actual) + ", expected " + Hex(expected[j])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("fma legacy tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index dfce3c276..878b9c801 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -1763,6 +1763,7 @@ bool supportsNativeVop3SourceModifiers(RdnaOpcode opcode) { case RdnaOpcode::VMaxF16: case RdnaOpcode::VMacF32: case RdnaOpcode::VMadF32: + case RdnaOpcode::VMadLegacyF32: case RdnaOpcode::VFmaF32: case RdnaOpcode::VPackB32F16: case RdnaOpcode::VCubeidF32: diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index 35d84281e..033a135aa 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -161,6 +161,7 @@ private: bool vDivFmasF32(const RdnaInstruction& inst); bool vDivFixupF32(const RdnaInstruction& inst); bool vFrexpMantF32(const RdnaInstruction& inst); + bool vFmaLegacyF32(const RdnaInstruction& inst); IrU32 readF16Bits(const RdnaOperand& operand); IrU32 normF16(IrU32 bits, bool signedValue); bool vLdexpF16(const RdnaInstruction& inst); diff --git a/core/shader/recompiler/Translation/src/FloatInstructions.cpp b/core/shader/recompiler/Translation/src/FloatInstructions.cpp index 4307ca9c9..5b4e4e1f2 100644 --- a/core/shader/recompiler/Translation/src/FloatInstructions.cpp +++ b/core/shader/recompiler/Translation/src/FloatInstructions.cpp @@ -394,6 +394,17 @@ bool TranslationContext::floatTernary(const RdnaInstruction& inst, IrOpcode opco return true; } +bool TranslationContext::vFmaLegacyF32(const RdnaInstruction& inst) { + IrValue* lhs = readOperand(sourceAt(inst, 0u), IrType::F32); + IrValue* rhs = readOperand(sourceAt(inst, 1u), IrType::F32); + IrValue* addend = readOperand(sourceAt(inst, 2u), IrType::F32); + const auto isZero = [&](IrValue* value) { return IrU1(ir.IEqual(ir.BitwiseAnd(ir.BitCastU32(*value), ir.Constant(0x7fffffffu)), ir.Constant(0u))); }; + const IrU1 zero(ir.LogicalOr(isZero(lhs).Value(), isZero(rhs).Value())); + const auto factor = [&](IrValue* value) { return &ir.Emit(IrOpcode::SelectF32, IrType::F32, {&zero.Value(), &ir.ConstantF32(0.0f), value}); }; + writeOperand(inst.destination, &ir.Emit(IrOpcode::FPFma32, IrType::F32, {factor(lhs), factor(rhs), addend})); + return true; +} + bool TranslationContext::vFrexpMantF32(const RdnaInstruction& inst) { const IrU32 bits = readU32(sourceAt(inst, 0u)); const IrU32 exponent(ir.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&bits.Value(), &ir.Constant(23u), &ir.Constant(8u)})); diff --git a/core/shader/recompiler/Translation/src/VectorInstructions.cpp b/core/shader/recompiler/Translation/src/VectorInstructions.cpp index 9e2d42450..d66cbdfd1 100644 --- a/core/shader/recompiler/Translation/src/VectorInstructions.cpp +++ b/core/shader/recompiler/Translation/src/VectorInstructions.cpp @@ -939,9 +939,10 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { case RdnaOpcode::VMadmkF32: case RdnaOpcode::VMadakF32: case RdnaOpcode::VMadF32: - case RdnaOpcode::VMadLegacyF32: case RdnaOpcode::VFmaF32: return floatTernary(inst, roundsProductSeparately(inst) ? IrOpcode::FPMad32 : IrOpcode::FPFma32, false, true); + case RdnaOpcode::VMadLegacyF32: + return vFmaLegacyF32(inst); case RdnaOpcode::VMin3F32: return floatTernary(inst, IrOpcode::FPMinTri32, false, false); case RdnaOpcode::VMax3F32: From 331d16d44a9413d09602592224bcf4b9c578e96a Mon Sep 17 00:00:00 2001 From: Nahuel Genchi <65604505+NahuelGenchi@users.noreply.github.com> Date: Sun, 4 Oct 2026 21:47:22 -0300 Subject: [PATCH 044/164] fix(tools): count v_fmac_legacy_f32 and v_fma_legacy_f32 as decoded shader instructions VOP2 0x06 and VOP3 0x140 were only matched by their RDNA 1 names. --- tools/progress.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/progress.py b/tools/progress.py index 3777369ff..9bcad2fe7 100644 --- a/tools/progress.py +++ b/tools/progress.py @@ -49,6 +49,8 @@ OPCODE_VARIANTS = { "VMacF32": ("V_FMAC_F32",), "VMadmkF32": ("V_FMAMK_F32",), "VMadakF32": ("V_FMAAK_F32",), + "VMacLegacyF32": ("V_FMAC_LEGACY_F32",), + "VMadLegacyF32": ("V_FMA_LEGACY_F32",), "ImageSample": ("IMAGE_SAMPLE_L", "IMAGE_SAMPLE_B", "IMAGE_SAMPLE_C_LZ", "IMAGE_SAMPLE_L_O", "IMAGE_SAMPLE_D_CL_O"), } REPORT_ROWS = 100 From 77e2fe132240c1c6a7872dc95de7c422159b675b Mon Sep 17 00:00:00 2001 From: raghav314 Date: Mon, 5 Oct 2026 06:58:23 +0530 Subject: [PATCH 045/164] docs(ajm): record where the codec info and gapless values come from --- docs/dev/TechnicalDebt.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 5c87a3d98..6705dcbd3 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -56,6 +56,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceAvPlayerInit / sceAvPlayerInitEx](../../core/libs/prx/libSceAvPlayer/src/Player.cpp) (libSceAvPlayer) - behaviour without a memory replacement unknown; frame and sample buffers then come from the guest heap - [SceAvPlayerVideoEx](../../core/libs/SceTypes.hpp) (libSceAvPlayer) - frame rate field and encoding unknown; it is left zero in frame and stream info - [sceAjmDecMp3ParseFrame](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - signature, result layout and rate tables taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation of the PS4 library, not confirmed on a PS5 title: the layer field is not checked and MPEG-2.5 stops at 64 kbps +- [sceAjmBatchJobGetCodecInfo, sceAjmBatchJobGetGaplessDecode](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the `RUN_GET_CODEC_INFO` flag (0x800), the ATRAC9 codec info layout and its next frame size are taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [RPCSX](https://github.com/RPCSX/rpcsx) reimplementations, not confirmed on a PS5 title; a run job without buffers returns result 0, as in shadPS4. The gapless sideband reports the total and skip counts the title set and does not count them down: RPCSX does the same, shadPS4 counts them down and BryKytyPS5 returns its current counts - [AJM Opus decoder](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native, codec 24) - the initialize parameter layout (`u32 channels, u32 sample rate, u32`, seen as `2, 48000, 0`) and the little-endian `u16` byte count before each packet come from one title (The Smurfs Dreams); only 48000 Hz and a zero third word are accepted ### Functional From 391eb6ffe8dfe96f7cdc3e3526623368389a603f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 16:15:06 +0200 Subject: [PATCH 046/164] feat(shader): decode DPP8 on VOP1 and VOP2 Co-Authored-By: Claude --- .../libSceAgcDriver/tests/execution/Dpp8.cpp | 285 ++++++++++++++++++ core/libs/tests/Wave32WideSubgroup.cpp | 2 +- .../IntermediateRepresentation/IrOpcode.hpp | 3 +- .../include/RdnaDecoder/RdnaInstruction.hpp | 1 + .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 71 ++++- .../SpirvBackend/SpirvEmitterHelpers.hpp | 2 +- .../SpirvMemory/SpirvSubgroup.hpp | 2 +- .../src/SpirvMemory/SpirvSubgroup.cpp | 10 +- .../src/TranslationContext/OperandAccess.cpp | 15 +- 9 files changed, 371 insertions(+), 20 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Dpp8.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Dpp8.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Dpp8.cpp new file mode 100644 index 000000000..61d69c415 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Dpp8.cpp @@ -0,0 +1,285 @@ +#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 MaxThreads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Checked = 12; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Wave32Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e1402e9, 0x78afc504, 0x7e1602ea, + 0x05397704, 0x4a180ae9, 0x00024904, 0x061a0ee9, 0x97705306, 0x7e1c0306, 0x561c0ee9, 0xa5602406, + 0x7d880b04, 0x021e0ae9, 0x78afc504, 0x7e200ae9, 0x97705304, 0x50220ae9, 0x05397704, 0x7e1202ff, + 0x12345678, 0xbefe03ff, 0x5a5af0f3, 0x7e2402e9, 0x78afc504, 0x7e2602ea, 0x78afc504, 0x4c280ae9, + 0xa5602404, 0x7e2a02e9, 0x05397709, 0xbefe03c1, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, + 0xe0781020, 0x80011203, 0xbf810000, +}; + +alignas(256) constexpr std::array Wave64Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e1402e9, 0x78afc504, 0x7e1602ea, + 0x05397704, 0x4a180ae9, 0x00024904, 0x061a0ee9, 0x97705306, 0x7e1c0306, 0x561c0ee9, 0xa5602406, + 0x7d880b04, 0x021e0ae9, 0x78afc504, 0x7e200ae9, 0x97705304, 0x50220ae9, 0x05397704, 0x7e1202ff, + 0x12345678, 0xbefe03ff, 0x5a5af0f3, 0xbeff03ff, 0xc3a5960f, 0x7e2402e9, 0x78afc504, 0x7e2602ea, + 0x78afc504, 0x4c280ae9, 0xa5602404, 0x7e2a02e9, 0x05397709, 0xbefe04c1, 0xe0781000, 0x80010a03, + 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xbf810000, +}; + +constexpr std::uint32_t Rows[64][4] = { + {0x1e7ea419u, 0x51c9bc70u, 0x42800000u, 0xc2180000u}, + {0xe255accbu, 0x39292d22u, 0x422c0000u, 0x42740000u}, + {0xc0433cbdu, 0xc5e818fau, 0x42400000u, 0xc0400000u}, + {0x00a61f93u, 0x9d3c7decu, 0xc2300000u, 0xc2100000u}, + {0x49889310u, 0xd11745adu, 0xc21c0000u, 0x424c0000u}, + {0x02f0ee99u, 0xd0944996u, 0x42740000u, 0x41800000u}, + {0x35d14880u, 0x65b00a2du, 0x00000000u, 0x41c80000u}, + {0xfadb8908u, 0xffb88309u, 0x41d80000u, 0x42000000u}, + {0xffe976abu, 0xbef7e268u, 0xc2340000u, 0x41b80000u}, + {0x16f44881u, 0x8eb5140fu, 0x41200000u, 0x41100000u}, + {0x751b4c83u, 0x24114258u, 0x41700000u, 0xc2680000u}, + {0xd9090b89u, 0xb5fb12e0u, 0x41f00000u, 0x41e80000u}, + {0x7608d942u, 0x6c167229u, 0xc2240000u, 0x42180000u}, + {0xe41686cdu, 0x95f19a51u, 0x427c0000u, 0xc20c0000u}, + {0x6ce9da66u, 0x81a0ba05u, 0x42740000u, 0x42140000u}, + {0xeab06e9bu, 0x864fa3f3u, 0x40000000u, 0x422c0000u}, + {0x90624fe3u, 0xed1d9d16u, 0x42680000u, 0xc2680000u}, + {0x93550840u, 0x397a7623u, 0xc1f80000u, 0xc2500000u}, + {0xf675299bu, 0xb464c554u, 0xc2180000u, 0x42340000u}, + {0x78549014u, 0x2475263cu, 0x426c0000u, 0xc0c00000u}, + {0xd9814559u, 0xe2cbbb93u, 0xc2040000u, 0x42500000u}, + {0xac9f21dfu, 0x782afe6bu, 0x00000000u, 0x42180000u}, + {0xab74fe57u, 0xff8b2a6au, 0x41600000u, 0x42800000u}, + {0xb2c55523u, 0x7a98b9acu, 0x42180000u, 0x42380000u}, + {0x33d91808u, 0x4b5c1a95u, 0x40800000u, 0xc2780000u}, + {0x0879d955u, 0x265c28eau, 0x42540000u, 0x42740000u}, + {0x5c24c1aeu, 0xebe80fa9u, 0xc1500000u, 0x40000000u}, + {0xb1515fffu, 0x7c4e4248u, 0x425c0000u, 0x427c0000u}, + {0xced66b44u, 0x6f29a9f1u, 0xc2600000u, 0x41200000u}, + {0x511070a7u, 0x7528ea79u, 0xc21c0000u, 0x42400000u}, + {0xb07822dbu, 0xb76dc0c3u, 0x40000000u, 0x424c0000u}, + {0xec0643b1u, 0xf71bd135u, 0xc1900000u, 0x42300000u}, + {0xd7aa2208u, 0xba5c875cu, 0x42680000u, 0xc2400000u}, + {0xa60741f3u, 0x44371ff3u, 0x427c0000u, 0x41f00000u}, + {0x18bee074u, 0x31d06d9cu, 0x42240000u, 0xc1d80000u}, + {0x730b89dcu, 0xea9e7ab5u, 0xc2740000u, 0xc2240000u}, + {0x000e05d0u, 0x7383d496u, 0xc22c0000u, 0xc1600000u}, + {0x3f724926u, 0x1eedfcfcu, 0xc2540000u, 0x40800000u}, + {0xab215122u, 0xc1f6192bu, 0x41d80000u, 0xc1c80000u}, + {0x01504585u, 0x503dc86bu, 0x42740000u, 0xc1f00000u}, + {0x8bb9c912u, 0x3911c9b0u, 0x42640000u, 0xc1f80000u}, + {0xf551abfdu, 0x991f77c9u, 0x41200000u, 0xc22c0000u}, + {0x6af38f6cu, 0xab266981u, 0xc1c00000u, 0xc1f00000u}, + {0xc88f63d6u, 0xc84c1884u, 0x41600000u, 0x42240000u}, + {0x63f1bbbcu, 0xced8c284u, 0xc1d00000u, 0xc1600000u}, + {0x6041f10eu, 0x039e257au, 0xc2140000u, 0x420c0000u}, + {0xe344f1c3u, 0x12cc86e3u, 0xc24c0000u, 0x41c00000u}, + {0xa5721c6au, 0x5a789b32u, 0xc2680000u, 0xc2680000u}, + {0x82a8c712u, 0x79dcbc81u, 0x41980000u, 0x41500000u}, + {0x733c6676u, 0x5377899au, 0xc2040000u, 0x427c0000u}, + {0xf2e36355u, 0x7771f894u, 0xc1a80000u, 0xc1c80000u}, + {0x975cef7au, 0x850567c2u, 0xc1400000u, 0x423c0000u}, + {0x9f93693fu, 0x561f8bfeu, 0x42540000u, 0xc0a00000u}, + {0xadb21095u, 0xb5fc684au, 0xc23c0000u, 0x42180000u}, + {0x08be3b07u, 0x320be3f8u, 0xc1a00000u, 0xc2540000u}, + {0xa5f27419u, 0x8b75d02au, 0xc2500000u, 0x424c0000u}, + {0xde3b7dd3u, 0xc134a79bu, 0xc1800000u, 0x426c0000u}, + {0xb600860cu, 0xadbafed0u, 0xc26c0000u, 0xc1f80000u}, + {0xa5497863u, 0x87a9ec13u, 0xc1100000u, 0xc1e80000u}, + {0x290f5d0fu, 0xa945e7bbu, 0xc1880000u, 0x420c0000u}, + {0x45d6ac42u, 0x6ce4048fu, 0x42280000u, 0xc2300000u}, + {0x62186f4bu, 0x4e0e8896u, 0xc1880000u, 0x41c00000u}, + {0x2cb8cefcu, 0x43710bafu, 0x42580000u, 0x427c0000u}, + {0xffce6973u, 0xc7be5613u, 0x42280000u, 0x41100000u} +}; + +constexpr std::uint32_t Expected32[32][12] = { + {0x02f0ee99u, 0xfadb8908u, 0x341f693bu, 0xc2a40000u, 0x44c14000u, 0x02f0ee99u, 0x4b261f93u, 0x4ca54578u, 0x02f0ee99u, 0x02f0ee99u, 0xf7bed6a0u, 0x12345678u}, + {0x1e7ea419u, 0x35d14880u, 0x1b7ed9edu, 0x42da0000u, 0xc5120000u, 0x39292d22u, 0xce7ef30du, 0x6efa75a3u, 0x1e7ea419u, 0x1e7ea419u, 0x105f65eeu, 0x12345678u}, + {0xfadb8908u, 0x02f0ee99u, 0xa83dc5c5u, 0x42200000u, 0xc3100000u, 0xfadb8908u, 0xcded529au, 0xc8d90793u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xfadb8908u, 0x49889310u, 0x7f922ab7u, 0x41e00000u, 0xc512c000u, 0xfadb8908u, 0x4df3f521u, 0xe6c510fcu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc0433cbdu, 0x00a61f93u, 0xef95e9c6u, 0x429c0000u, 0xc21c0000u, 0xc0433cbdu, 0xcca48edfu, 0xd1bd6540u, 0x00000000u, 0xc0433cbdu, 0x64ba02d3u, 0x00000000u}, + {0xe255accbu, 0xc0433cbdu, 0xef12edafu, 0x41800000u, 0x444f4000u, 0xe255accbu, 0x4e574522u, 0x90d78653u, 0xe255accbu, 0xe255accbu, 0x2f6bb66au, 0x00000000u}, + {0x35d14880u, 0xe255accbu, 0x842eae46u, 0x42ac0000u, 0x44866000u, 0x35d14880u, 0x4c3c3ba6u, 0x4805b6f8u, 0x35d14880u, 0x35d14880u, 0x7ca5a29eu, 0x12345678u}, + {0x00a61f93u, 0x1e7ea419u, 0x1e372722u, 0xc0e00000u, 0x44f76000u, 0x00a61f93u, 0x4e931126u, 0x1e372722u, 0x00000000u, 0x00a61f93u, 0x03386b90u, 0x12345678u}, + {0xe41686cdu, 0xeab06e9bu, 0xd5ec2ae9u, 0x42540000u, 0xc4770000u, 0xbef7e268u, 0xce1bdbd2u, 0xa9a85104u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xffe976abu, 0x6ce9da66u, 0xa5a95c90u, 0x41c00000u, 0xc3b38000u, 0xffe976abu, 0x4eea3699u, 0xfb9eee75u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xeab06e9bu, 0xe41686cdu, 0x3b058ad9u, 0xc2400000u, 0x45241000u, 0x24114258u, 0x4db7a244u, 0x0827c926u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xeab06e9bu, 0x7608d942u, 0xccef5b61u, 0xc1800000u, 0xc49f6000u, 0xb5fb12e0u, 0xc9b44aa8u, 0x2c03ec23u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x751b4c83u, 0xd9090b89u, 0x6bffe8d4u, 0x42200000u, 0x450e5000u, 0x6c167229u, 0xcdaa7c8bu, 0x451f7db3u, 0x00000000u, 0x751b4c83u, 0x00d3683du, 0x00000000u}, + {0x16f44881u, 0x751b4c83u, 0x95db10fcu, 0x41d00000u, 0xc3e70000u, 0x95f19a51u, 0x4ed9d3b5u, 0x0b0ce6d5u, 0x00000000u, 0x16f44881u, 0x6a0e65afu, 0x00000000u}, + {0x6ce9da66u, 0x16f44881u, 0x818a30b0u, 0x42c80000u, 0x43d78000u, 0x6ce9da66u, 0xcddf4bcau, 0x98950286u, 0x6ce9da66u, 0x6ce9da66u, 0x7e5f45fbu, 0x00000000u}, + {0xd9090b89u, 0xffe976abu, 0x86391a9eu, 0x40000000u, 0x45297000u, 0x864fa3f3u, 0x4eec11b3u, 0x86391a9fu, 0x00000000u, 0xd9090b89u, 0x5dc6e2dau, 0x00000000u}, + {0xac9f21dfu, 0xb2c55523u, 0x8072a556u, 0x3f800000u, 0x44f68000u, 0xac9f21dfu, 0x4ef0a920u, 0x9fe2f239u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x90624fe3u, 0xab74fe57u, 0xcccf7e63u, 0xc2b40000u, 0x44d2a000u, 0x397a7623u, 0xcd18ad66u, 0xe4ef747bu, 0x00000000u, 0x90624fe3u, 0xa006cf36u, 0x12345678u}, + {0xb2c55523u, 0xac9f21dfu, 0x47b9cd94u, 0x41600000u, 0x4520c000u, 0xb464c554u, 0xced955f0u, 0x6103e734u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb2c55523u, 0xd9814559u, 0xb7ca2e7cu, 0x42500000u, 0xc3908000u, 0x2475263cu, 0xcedf3b60u, 0xfdf66b96u, 0x00000000u, 0xb2c55523u, 0xdb8ad9c4u, 0x12345678u}, + {0xf675299bu, 0x78549014u, 0x732e0b76u, 0x42b40000u, 0x442dc000u, 0xf675299bu, 0xce9a7556u, 0x5b204ba7u, 0x00000000u, 0xf675299bu, 0xc8a942c4u, 0x12345678u}, + {0x93550840u, 0xf675299bu, 0x088d4e4eu, 0x42500000u, 0xc4b48000u, 0x782afe6bu, 0xcea91603u, 0x6ea02807u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xab74fe57u, 0x93550840u, 0x8fed7a4du, 0x42800000u, 0xc4f64000u, 0xab74fe57u, 0xcea6c1bcu, 0x92e032aau, 0xab74fe57u, 0xab74fe57u, 0x93c9ddd6u, 0x12345678u}, + {0x78549014u, 0x90624fe3u, 0x0afb098fu, 0x41500000u, 0x42180000u, 0x7a98b9acu, 0xce19faebu, 0x0afb0990u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x511070a7u, 0xec0643b1u, 0x53d5f3eau, 0xc0e00000u, 0x45594000u, 0x511070a7u, 0xce9d5d40u, 0x37625e46u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x33d91808u, 0xb07822dbu, 0x2ed6023fu, 0x42400000u, 0xc5523000u, 0x33d91808u, 0x4eb84983u, 0xd6d44bc5u, 0x00000000u, 0x33d91808u, 0xa87a425au, 0x12345678u}, + {0xec0643b1u, 0x511070a7u, 0xf461e8feu, 0x425c0000u, 0xc0a00000u, 0xec0643b1u, 0x4d079d95u, 0x3cf88050u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xec0643b1u, 0xced66b44u, 0x84c81b9du, 0x42860000u, 0x43998000u, 0x7c4e4248u, 0x4e4f6460u, 0x4b24ad8du, 0x00000000u, 0xec0643b1u, 0x83b1bdb8u, 0x12345678u}, + {0x5c24c1aeu, 0xb1515fffu, 0xa302c1f9u, 0xc1000000u, 0xc2100000u, 0x6f29a9f1u, 0xcd9fcde2u, 0x207b09f1u, 0x00000000u, 0x5c24c1aeu, 0x414e78eau, 0x12345678u}, + {0x0879d955u, 0x5c24c1aeu, 0xa9020281u, 0x42480000u, 0xc425c000u, 0x0879d955u, 0xce9f0fbau, 0xd14dac27u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb07822dbu, 0x0879d955u, 0xeb46d8cbu, 0x41400000u, 0x45291000u, 0xb07822dbu, 0x4ea220e1u, 0xbfe79a18u, 0xb07822dbu, 0xb07822dbu, 0x510c1892u, 0x12345678u}, + {0xb1515fffu, 0x33d91808u, 0x2af4e93du, 0xc1400000u, 0xc4d8c000u, 0xb1515fffu, 0xce44a653u, 0x2af4e93du, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu} +}; +constexpr std::uint32_t Expected64[64][12] = { + {0x02f0ee99u, 0xfadb8908u, 0x341f693bu, 0xc2a40000u, 0x44c14000u, 0x02f0ee99u, 0x4b261f93u, 0x4ca54578u, 0x02f0ee99u, 0x02f0ee99u, 0xf7bed6a0u, 0x12345678u}, + {0x1e7ea419u, 0x35d14880u, 0x1b7ed9edu, 0x42da0000u, 0xc5120000u, 0x39292d22u, 0xce7ef30du, 0x6efa75a3u, 0x1e7ea419u, 0x1e7ea419u, 0x105f65eeu, 0x12345678u}, + {0xfadb8908u, 0x02f0ee99u, 0xa83dc5c5u, 0x42200000u, 0xc3100000u, 0xfadb8908u, 0xcded529au, 0xc8d90793u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xfadb8908u, 0x49889310u, 0x7f922ab7u, 0x41e00000u, 0xc512c000u, 0xfadb8908u, 0x4df3f521u, 0xe6c510fcu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc0433cbdu, 0x00a61f93u, 0xef95e9c6u, 0x429c0000u, 0xc21c0000u, 0xc0433cbdu, 0xcca48edfu, 0xd1bd6540u, 0x00000000u, 0xc0433cbdu, 0x64ba02d3u, 0x00000000u}, + {0xe255accbu, 0xc0433cbdu, 0xef12edafu, 0x41800000u, 0x444f4000u, 0xe255accbu, 0x4e574522u, 0x90d78653u, 0xe255accbu, 0xe255accbu, 0x2f6bb66au, 0x00000000u}, + {0x35d14880u, 0xe255accbu, 0x842eae46u, 0x42ac0000u, 0x44866000u, 0x35d14880u, 0x4c3c3ba6u, 0x4805b6f8u, 0x35d14880u, 0x35d14880u, 0x7ca5a29eu, 0x12345678u}, + {0x00a61f93u, 0x1e7ea419u, 0x1e372722u, 0xc0e00000u, 0x44f76000u, 0x00a61f93u, 0x4e931126u, 0x1e372722u, 0x00000000u, 0x00a61f93u, 0x03386b90u, 0x12345678u}, + {0xe41686cdu, 0xeab06e9bu, 0xd5ec2ae9u, 0x42540000u, 0xc4770000u, 0xbef7e268u, 0xce1bdbd2u, 0xa9a85104u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xffe976abu, 0x6ce9da66u, 0xa5a95c90u, 0x41c00000u, 0xc3b38000u, 0xffe976abu, 0x4eea3699u, 0xfb9eee75u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xeab06e9bu, 0xe41686cdu, 0x3b058ad9u, 0xc2400000u, 0x45241000u, 0x24114258u, 0x4db7a244u, 0x0827c926u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xeab06e9bu, 0x7608d942u, 0xccef5b61u, 0xc1800000u, 0xc49f6000u, 0xb5fb12e0u, 0xc9b44aa8u, 0x2c03ec23u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x751b4c83u, 0xd9090b89u, 0x6bffe8d4u, 0x42200000u, 0x450e5000u, 0x6c167229u, 0xcdaa7c8bu, 0x451f7db3u, 0x00000000u, 0x751b4c83u, 0x00d3683du, 0x00000000u}, + {0x16f44881u, 0x751b4c83u, 0x95db10fcu, 0x41d00000u, 0xc3e70000u, 0x95f19a51u, 0x4ed9d3b5u, 0x0b0ce6d5u, 0x00000000u, 0x16f44881u, 0x6a0e65afu, 0x00000000u}, + {0x6ce9da66u, 0x16f44881u, 0x818a30b0u, 0x42c80000u, 0x43d78000u, 0x6ce9da66u, 0xcddf4bcau, 0x98950286u, 0x6ce9da66u, 0x6ce9da66u, 0x7e5f45fbu, 0x00000000u}, + {0xd9090b89u, 0xffe976abu, 0x86391a9eu, 0x40000000u, 0x45297000u, 0x864fa3f3u, 0x4eec11b3u, 0x86391a9fu, 0x00000000u, 0xd9090b89u, 0x5dc6e2dau, 0x00000000u}, + {0xac9f21dfu, 0xb2c55523u, 0x8072a556u, 0x3f800000u, 0x44f68000u, 0xac9f21dfu, 0x4ef0a920u, 0x9fe2f239u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x90624fe3u, 0xab74fe57u, 0xcccf7e63u, 0xc2b40000u, 0x44d2a000u, 0x397a7623u, 0xcd18ad66u, 0xe4ef747bu, 0x00000000u, 0x90624fe3u, 0xa006cf36u, 0x12345678u}, + {0xb2c55523u, 0xac9f21dfu, 0x47b9cd94u, 0x41600000u, 0x4520c000u, 0xb464c554u, 0xced955f0u, 0x6103e734u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb2c55523u, 0xd9814559u, 0xb7ca2e7cu, 0x42500000u, 0xc3908000u, 0x2475263cu, 0xcedf3b60u, 0xfdf66b96u, 0x00000000u, 0xb2c55523u, 0xdb8ad9c4u, 0x12345678u}, + {0xf675299bu, 0x78549014u, 0x732e0b76u, 0x42b40000u, 0x442dc000u, 0xf675299bu, 0xce9a7556u, 0x5b204ba7u, 0x00000000u, 0xf675299bu, 0xc8a942c4u, 0x12345678u}, + {0x93550840u, 0xf675299bu, 0x088d4e4eu, 0x42500000u, 0xc4b48000u, 0x782afe6bu, 0xcea91603u, 0x6ea02807u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xab74fe57u, 0x93550840u, 0x8fed7a4du, 0x42800000u, 0xc4f64000u, 0xab74fe57u, 0xcea6c1bcu, 0x92e032aau, 0xab74fe57u, 0xab74fe57u, 0x93c9ddd6u, 0x12345678u}, + {0x78549014u, 0x90624fe3u, 0x0afb098fu, 0x41500000u, 0x42180000u, 0x7a98b9acu, 0xce19faebu, 0x0afb0990u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x511070a7u, 0xec0643b1u, 0x53d5f3eau, 0xc0e00000u, 0x45594000u, 0x511070a7u, 0xce9d5d40u, 0x37625e46u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x33d91808u, 0xb07822dbu, 0x2ed6023fu, 0x42400000u, 0xc5523000u, 0x33d91808u, 0x4eb84983u, 0xd6d44bc5u, 0x00000000u, 0x33d91808u, 0xa87a425au, 0x12345678u}, + {0xec0643b1u, 0x511070a7u, 0xf461e8feu, 0x425c0000u, 0xc0a00000u, 0xec0643b1u, 0x4d079d95u, 0x3cf88050u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xec0643b1u, 0xced66b44u, 0x84c81b9du, 0x42860000u, 0x43998000u, 0x7c4e4248u, 0x4e4f6460u, 0x4b24ad8du, 0x00000000u, 0xec0643b1u, 0x83b1bdb8u, 0x12345678u}, + {0x5c24c1aeu, 0xb1515fffu, 0xa302c1f9u, 0xc1000000u, 0xc2100000u, 0x6f29a9f1u, 0xcd9fcde2u, 0x207b09f1u, 0x00000000u, 0x5c24c1aeu, 0x414e78eau, 0x12345678u}, + {0x0879d955u, 0x5c24c1aeu, 0xa9020281u, 0x42480000u, 0xc425c000u, 0x0879d955u, 0xce9f0fbau, 0xd14dac27u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb07822dbu, 0x0879d955u, 0xeb46d8cbu, 0x41400000u, 0x45291000u, 0xb07822dbu, 0x4ea220e1u, 0xbfe79a18u, 0xb07822dbu, 0xb07822dbu, 0x510c1892u, 0x12345678u}, + {0xb1515fffu, 0x33d91808u, 0x2af4e93du, 0xc1400000u, 0xc4d8c000u, 0xb1515fffu, 0xce44a653u, 0x2af4e93du, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x3f724926u, 0x01504585u, 0x6063c94fu, 0xc2da0000u, 0x4504a000u, 0xba5c875cu, 0x4ee61714u, 0xbbaccce2u, 0x00000000u, 0x3f724926u, 0x45a378a4u, 0x00000000u}, + {0xd7aa2208u, 0xab215122u, 0xea3e61e6u, 0x428e0000u, 0xc4996000u, 0x44371ff3u, 0x4dc5f704u, 0xef587116u, 0xd7aa2208u, 0xd7aa2208u, 0xbbc8e00du, 0x00000000u}, + {0x01504585u, 0x3f724926u, 0xd7d7af8fu, 0x42100000u, 0xc4bea000u, 0x01504585u, 0xceb3f17cu, 0x7142b6c2u, 0x00000000u, 0x01504585u, 0xa5d9b46cu, 0x00000000u}, + {0x01504585u, 0x000e05d0u, 0x90a5bca8u, 0x41880000u, 0xc5187000u, 0x01504585u, 0xce215778u, 0xeaac8085u, 0x00000000u, 0x01504585u, 0xed0ba753u, 0x00000000u}, + {0x18bee074u, 0x730b89dcu, 0x4b2df69eu, 0x423c0000u, 0xc3d28000u, 0x18bee074u, 0x4ba822c2u, 0xe68f5e72u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xa60741f3u, 0x18bee074u, 0xf6981f04u, 0x41f80000u, 0x42de0000u, 0x1eedfcfcu, 0xcea9bd5eu, 0x37acdd71u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xab215122u, 0xa60741f3u, 0x99a03b33u, 0xc29c0000u, 0xc4c18000u, 0xab215122u, 0x4e7dc925u, 0x67fd5b1eu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x730b89dcu, 0xd7aa2208u, 0x27e7ea73u, 0xc2920000u, 0x44ce6000u, 0x730b89dcu, 0x49605d00u, 0x27e7ea73u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x6041f10eu, 0xa5721c6au, 0x2e6375adu, 0xc1880000u, 0x4457c000u, 0x3911c9b0u, 0xce5dc271u, 0xde83e61bu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8bb9c912u, 0xe344f1c3u, 0x8e7123c6u, 0xc2860000u, 0x448d0000u, 0x991f77c9u, 0x4ed5e71fu, 0x7c64698du, 0x00000000u, 0x8bb9c912u, 0xcad243f3u, 0x00000000u}, + {0xa5721c6au, 0x6041f10eu, 0xa078157eu, 0xc1a00000u, 0xc4d8c000u, 0xa5721c6au, 0xcd2ae540u, 0x0b685a8fu, 0xa5721c6au, 0xa5721c6au, 0x54d9967fu, 0x00000000u}, + {0xa5721c6au, 0x63f1bbbcu, 0xbd9dc481u, 0x42c40000u, 0x4512f000u, 0xc84c1884u, 0xcee88c6eu, 0x2c3dd441u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x6af38f6cu, 0xc88f63d6u, 0x5a928b96u, 0xc2900000u, 0x442c0000u, 0x6af38f6cu, 0xceb51bc7u, 0x9768265au, 0x6af38f6cu, 0x6af38f6cu, 0x31273d7cu, 0x00000000u}, + {0xf551abfdu, 0x6af38f6cu, 0x8f57ee8cu, 0xc1800000u, 0xc45b4000u, 0x039e257au, 0xcde5d872u, 0x6e91b4e7u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xe344f1c3u, 0xf551abfdu, 0x9e864ff5u, 0xc1500000u, 0x433d0000u, 0x12cc86e3u, 0x4ec083e2u, 0x081e32e1u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc88f63d6u, 0x8bb9c912u, 0xe6326444u, 0xc2a80000u, 0x45028000u, 0x5a789b32u, 0x4ec7e377u, 0xe6326445u, 0x00000000u, 0xc88f63d6u, 0xa58764ceu, 0x00000000u}, + {0xadb21095u, 0xa5f27419u, 0xed1922f7u, 0x3f800000u, 0x44310000u, 0x79dcbc81u, 0xced14621u, 0x1fcf309bu, 0xadb21095u, 0xadb21095u, 0x8623437fu, 0x12345678u}, + {0x82a8c712u, 0x08be3b07u, 0xc6b3f010u, 0x42280000u, 0x454ea000u, 0x5377899au, 0xcd51c9cbu, 0x5c35c4a2u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xa5f27419u, 0xadb21095u, 0xeaae5f0au, 0xc2680000u, 0xc3f80000u, 0x7771f894u, 0x4ee678cdu, 0x2524092au, 0xa5f27419u, 0xa5f27419u, 0x0b36ce7eu, 0x12345678u}, + {0xa5f27419u, 0x9f93693fu, 0xf841ce38u, 0x42840000u, 0x445c4000u, 0x850567c2u, 0xcefaae72u, 0x2498d102u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xf2e36355u, 0x975cef7au, 0xd8c85310u, 0xc2640000u, 0x43190000u, 0x561f8bfeu, 0xceb41b18u, 0xed7c7b79u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x733c6676u, 0xf2e36355u, 0x38a52f5cu, 0x41900000u, 0xc4534000u, 0x733c6676u, 0x4d0be3b0u, 0xa8dfcb9fu, 0x00000000u, 0x733c6676u, 0x3ce6fb0bu, 0x12345678u}, + {0x08be3b07u, 0x733c6676u, 0xb4b4ab0au, 0xc2c80000u, 0x44d82000u, 0x08be3b07u, 0xcea49bdfu, 0xa5484a6eu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x975cef7au, 0x82a8c712u, 0x0e1e973cu, 0x42d00000u, 0xc5191000u, 0x8b75d02au, 0xcec0d92eu, 0x0e1e973du, 0x00000000u, 0x975cef7au, 0x223c406bu, 0x12345678u}, + {0x62186f4bu, 0xffce6973u, 0x77352da7u, 0x42280000u, 0x4519e000u, 0xc134a79bu, 0x4e243d74u, 0xc103110fu, 0x00000000u, 0x62186f4bu, 0x3ecb5865u, 0x12345678u}, + {0xde3b7dd3u, 0x2cb8cefcu, 0x63bb84dcu, 0xc2200000u, 0xc4aa2000u, 0xadbafed0u, 0xceb56d0fu, 0xda73cdcdu, 0xde3b7dd3u, 0xde3b7dd3u, 0x52450130u, 0x12345678u}, + {0xffce6973u, 0x62186f4bu, 0x3daa721fu, 0xc2b00000u, 0x43e38000u, 0x87a9ec13u, 0xce93fef4u, 0xe9c25b5fu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xffce6973u, 0x45d6ac42u, 0x5f466dc7u, 0x41980000u, 0xc4104000u, 0xffce6973u, 0xce071209u, 0xef1c93fdu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xa5497863u, 0x290f5d0fu, 0x4b1f8262u, 0xc0000000u, 0xc511e000u, 0xa5497863u, 0xca465a34u, 0x95f3619eu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb600860cu, 0xa5497863u, 0x2c4a0669u, 0x429c0000u, 0xc3690000u, 0x4e0e8896u, 0x4e32e33cu, 0xf35800fau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x2cb8cefcu, 0xb600860cu, 0x21ac8982u, 0x42380000u, 0xc564f000u, 0x2cb8cefcu, 0x4ec430dfu, 0xf97191bbu, 0x2cb8cefcu, 0x2cb8cefcu, 0x728f7a5du, 0x12345678u}, + {0x290f5d0fu, 0xde3b7dd3u, 0xa5f9d3e6u, 0x424c0000u, 0xc2de0000u, 0xc7be5613u, 0x4e8bad59u, 0xa5f9d3e7u, 0x00000000u, 0x290f5d0fu, 0x3841a9edu, 0x12345678u} +}; + +constexpr const char* Names[Checked] = {"mov", "mov_fi", "add_nc_u32", "add_f32", "fmac_f32", "cndmask", "cvt_f32_i32", "add_co_ci_u32", "mov_inactive", "mov_inactive_fi", "sub_nc_u32_inactive", "mov_uniform_inactive"}; + +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; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + for (std::uint32_t tid = 0; tid < MaxThreads; ++tid) std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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{{waveSize, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + 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(); +} + +template +void Check(const std::uint32_t (&expected)[Lanes][Checked]) { + for (std::uint32_t tid = 0; tid < Lanes; ++tid) { + for (std::uint32_t index = 0; index < Checked; ++index) { + const auto actual = Output[tid * Results + index]; + Require(actual == expected[tid][index], "dpp8 wave" + std::to_string(Lanes) + ": lane " + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < 32) { + std::printf("skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); + return VulkanTestSkipped; + } + Run(*device, Wave32Code, 32, device->ComputeTarget(32)); + Check(Expected32); + Run(*device, Wave64Code, 64, device->Target()); + Check(Expected64); + Run(*device, Wave64Code, 64, device->ComputeTarget(32)); + Check(Expected64); + std::puts("dpp8 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/tests/Wave32WideSubgroup.cpp b/core/libs/tests/Wave32WideSubgroup.cpp index 7635e864c..af28fb59d 100644 --- a/core/libs/tests/Wave32WideSubgroup.cpp +++ b/core/libs/tests/Wave32WideSubgroup.cpp @@ -150,7 +150,7 @@ void Check(std::uint32_t size, const Value& masks) { const auto readFirst = EmitReadFirstLane(context, first); const auto readLane = EmitReadLane(context, read); const auto active = EmitBallotLaneActiveBool(state, ballot, requestedId); - const auto quad = EmitDppQuadPermTargetLane(state, logicalId, 0x1bu); + const auto quad = EmitDppGroupPermTargetLane(state, logicalId, 0x1bu, 2u); const auto quadValue = context.Shuffle(read, 0, quad.lane); const auto hardwareActive = EmitSubgroupLaneActiveBool(state, requestedId); const auto swizzled = EmitSwizzleU32(context, swizzle); diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp index 3ba9c509f..c7c17fe26 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp @@ -440,7 +440,8 @@ struct ImageOpcodeInfo { }; struct DppMoveFlags { - std::uint16_t control = 0; + static constexpr std::uint32_t Lanes8 = 0x1000000u; + std::uint32_t control = 0; std::uint8_t rowMask = 0xf; std::uint8_t bankMask = 0xf; bool fetchInactive = false; diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaInstruction.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaInstruction.hpp index e27d5fb18..a3b7ea54c 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaInstruction.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaInstruction.hpp @@ -100,6 +100,7 @@ struct RdnaOperand { bool absolute = false; bool clamp = false; bool dpp = false; + bool dpp8 = false; }; struct RdnaInstruction { diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index e70717129..9ddc3f21c 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -691,6 +691,38 @@ bool isPermlaneOpcode(RdnaOpcode opcode) { return opcode == RdnaOpcode::VPermlane16B32 || opcode == RdnaOpcode::VPermlanex16B32; } +bool acceptsDpp8(RdnaOpcode opcode) { + switch (opcode) { + case RdnaOpcode::VReadfirstlaneB32: + case RdnaOpcode::VCvtI32F64: + case RdnaOpcode::VCvtF64I32: + case RdnaOpcode::VCvtF32F64: + case RdnaOpcode::VCvtF64F32: + case RdnaOpcode::VCvtU32F64: + case RdnaOpcode::VCvtF64U32: + case RdnaOpcode::VTruncF64: + case RdnaOpcode::VCeilF64: + case RdnaOpcode::VRndneF64: + case RdnaOpcode::VFloorF64: + case RdnaOpcode::VFrexpExpI32F64: + case RdnaOpcode::VFrexpMantF64: + case RdnaOpcode::VFractF64: + case RdnaOpcode::VSwapB32: + case RdnaOpcode::VSwaprelB32: + case RdnaOpcode::VMacLegacyF32: + case RdnaOpcode::VMadmkF32: + case RdnaOpcode::VMadakF32: + case RdnaOpcode::VFmamkF16: + case RdnaOpcode::VFmaakF16: + case RdnaOpcode::VPkFmacF16: + case RdnaOpcode::VMovrelsB32: + case RdnaOpcode::VMovrelsdB32: + case RdnaOpcode::VMovrelsd2B32: + return false; + default: return true; + } +} + bool isNativeVop3F16TernaryOpcode(RdnaOpcode opcode) { return opcode == RdnaOpcode::VMin3F16 || opcode == RdnaOpcode::VMax3F16 || opcode == RdnaOpcode::VMed3F16 || opcode == RdnaOpcode::VDivFixupF16 || @@ -1152,7 +1184,14 @@ void applyDppModifier(RdnaOperand& operand, std::uint32_t modifier) { operand.dppRowMask = (modifier >> 28u) & 0xfu; } -void decodeVop1Dpp(std::uint32_t programCounter, std::span code, std::uint32_t wordIndex, std::uint32_t vdst, RdnaInstruction& instruction) { +void applyDpp8Modifier(RdnaOperand& operand, std::uint32_t modifier, bool fetchInactive) { + operand.dpp = true; + operand.dpp8 = true; + operand.dppCtrl = modifier >> 8u; + operand.dppFetchInactive = fetchInactive; +} + +void decodeVop1Dpp(std::uint32_t programCounter, std::span code, std::uint32_t wordIndex, std::uint32_t form, std::uint32_t vdst, RdnaInstruction& instruction) { if (static_cast(wordIndex) + 1u >= code.size()) { throw std::out_of_range("truncated VOP1 DPP instruction"); } @@ -1164,7 +1203,13 @@ void decodeVop1Dpp(std::uint32_t programCounter, std::span ? DecodeRdnaScalarDestination(vdst, programCounter) : DecodeRdnaVectorGpr(vdst); instruction.source0 = DecodeRdnaScalarSource(src0 + 256u, programCounter); - applyDppModifier(instruction.source0, modifier); + if (form == 250u) { + applyDppModifier(instruction.source0, modifier); + } else if (acceptsDpp8(instruction.op)) { + applyDpp8Modifier(instruction.source0, modifier, form == 234u); + } else { + throw std::invalid_argument("DPP8 modifier is not supported for opcode"); + } instruction.sourceCount = 1; if (!isVop1FloatSourceOpcode(instruction.op) && (instruction.source0.negate || instruction.source0.absolute)) { @@ -1346,7 +1391,7 @@ void decodeVop2Sdwa(std::uint32_t programCounter, std::span finalizeVop2Instruction(code, wordIndex, instruction); } -void decodeVop2Dpp(std::uint32_t programCounter, std::span code, std::uint32_t wordIndex, std::uint32_t opcode, std::uint32_t vdst, std::uint32_t vsrc1, RdnaInstruction& instruction) { +void decodeVop2Dpp(std::uint32_t programCounter, std::span code, std::uint32_t wordIndex, std::uint32_t form, std::uint32_t vdst, std::uint32_t vsrc1, RdnaInstruction& instruction) { if (static_cast(wordIndex) + 1u >= code.size()) { throw std::out_of_range("truncated VOP2 DPP instruction"); } @@ -1357,9 +1402,15 @@ void decodeVop2Dpp(std::uint32_t programCounter, std::span instruction.destination = DecodeRdnaVectorGpr(vdst); instruction.source1 = DecodeRdnaVectorGpr(vsrc1); instruction.source0 = DecodeRdnaScalarSource(src0 + 256u, programCounter); - applyDppModifier(instruction.source0, modifier); - instruction.source1.negate = ((modifier >> 22u) & 0x1u) != 0u; - instruction.source1.absolute = ((modifier >> 23u) & 0x1u) != 0u; + if (form == 250u) { + applyDppModifier(instruction.source0, modifier); + instruction.source1.negate = ((modifier >> 22u) & 0x1u) != 0u; + instruction.source1.absolute = ((modifier >> 23u) & 0x1u) != 0u; + } else if (acceptsDpp8(instruction.op)) { + applyDpp8Modifier(instruction.source0, modifier, form == 234u); + } else { + throw std::invalid_argument("DPP8 modifier is not supported for opcode"); + } const bool packedFmac = instruction.op == RdnaOpcode::VPkFmacF16; if (packedFmac) { instruction.source0.negateHi = instruction.source0.negate; @@ -1955,8 +2006,8 @@ RdnaInstruction DecodeRdnaVop1(std::uint32_t programCounter, std::span= 0x101u && control <= 0x10fu) { return EmitDppRowShiftTargetLane(state, subid, control & 0xfu, true); diff --git a/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp b/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp index 235393ebe..98ae5480b 100644 --- a/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp +++ b/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp @@ -5,6 +5,15 @@ namespace ShaderRecompiler { +namespace { + +DppMoveFlags dppFlags(const RdnaOperand& operand) { + const auto control = operand.dpp8 ? DppMoveFlags::Lanes8 | operand.dppCtrl : operand.dppCtrl; + return {control, static_cast(operand.dppRowMask), static_cast(operand.dppBankMask), operand.dppFetchInactive, operand.dppBoundCtrl}; +} + +} + const RdnaOperand& TranslationContext::sourceAt(const RdnaInstruction& inst, std::uint32_t index) { switch (index) { case 0u: return inst.source0; @@ -26,6 +35,7 @@ RdnaOperand TranslationContext::destinationOperand(const RdnaInstruction& inst) continue; } destination.dpp = true; + destination.dpp8 = source.dpp8; destination.dppCtrl = source.dppCtrl; destination.dppRowMask = source.dppRowMask; destination.dppBankMask = source.dppBankMask; @@ -111,6 +121,7 @@ RdnaOperand TranslationContext::plainOperand(const RdnaOperand& operand) { result.dppFetchInactive = false; result.dppBoundCtrl = false; result.dpp = false; + result.dpp8 = false; return result; } @@ -223,7 +234,7 @@ void TranslationContext::writeOperand(const RdnaOperand& operand, IrValue* value IrU32 TranslationContext::applyBitSourceModifiers(const RdnaOperand& operand, IrU32 value) { if (operand.dpp) { - const DppMoveFlags flags{static_cast(operand.dppCtrl), static_cast(operand.dppRowMask), static_cast(operand.dppBankMask), operand.dppFetchInactive, operand.dppBoundCtrl}; + const auto flags = dppFlags(operand); value = IrU32(ir.Emit(IrOpcode::DppMoveU32, IrType::U32, {&value.Value(), &ir.GetExec()}, flags)); } if (operand.sdwaSel != 6u) { @@ -355,7 +366,7 @@ void TranslationContext::writeRawU32(const RdnaOperand& operand, IrU32 value) { const VectorReg reg = static_cast(operand.reg); IrValue& old = ir.GetVectorReg(reg); if (operand.dpp) { - const DppMoveFlags flags{static_cast(operand.dppCtrl), static_cast(operand.dppRowMask), static_cast(operand.dppBankMask), operand.dppFetchInactive, operand.dppBoundCtrl}; + const auto flags = dppFlags(operand); value = IrU32(ir.Emit(IrOpcode::DppUpdateU32, IrType::U32, {&value.Value(), &old, &ir.GetExec()}, flags)); } else { value = IrU32(ir.Select(ir.GetExec(), value.Value(), old)); From fd159a63b0e3494a4d6cd93404245a75e294ed60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 16:30:31 +0200 Subject: [PATCH 047/164] fix(shader): DPP16 lanes whose source lane is inactive Without bound_ctrl and fetch-inactive, a DPP16 lane whose source lane is inactive keeps its old value instead of being written with 0. With fetch-inactive, a source outside the row reads 0. Co-Authored-By: Claude --- .../tests/execution/Dpp16InactiveSource.cpp | 287 ++++++++++++++++++ .../src/SpirvAlu/SpirvAluEmitterMath.cpp | 14 +- 2 files changed, 296 insertions(+), 5 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Dpp16InactiveSource.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16InactiveSource.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16InactiveSource.cpp new file mode 100644 index 000000000..1350e1ff2 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16InactiveSource.cpp @@ -0,0 +1,287 @@ +#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 MaxThreads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Checked = 15; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Wave32Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0xbefe03ff, 0x33cc0ff3, 0x7e1402fa, 0xff004b04, 0x7e1602fa, + 0xff044b04, 0x7e1802fa, 0xff084b04, 0x7e1a02fa, 0xff011104, 0x7e1c02fa, 0xff051104, 0x7e1e02fa, + 0xff091104, 0x7e2002fa, 0xff0d1104, 0x7e2202fa, 0x5a014004, 0x4a240afa, 0xff004b04, 0x4a260afa, + 0xff044b04, 0x4a280afa, 0xff084b04, 0x4a2a0afa, 0xff011104, 0x4a2c0afa, 0xff051104, 0x4a2e0afa, + 0xff091104, 0x4a300afa, 0xff0d1104, 0xbefe03c1, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, + 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +alignas(256) constexpr std::array Wave64Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0xbefe03ff, 0x33cc0ff3, 0xbeff03ff, 0xc3a5960f, 0x7e1402fa, + 0xff004b04, 0x7e1602fa, 0xff044b04, 0x7e1802fa, 0xff084b04, 0x7e1a02fa, 0xff011104, 0x7e1c02fa, + 0xff051104, 0x7e1e02fa, 0xff091104, 0x7e2002fa, 0xff0d1104, 0x7e2202fa, 0x5a014004, 0x4a240afa, + 0xff004b04, 0x4a260afa, 0xff044b04, 0x4a280afa, 0xff084b04, 0x4a2a0afa, 0xff011104, 0x4a2c0afa, + 0xff051104, 0x4a2e0afa, 0xff091104, 0x4a300afa, 0xff0d1104, 0xbefe04c1, 0xe0781000, 0x80010a03, + 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[64][4] = { + {0x1e7ea419u, 0x51c9bc70u, 0x00000000u, 0x00000000u}, + {0x80a4df5au, 0xf38b2ffcu, 0x00000000u, 0x00000000u}, + {0x8306d03bu, 0xa5aec797u, 0x00000000u, 0x00000000u}, + {0xdc28ff90u, 0xf3f49249u, 0x00000000u, 0x00000000u}, + {0x1a466884u, 0xe255accbu, 0x00000000u, 0x00000000u}, + {0x39292d22u, 0xe5121482u, 0x00000000u, 0x00000000u}, + {0x99dd251du, 0x9f199504u, 0x00000000u, 0x00000000u}, + {0x8e7aa6e9u, 0x6bad6be2u, 0x00000000u, 0x00000000u}, + {0xc88b2875u, 0x9293de8fu, 0x00000000u, 0x00000000u}, + {0x8c3d5f16u, 0xd7a7a3ccu, 0x00000000u, 0x00000000u}, + {0xbb049a79u, 0xc6cd75e9u, 0x00000000u, 0x00000000u}, + {0xc4a334bfu, 0x7dabe929u, 0x00000000u, 0x00000000u}, + {0xc0433cbdu, 0xc5e818fau, 0x00000000u, 0x00000000u}, + {0x96263ae6u, 0x70eb9a0au, 0x00000000u, 0x00000000u}, + {0x3d6c51e3u, 0x00a61f93u, 0x00000000u, 0x00000000u}, + {0x9d3c7decu, 0x14aa4e71u, 0x00000000u, 0x00000000u}, + {0x1c593af5u, 0x49889310u, 0x00000000u, 0x00000000u}, + {0xd11745adu, 0x1919e93au, 0x00000000u, 0x00000000u}, + {0x731c9452u, 0x02f0ee99u, 0x00000000u, 0x00000000u}, + {0xd0944996u, 0xe4163207u, 0x00000000u, 0x00000000u}, + {0xaf67d461u, 0x7d836e77u, 0x00000000u, 0x00000000u}, + {0xadec6f11u, 0x5071950eu, 0x00000000u, 0x00000000u}, + {0x35d14880u, 0x65b00a2du, 0x00000000u, 0x00000000u}, + {0x406329bcu, 0x59001ac9u, 0x00000000u, 0x00000000u}, + {0xfadb8908u, 0xffb88309u, 0x00000000u, 0x00000000u}, + {0xede7bd0cu, 0x5b4c8012u, 0x00000000u, 0x00000000u}, + {0xce1d62e0u, 0x60581ccau, 0x00000000u, 0x00000000u}, + {0xffe976abu, 0xbef7e268u, 0x00000000u, 0x00000000u}, + {0x837a7935u, 0xa341738cu, 0x00000000u, 0x00000000u}, + {0x1389b372u, 0xb91dddd9u, 0x00000000u, 0x00000000u}, + {0x57241955u, 0x16f44881u, 0x00000000u, 0x00000000u}, + {0x8eb5140fu, 0x89be9c1cu, 0x00000000u, 0x00000000u}, + {0x4ac87fc0u, 0x49a047dcu, 0x00000000u, 0x00000000u}, + {0x751b4c83u, 0x24114258u, 0x00000000u, 0x00000000u}, + {0xe060a724u, 0xa51149bbu, 0x00000000u, 0x00000000u}, + {0xb437bdb5u, 0xb766ff10u, 0x00000000u, 0x00000000u}, + {0x93d20470u, 0x4f1a8a1du, 0x00000000u, 0x00000000u}, + {0x06555096u, 0xd9090b89u, 0x00000000u, 0x00000000u}, + {0xb5fb12e0u, 0x5e6c9992u, 0x00000000u, 0x00000000u}, + {0x5d111a9du, 0x7608d942u, 0x00000000u, 0x00000000u}, + {0x6c167229u, 0x17371472u, 0x00000000u, 0x00000000u}, + {0x66160227u, 0xe41686cdu, 0x00000000u, 0x00000000u}, + {0x95f19a51u, 0x8df05f25u, 0x00000000u, 0x00000000u}, + {0x7f9ac098u, 0xde0279e1u, 0x00000000u, 0x00000000u}, + {0x1dcf884cu, 0x6ce9da66u, 0x00000000u, 0x00000000u}, + {0x81a0ba05u, 0xcd73de00u, 0x00000000u, 0x00000000u}, + {0xc0099ebau, 0xdde3a361u, 0x00000000u, 0x00000000u}, + {0x99a46df0u, 0xe01c683eu, 0x00000000u, 0x00000000u}, + {0x7db4304du, 0x65ed0de4u, 0x00000000u, 0x00000000u}, + {0xeab06e9bu, 0x864fa3f3u, 0x00000000u, 0x00000000u}, + {0x423674a6u, 0xd82fb0f1u, 0x00000000u, 0x00000000u}, + {0x6b82e6c9u, 0x90624fe3u, 0x00000000u, 0x00000000u}, + {0xed1d9d16u, 0xfb759e0fu, 0x00000000u, 0x00000000u}, + {0x7aac319fu, 0x8350a73fu, 0x00000000u, 0x00000000u}, + {0x849acfb5u, 0xce1fd3d9u, 0x00000000u, 0x00000000u}, + {0x067f0cfcu, 0x93550840u, 0x00000000u, 0x00000000u}, + {0x397a7623u, 0xc40d31b5u, 0x00000000u, 0x00000000u}, + {0x2106119eu, 0xd1711cbdu, 0x00000000u, 0x00000000u}, + {0x0c83e786u, 0xf675299bu, 0x00000000u, 0x00000000u}, + {0xb464c554u, 0x85f184e0u, 0x00000000u, 0x00000000u}, + {0xe306f0c4u, 0xaba4c70eu, 0x00000000u, 0x00000000u}, + {0x1a42721eu, 0xb7a1774fu, 0x00000000u, 0x00000000u}, + {0x9f4fb02bu, 0x6dedc86au, 0x00000000u, 0x00000000u}, + {0x78549014u, 0x2475263cu, 0x00000000u, 0x00000000u} +}; + +constexpr std::uint32_t Expected32[32][15] = { + {0xdeaddeadu, 0xdc28ff90u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x00000000u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x2df2bc00u, 0x51c9bc70u, 0xdeaddeadu, 0xdeaddeadu, 0x51c9bc70u, 0x51c9bc70u}, + {0xdeaddeadu, 0x8306d03bu, 0x00000000u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0xdeaddeadu, 0xdeaddeadu, 0x76920037u, 0xf38b2ffcu, 0x1209d415u, 0x1209d415u, 0x1209d415u, 0x1209d415u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8e7aa6e9u, 0x8e7aa6e9u, 0x8e7aa6e9u, 0xdeaddeadu, 0xdc28ff90u, 0x00000000u, 0xdc28ff90u, 0xc4a334bfu, 0x70d053b4u, 0x70d053b4u, 0x70d053b4u, 0xdeaddeadu, 0xbe7eac5bu, 0xe255accbu, 0xbe7eac5bu}, + {0x99dd251du, 0x99dd251du, 0x99dd251du, 0x1a466884u, 0x1a466884u, 0x1a466884u, 0x1a466884u, 0xbb049a79u, 0x7eef399fu, 0x7eef399fu, 0x7eef399fu, 0xff587d06u, 0xff587d06u, 0xff587d06u, 0xff587d06u}, + {0x1a466884u, 0x1a466884u, 0x1a466884u, 0x39292d22u, 0x39292d22u, 0x39292d22u, 0x39292d22u, 0x8c3d5f16u, 0xb95ffd88u, 0xb95ffd88u, 0xb95ffd88u, 0xd842c226u, 0xd842c226u, 0xd842c226u, 0xd842c226u}, + {0x39292d22u, 0x39292d22u, 0x39292d22u, 0x99dd251du, 0x99dd251du, 0x99dd251du, 0x99dd251du, 0xc88b2875u, 0xa4d69904u, 0xa4d69904u, 0xa4d69904u, 0x058a90ffu, 0x058a90ffu, 0x058a90ffu, 0x058a90ffu}, + {0xc4a334bfu, 0xc4a334bfu, 0xc4a334bfu, 0x8e7aa6e9u, 0x8e7aa6e9u, 0x8e7aa6e9u, 0x8e7aa6e9u, 0xdeaddeadu, 0x5737134eu, 0x5737134eu, 0x5737134eu, 0x210e8578u, 0x210e8578u, 0x210e8578u, 0x210e8578u}, + {0xbb049a79u, 0xbb049a79u, 0xbb049a79u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0xdeaddeadu, 0x92ac3e45u, 0x92ac3e45u, 0x92ac3e45u, 0xa032cc41u, 0xa032cc41u, 0xa032cc41u, 0xa032cc41u}, + {0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0x8c3d5f16u, 0x8c3d5f16u, 0x8c3d5f16u, 0x8c3d5f16u, 0xdeaddeadu, 0x8f589e5eu, 0x8f589e5eu, 0x8f589e5eu, 0x530ad4ffu, 0x530ad4ffu, 0x530ad4ffu, 0x530ad4ffu}, + {0x8c3d5f16u, 0x8c3d5f16u, 0x8c3d5f16u, 0xbb049a79u, 0xbb049a79u, 0xbb049a79u, 0xbb049a79u, 0xdeaddeadu, 0x09e9483fu, 0x09e9483fu, 0x09e9483fu, 0x38b083a2u, 0x38b083a2u, 0x38b083a2u, 0x38b083a2u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0x1c593af5u, 0x00000000u, 0xdeaddeadu, 0xd11745adu, 0x00000000u, 0xd11745adu, 0xdeaddeadu, 0xdeaddeadu, 0x1f4a298eu, 0x02f0ee99u, 0xdeaddeadu, 0xd4083446u, 0x02f0ee99u, 0xd4083446u}, + {0xdeaddeadu, 0xd11745adu, 0x00000000u, 0x731c9452u, 0x731c9452u, 0x731c9452u, 0x731c9452u, 0xdeaddeadu, 0xdeaddeadu, 0xb52d77b4u, 0xe4163207u, 0x5732c659u, 0x5732c659u, 0x5732c659u, 0x5732c659u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xaf67d461u, 0x00000000u, 0xdeaddeadu, 0xadec6f11u, 0x00000000u, 0xadec6f11u, 0xdeaddeadu, 0xdeaddeadu, 0x1517de8eu, 0x65b00a2du, 0xdeaddeadu, 0x139c793eu, 0x65b00a2du, 0x139c793eu}, + {0xdeaddeadu, 0xadec6f11u, 0x00000000u, 0x35d14880u, 0x35d14880u, 0x35d14880u, 0x35d14880u, 0xdeaddeadu, 0xdeaddeadu, 0x06ec89dau, 0x59001ac9u, 0x8ed16349u, 0x8ed16349u, 0x8ed16349u, 0x8ed16349u}, + {0xdeaddeadu, 0xffe976abu, 0x00000000u, 0x406329bcu, 0x406329bcu, 0x406329bcu, 0x406329bcu, 0xdeaddeadu, 0xdeaddeadu, 0xffa1f9b4u, 0xffb88309u, 0x401bacc5u, 0x401bacc5u, 0x401bacc5u, 0x401bacc5u}, + {0xdeaddeadu, 0xce1d62e0u, 0x00000000u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u, 0xdeaddeadu, 0xdeaddeadu, 0x2969e2f2u, 0x5b4c8012u, 0x5628091au, 0x5628091au, 0x5628091au, 0x5628091au}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0x8eb5140fu, 0x00000000u, 0xdeaddeadu, 0xffe976abu, 0x00000000u, 0xffe976abu, 0xdeaddeadu, 0xdeaddeadu, 0x31f6879bu, 0xa341738cu, 0xdeaddeadu, 0xa32aea37u, 0xa341738cu, 0xa32aea37u}, + {0xdeaddeadu, 0x57241955u, 0x00000000u, 0x837a7935u, 0x837a7935u, 0x837a7935u, 0x837a7935u, 0xdeaddeadu, 0xdeaddeadu, 0x1041f72eu, 0xb91dddd9u, 0x3c98570eu, 0x3c98570eu, 0x3c98570eu, 0x3c98570eu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu} +}; +constexpr std::uint32_t Expected64[64][15] = { + {0xdeaddeadu, 0xdc28ff90u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x00000000u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x2df2bc00u, 0x51c9bc70u, 0xdeaddeadu, 0xdeaddeadu, 0x51c9bc70u, 0x51c9bc70u}, + {0xdeaddeadu, 0x8306d03bu, 0x00000000u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0xdeaddeadu, 0xdeaddeadu, 0x76920037u, 0xf38b2ffcu, 0x1209d415u, 0x1209d415u, 0x1209d415u, 0x1209d415u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8e7aa6e9u, 0x8e7aa6e9u, 0x8e7aa6e9u, 0xdeaddeadu, 0xdc28ff90u, 0x00000000u, 0xdc28ff90u, 0xc4a334bfu, 0x70d053b4u, 0x70d053b4u, 0x70d053b4u, 0xdeaddeadu, 0xbe7eac5bu, 0xe255accbu, 0xbe7eac5bu}, + {0x99dd251du, 0x99dd251du, 0x99dd251du, 0x1a466884u, 0x1a466884u, 0x1a466884u, 0x1a466884u, 0xbb049a79u, 0x7eef399fu, 0x7eef399fu, 0x7eef399fu, 0xff587d06u, 0xff587d06u, 0xff587d06u, 0xff587d06u}, + {0x1a466884u, 0x1a466884u, 0x1a466884u, 0x39292d22u, 0x39292d22u, 0x39292d22u, 0x39292d22u, 0x8c3d5f16u, 0xb95ffd88u, 0xb95ffd88u, 0xb95ffd88u, 0xd842c226u, 0xd842c226u, 0xd842c226u, 0xd842c226u}, + {0x39292d22u, 0x39292d22u, 0x39292d22u, 0x99dd251du, 0x99dd251du, 0x99dd251du, 0x99dd251du, 0xc88b2875u, 0xa4d69904u, 0xa4d69904u, 0xa4d69904u, 0x058a90ffu, 0x058a90ffu, 0x058a90ffu, 0x058a90ffu}, + {0xc4a334bfu, 0xc4a334bfu, 0xc4a334bfu, 0x8e7aa6e9u, 0x8e7aa6e9u, 0x8e7aa6e9u, 0x8e7aa6e9u, 0xdeaddeadu, 0x5737134eu, 0x5737134eu, 0x5737134eu, 0x210e8578u, 0x210e8578u, 0x210e8578u, 0x210e8578u}, + {0xbb049a79u, 0xbb049a79u, 0xbb049a79u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0xdeaddeadu, 0x92ac3e45u, 0x92ac3e45u, 0x92ac3e45u, 0xa032cc41u, 0xa032cc41u, 0xa032cc41u, 0xa032cc41u}, + {0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0x8c3d5f16u, 0x8c3d5f16u, 0x8c3d5f16u, 0x8c3d5f16u, 0xdeaddeadu, 0x8f589e5eu, 0x8f589e5eu, 0x8f589e5eu, 0x530ad4ffu, 0x530ad4ffu, 0x530ad4ffu, 0x530ad4ffu}, + {0x8c3d5f16u, 0x8c3d5f16u, 0x8c3d5f16u, 0xbb049a79u, 0xbb049a79u, 0xbb049a79u, 0xbb049a79u, 0xdeaddeadu, 0x09e9483fu, 0x09e9483fu, 0x09e9483fu, 0x38b083a2u, 0x38b083a2u, 0x38b083a2u, 0x38b083a2u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0x1c593af5u, 0x00000000u, 0xdeaddeadu, 0xd11745adu, 0x00000000u, 0xd11745adu, 0xdeaddeadu, 0xdeaddeadu, 0x1f4a298eu, 0x02f0ee99u, 0xdeaddeadu, 0xd4083446u, 0x02f0ee99u, 0xd4083446u}, + {0xdeaddeadu, 0xd11745adu, 0x00000000u, 0x731c9452u, 0x731c9452u, 0x731c9452u, 0x731c9452u, 0xdeaddeadu, 0xdeaddeadu, 0xb52d77b4u, 0xe4163207u, 0x5732c659u, 0x5732c659u, 0x5732c659u, 0x5732c659u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xaf67d461u, 0x00000000u, 0xdeaddeadu, 0xadec6f11u, 0x00000000u, 0xadec6f11u, 0xdeaddeadu, 0xdeaddeadu, 0x1517de8eu, 0x65b00a2du, 0xdeaddeadu, 0x139c793eu, 0x65b00a2du, 0x139c793eu}, + {0xdeaddeadu, 0xadec6f11u, 0x00000000u, 0x35d14880u, 0x35d14880u, 0x35d14880u, 0x35d14880u, 0xdeaddeadu, 0xdeaddeadu, 0x06ec89dau, 0x59001ac9u, 0x8ed16349u, 0x8ed16349u, 0x8ed16349u, 0x8ed16349u}, + {0xdeaddeadu, 0xffe976abu, 0x00000000u, 0x406329bcu, 0x406329bcu, 0x406329bcu, 0x406329bcu, 0xdeaddeadu, 0xdeaddeadu, 0xffa1f9b4u, 0xffb88309u, 0x401bacc5u, 0x401bacc5u, 0x401bacc5u, 0x401bacc5u}, + {0xdeaddeadu, 0xce1d62e0u, 0x00000000u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u, 0xdeaddeadu, 0xdeaddeadu, 0x2969e2f2u, 0x5b4c8012u, 0x5628091au, 0x5628091au, 0x5628091au, 0x5628091au}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0x8eb5140fu, 0x00000000u, 0xdeaddeadu, 0xffe976abu, 0x00000000u, 0xffe976abu, 0xdeaddeadu, 0xdeaddeadu, 0x31f6879bu, 0xa341738cu, 0xdeaddeadu, 0xa32aea37u, 0xa341738cu, 0xa32aea37u}, + {0xdeaddeadu, 0x57241955u, 0x00000000u, 0x837a7935u, 0x837a7935u, 0x837a7935u, 0x837a7935u, 0xdeaddeadu, 0xdeaddeadu, 0x1041f72eu, 0xb91dddd9u, 0x3c98570eu, 0x3c98570eu, 0x3c98570eu, 0x3c98570eu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb437bdb5u, 0xb437bdb5u, 0xb437bdb5u, 0xdeaddeadu, 0xdeaddeadu, 0x00000000u, 0x00000000u, 0xdeaddeadu, 0xfdd80591u, 0xfdd80591u, 0xfdd80591u, 0xdeaddeadu, 0xdeaddeadu, 0x49a047dcu, 0x49a047dcu}, + {0xe060a724u, 0xe060a724u, 0xe060a724u, 0x4ac87fc0u, 0x4ac87fc0u, 0x4ac87fc0u, 0x4ac87fc0u, 0xdeaddeadu, 0x0471e97cu, 0x0471e97cu, 0x0471e97cu, 0x6ed9c218u, 0x6ed9c218u, 0x6ed9c218u, 0x6ed9c218u}, + {0x4ac87fc0u, 0x4ac87fc0u, 0x4ac87fc0u, 0x751b4c83u, 0x751b4c83u, 0x751b4c83u, 0x751b4c83u, 0xdeaddeadu, 0xefd9c97bu, 0xefd9c97bu, 0xefd9c97bu, 0x1a2c963eu, 0x1a2c963eu, 0x1a2c963eu, 0x1a2c963eu}, + {0x751b4c83u, 0x751b4c83u, 0x751b4c83u, 0xe060a724u, 0xe060a724u, 0xe060a724u, 0xe060a724u, 0xdeaddeadu, 0x2c824b93u, 0x2c824b93u, 0x2c824b93u, 0x97c7a634u, 0x97c7a634u, 0x97c7a634u, 0x97c7a634u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x95f19a51u, 0x95f19a51u, 0x95f19a51u, 0xdeaddeadu, 0x6c167229u, 0x00000000u, 0x6c167229u, 0xdeaddeadu, 0x7a08211eu, 0x7a08211eu, 0x7a08211eu, 0xdeaddeadu, 0x502cf8f6u, 0xe41686cdu, 0x502cf8f6u}, + {0xdeaddeadu, 0x6c167229u, 0x00000000u, 0x66160227u, 0x66160227u, 0x66160227u, 0x66160227u, 0xdeaddeadu, 0xdeaddeadu, 0xfa06d14eu, 0x8df05f25u, 0xf406614cu, 0xf406614cu, 0xf406614cu, 0xf406614cu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x99a46df0u, 0x99a46df0u, 0x99a46df0u, 0xdeaddeadu, 0x7f9ac098u, 0x00000000u, 0x7f9ac098u, 0xb437bdb5u, 0x068e4856u, 0x068e4856u, 0x068e4856u, 0xdeaddeadu, 0xec849afeu, 0x6ce9da66u, 0xec849afeu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0x81a0ba05u, 0x00000000u, 0xdeaddeadu, 0xc0099ebau, 0x00000000u, 0xc0099ebau, 0x4ac87fc0u, 0xdeaddeadu, 0x61bd2243u, 0xe01c683eu, 0xdeaddeadu, 0xa02606f8u, 0xe01c683eu, 0xa02606f8u}, + {0xdeaddeadu, 0x6b82e6c9u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x00000000u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0xd16ff4adu, 0x65ed0de4u, 0xdeaddeadu, 0xdeaddeadu, 0x65ed0de4u, 0x65ed0de4u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x7db4304du, 0x7db4304du, 0x7db4304du, 0xdeaddeadu, 0xeab06e9bu, 0x00000000u, 0xeab06e9bu, 0xdeaddeadu, 0x55e3e13eu, 0x55e3e13eu, 0x55e3e13eu, 0xdeaddeadu, 0xc2e01f8cu, 0xd82fb0f1u, 0xc2e01f8cu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0x849acfb5u, 0x00000000u, 0xdeaddeadu, 0xed1d9d16u, 0x00000000u, 0xed1d9d16u, 0xdeaddeadu, 0xdeaddeadu, 0x07eb76f4u, 0x8350a73fu, 0xdeaddeadu, 0x706e4455u, 0x8350a73fu, 0x706e4455u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x7aac319fu, 0x7aac319fu, 0x7aac319fu, 0xdeaddeadu, 0x849acfb5u, 0x00000000u, 0x849acfb5u, 0xdeaddeadu, 0x0e0139dfu, 0x0e0139dfu, 0x0e0139dfu, 0xdeaddeadu, 0x17efd7f5u, 0x93550840u, 0x17efd7f5u}, + {0xdeaddeadu, 0xb464c554u, 0x00000000u, 0x067f0cfcu, 0x067f0cfcu, 0x067f0cfcu, 0x067f0cfcu, 0xdeaddeadu, 0xdeaddeadu, 0x7871f709u, 0xc40d31b5u, 0xca8c3eb1u, 0xca8c3eb1u, 0xca8c3eb1u, 0xca8c3eb1u}, + {0xdeaddeadu, 0x0c83e786u, 0x00000000u, 0x397a7623u, 0x397a7623u, 0x397a7623u, 0x397a7623u, 0xdeaddeadu, 0xdeaddeadu, 0xddf50443u, 0xd1711cbdu, 0x0aeb92e0u, 0x0aeb92e0u, 0x0aeb92e0u, 0x0aeb92e0u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xe306f0c4u, 0x00000000u, 0xdeaddeadu, 0x1a42721eu, 0x00000000u, 0x1a42721eu, 0xdeaddeadu, 0xdeaddeadu, 0x50f4b92eu, 0x6dedc86au, 0xdeaddeadu, 0x88303a88u, 0x6dedc86au, 0x88303a88u}, + {0xdeaddeadu, 0x1a42721eu, 0x00000000u, 0x9f4fb02bu, 0x9f4fb02bu, 0x9f4fb02bu, 0x9f4fb02bu, 0xdeaddeadu, 0xdeaddeadu, 0x3eb7985au, 0x2475263cu, 0xc3c4d667u, 0xc3c4d667u, 0xc3c4d667u, 0xc3c4d667u} +}; + +constexpr const char* Names[Checked] = {"mov_quad_perm", "mov_quad_perm_fi", "mov_quad_perm_bc", "mov_row_shr", "mov_row_shr_fi", "mov_row_shr_bc", "mov_row_shr_bc_fi", "mov_row_mirror_masks", "add_quad_perm", "add_quad_perm_fi", "add_quad_perm_bc", "add_row_shr", "add_row_shr_fi", "add_row_shr_bc", "add_row_shr_bc_fi"}; + +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; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + for (std::uint32_t tid = 0; tid < MaxThreads; ++tid) std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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{{waveSize, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + 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(); +} + +template +void Check(const std::uint32_t (&expected)[Lanes][Checked]) { + for (std::uint32_t tid = 0; tid < Lanes; ++tid) { + for (std::uint32_t index = 0; index < Checked; ++index) { + const auto actual = Output[tid * Results + index]; + Require(actual == expected[tid][index], "dpp16 inactive source wave" + std::to_string(Lanes) + ": lane " + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < 32) { + std::printf("skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); + return VulkanTestSkipped; + } + Run(*device, Wave32Code, 32, device->ComputeTarget(32)); + Check(Expected32); + Run(*device, Wave64Code, 64, device->Target()); + Check(Expected64); + Run(*device, Wave64Code, 64, device->ComputeTarget(32)); + Check(Expected64); + std::puts("dpp16 inactive source tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp index 13b9b7244..8c3ca95fc 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp @@ -160,8 +160,9 @@ std::uint32_t EmitF32ToU32(SpirvEmitterState& state, std::uint32_t src, bool sig return Select(state, TypeU32(state), zero, ConstantU32(state, 0u), high); } -std::uint32_t EmitDppWriteCondition(SpirvValueEmitContext& ctx, const DppMoveFlags& flags, std::uint32_t exec) { +std::uint32_t EmitDppWriteCondition(SpirvValueEmitContext& ctx, const IrValue& inst) { auto& state = ctx.state; + const auto flags = inst.Flags(); const auto lane = EmitSubgroupLocalInvocationId(state); const auto bankShift = state.module.AllocateId(); const auto rowShift = state.module.AllocateId(); @@ -191,9 +192,13 @@ std::uint32_t EmitDppWriteCondition(SpirvValueEmitContext& ctx, const DppMoveFla const auto bounded = state.module.AllocateId(); state.module.AddFunction(spv::OpLogicalAnd, TypeBool(state), bounded, writable, target.valid); writable = bounded; + if (!flags.fetchInactive && (flags.control & DppMoveFlags::Lanes8) == 0u) { + const auto sourceActive = EmitBallotLaneActiveBool(state, ctx.Ballot(inst.Argument(2)), target.lane); + writable = Binary(state, spv::OpLogicalAnd, TypeBool(state), writable, sourceActive); + } } const auto result = state.module.AllocateId(); - state.module.AddFunction(spv::OpLogicalAnd, TypeBool(state), result, exec, writable); + state.module.AddFunction(spv::OpLogicalAnd, TypeBool(state), result, ctx.Arg(inst, 2), writable); return result; } @@ -493,7 +498,7 @@ std::uint32_t EmitDppMoveU32(SpirvValueEmitContext& ctx, const IrValue& inst) { const auto target = EmitDppTargetLane(state, flags.control); const auto shuffled = ctx.Shuffle(inst, 0, target.lane); if (flags.fetchInactive) { - return shuffled; + return EmitNative(state, target.valid, shuffled, ConstantU32(state, 0u)); } const auto ballot = ctx.Ballot(inst.Argument(1)); const auto sourceActive = EmitBallotLaneActiveBool(state, ballot, target.lane); @@ -503,8 +508,7 @@ std::uint32_t EmitDppMoveU32(SpirvValueEmitContext& ctx, const IrValue& inst) { } std::uint32_t EmitDppUpdateU32(SpirvValueEmitContext& ctx, const IrValue& inst) { - const auto flags = inst.Flags(); - const auto write = EmitDppWriteCondition(ctx, flags, ctx.Arg(inst, 2)); + const auto write = EmitDppWriteCondition(ctx, inst); return EmitNative(ctx.state, write, ctx.Arg(inst, 0), ctx.Arg(inst, 1)); } From 12befda5cfbe33c7db93c1da5678325307ead43d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 16:37:36 +0200 Subject: [PATCH 048/164] fix(shader): refuse DPP16 on opcodes with no DPP form Co-Authored-By: Claude --- .../tests/execution/DppNoForm.cpp | 97 +++++++++++++++++++ .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 23 +++-- 2 files changed, 112 insertions(+), 8 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/DppNoForm.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DppNoForm.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DppNoForm.cpp new file mode 100644 index 000000000..5f94f7849 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DppNoForm.cpp @@ -0,0 +1,97 @@ +#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 + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +struct NoFormOpcode { + bool vop2; + std::uint32_t opcode; + const char* name; +}; + +constexpr std::array NoFormOpcodes{{ + {false, 0x02u, "v_readfirstlane_b32"}, {false, 0x03u, "v_cvt_i32_f64"}, {false, 0x04u, "v_cvt_f64_i32"}, + {false, 0x0fu, "v_cvt_f32_f64"}, {false, 0x10u, "v_cvt_f64_f32"}, {false, 0x15u, "v_cvt_u32_f64"}, + {false, 0x16u, "v_cvt_f64_u32"}, {false, 0x17u, "v_trunc_f64"}, {false, 0x18u, "v_ceil_f64"}, + {false, 0x19u, "v_rndne_f64"}, {false, 0x1au, "v_floor_f64"}, {false, 0x3cu, "v_frexp_exp_i32_f64"}, + {false, 0x3du, "v_frexp_mant_f64"}, {false, 0x3eu, "v_fract_f64"}, {false, 0x65u, "v_swap_b32"}, + {false, 0x68u, "v_swaprel_b32"}, {true, 0x06u, "v_mac_legacy_f32"}, {true, 0x20u, "v_madmk_f32"}, + {true, 0x21u, "v_madak_f32"}, {true, 0x2cu, "v_fmamk_f32"}, {true, 0x2du, "v_fmaak_f32"}, + {true, 0x37u, "v_fmamk_f16"}, {true, 0x38u, "v_fmaak_f16"}, +}}; + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +auto Compile(AgcDriver::VulkanDevice& device, std::span code) { + std::vector userData(8, 0u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{32, 1, 1}, 0, {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 CheckRefused(AgcDriver::VulkanDevice& device, std::uint32_t word0, std::uint32_t word1, const std::string& reason, const std::string& what) { + alignas(256) const std::array code{word0, word1, 0x3c003c00u, 0xbf810000u}; + std::string refusal; + try { + static_cast(Compile(device, code)); + } catch (const std::exception& error) { + refusal = error.what(); + } + Require(refusal.find(reason) != std::string::npos, what + " " + Hex(word0) + " was not refused"); +} + +void CheckAccepted(AgcDriver::VulkanDevice& device, std::uint32_t word0, std::uint32_t word1) { + alignas(256) const std::array code{word0, word1, 0xbf810000u}; + static_cast(Compile(device, code)); +} + +std::uint32_t Encode(const NoFormOpcode& op, std::uint32_t form) { + return op.vop2 ? (op.opcode << 25u) | (10u << 17u) | (5u << 9u) | form : 0x7e000000u | (10u << 17u) | (op.opcode << 9u) | form; +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + constexpr std::uint32_t Dpp16 = 0xff00b104u; + constexpr std::uint32_t Dpp8 = 0x00fac604u; + for (const auto& op : NoFormOpcodes) { + const std::string name = op.name; + CheckRefused(*device, Encode(op, 0xfau), Dpp16, "DPP modifier is not supported for opcode", name + " with DPP16"); + CheckRefused(*device, Encode(op, 0xe9u), Dpp8, "DPP8 modifier is not supported for opcode", name + " with DPP8"); + CheckRefused(*device, Encode(op, 0xeau), Dpp8, "DPP8 modifier is not supported for opcode", name + " with DPP8 fi:1"); + } + CheckAccepted(*device, 0x7e1402fau, Dpp16); + CheckAccepted(*device, 0x4a140afau, Dpp16); + std::puts("dpp no form tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index 9ddc3f21c..007f27c6b 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -691,7 +691,7 @@ bool isPermlaneOpcode(RdnaOpcode opcode) { return opcode == RdnaOpcode::VPermlane16B32 || opcode == RdnaOpcode::VPermlanex16B32; } -bool acceptsDpp8(RdnaOpcode opcode) { +bool hasDppForm(RdnaOpcode opcode) { switch (opcode) { case RdnaOpcode::VReadfirstlaneB32: case RdnaOpcode::VCvtI32F64: @@ -714,12 +714,19 @@ bool acceptsDpp8(RdnaOpcode opcode) { case RdnaOpcode::VMadakF32: case RdnaOpcode::VFmamkF16: case RdnaOpcode::VFmaakF16: + return false; + default: return true; + } +} + +bool acceptsDpp8(RdnaOpcode opcode) { + switch (opcode) { case RdnaOpcode::VPkFmacF16: case RdnaOpcode::VMovrelsB32: case RdnaOpcode::VMovrelsdB32: case RdnaOpcode::VMovrelsd2B32: return false; - default: return true; + default: return hasDppForm(opcode); } } @@ -1203,12 +1210,12 @@ void decodeVop1Dpp(std::uint32_t programCounter, std::span ? DecodeRdnaScalarDestination(vdst, programCounter) : DecodeRdnaVectorGpr(vdst); instruction.source0 = DecodeRdnaScalarSource(src0 + 256u, programCounter); - if (form == 250u) { + if (form == 250u && hasDppForm(instruction.op)) { applyDppModifier(instruction.source0, modifier); - } else if (acceptsDpp8(instruction.op)) { + } else if (form != 250u && acceptsDpp8(instruction.op)) { applyDpp8Modifier(instruction.source0, modifier, form == 234u); } else { - throw std::invalid_argument("DPP8 modifier is not supported for opcode"); + throw std::invalid_argument(form == 250u ? "DPP modifier is not supported for opcode" : "DPP8 modifier is not supported for opcode"); } instruction.sourceCount = 1; @@ -1402,14 +1409,14 @@ void decodeVop2Dpp(std::uint32_t programCounter, std::span instruction.destination = DecodeRdnaVectorGpr(vdst); instruction.source1 = DecodeRdnaVectorGpr(vsrc1); instruction.source0 = DecodeRdnaScalarSource(src0 + 256u, programCounter); - if (form == 250u) { + if (form == 250u && hasDppForm(instruction.op)) { applyDppModifier(instruction.source0, modifier); instruction.source1.negate = ((modifier >> 22u) & 0x1u) != 0u; instruction.source1.absolute = ((modifier >> 23u) & 0x1u) != 0u; - } else if (acceptsDpp8(instruction.op)) { + } else if (form != 250u && acceptsDpp8(instruction.op)) { applyDpp8Modifier(instruction.source0, modifier, form == 234u); } else { - throw std::invalid_argument("DPP8 modifier is not supported for opcode"); + throw std::invalid_argument(form == 250u ? "DPP modifier is not supported for opcode" : "DPP8 modifier is not supported for opcode"); } const bool packedFmac = instruction.op == RdnaOpcode::VPkFmacF16; if (packedFmac) { From 69e2d801d1d9c5a47541501102f7613237115fda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sat, 3 Oct 2026 09:00:00 +0200 Subject: [PATCH 049/164] feat(shader): decode the s_endpgm variants, s_round_mode and SMEM probe/discard s_endpgm_saved and s_endpgm_ordered_ps_done end the wave like s_endpgm (checked on an RDNA2 GPU: nothing after them runs), so they decode to SEndpgm and the control flow graph treats them as terminators. s_atc_probe, s_atc_probe_buffer and s_dcache_discard(_x2) have no visible effect and translate to no-ops like the other scalar cache controls. s_round_mode is ignored like s_setreg MODE writes; listed in TechnicalDebt. Co-Authored-By: Claude --- .../tests/execution/ScalarProgramEnd.cpp | 96 +++++++++++++++++++ .../tests/execution/ScalarProgramEndSaved.cpp | 96 +++++++++++++++++++ .../include/RdnaDecoder/RdnaOpcode.hpp | 5 + .../RdnaDecoder/src/RdnaMemoryOpDecoder.cpp | 4 + .../RdnaDecoder/src/RdnaScalarOpDecoder.cpp | 5 +- .../MemoryInstructionDispatch.cpp | 4 + .../Translation/src/ScalarInstructions.cpp | 1 + docs/dev/TechnicalDebt.md | 1 + 8 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEnd.cpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEndSaved.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEnd.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEnd.cpp new file mode 100644 index 000000000..d65402bd7 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEnd.cpp @@ -0,0 +1,96 @@ +#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 +#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, 0x34060086, 0xe0301000, 0x80000401, 0xbf8c3f70, 0xbfa40003, 0xf4980000, 0xfa000000, + 0xf49c0000, 0xfa000000, 0xf4a00000, 0xfa000000, 0xf4a40000, 0xfa000000, 0xbfa40000, 0x4a0a0881, + 0x7e0c02ff, 0x00000bad, 0xbf060000, 0xbf840007, 0xbf840003, 0xe0701000, 0x80010503, 0xbf9e0000, + 0xe0701000, 0x80010603, 0xbf9b0000, 0xe0701004, 0x80010603, 0xbf810000, +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { words[0] = tid * 0x01010101u + 7u; } + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("scalar program end: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + Expect(tid, out[0], in[0] + 1u, "store before the end"); + Expect(tid, out[1], 0xdeadbeefu, "nothing past the end"); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("scalar program end tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEndSaved.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEndSaved.cpp new file mode 100644 index 000000000..1f54ffb13 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEndSaved.cpp @@ -0,0 +1,96 @@ +#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 +#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, 0x34060086, 0xe0301000, 0x80000401, 0xbf8c3f70, 0xbfa40003, 0xf4980000, 0xfa000000, + 0xf49c0000, 0xfa000000, 0xf4a00000, 0xfa000000, 0xf4a40000, 0xfa000000, 0xbfa40000, 0x4a0a0881, + 0x7e0c02ff, 0x00000bad, 0xbf060000, 0xbf840007, 0xbf840003, 0xe0701000, 0x80010503, 0xbf9b0000, + 0xe0701000, 0x80010603, 0xbf9e0000, 0xe0701004, 0x80010603, 0xbf810000, +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { words[0] = tid * 0x01010101u + 7u; } + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("scalar program end saved: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + Expect(tid, out[0], in[0] + 1u, "store before the end"); + Expect(tid, out[1], 0xdeadbeefu, "nothing past the end"); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("scalar program end saved 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 077686add..a60932b61 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -950,11 +950,16 @@ enum class RdnaOpcode : std::uint16_t { SIncperflevel, SDecperflevel, SWaitIdle, + SRoundMode, SGl1Inv, SDcacheInv, SDcacheWb, SMemtime, SMemrealtime, + SAtcProbe, + SAtcProbeBuffer, + SDcacheDiscard, + SDcacheDiscardX2, BufferGl0Inv, BufferGl1Inv, SDenormMode, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp index 2b5b1000c..52a0b30ba 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp @@ -563,6 +563,10 @@ RdnaOpcode cacheControlOpcode(RdnaInstructionFamily family, std::uint32_t opcode case 0x1fu: return RdnaOpcode::SGl1Inv; case 0x20u: return RdnaOpcode::SDcacheInv; case 0x21u: return RdnaOpcode::SDcacheWb; + case 0x26u: return RdnaOpcode::SAtcProbe; + case 0x27u: return RdnaOpcode::SAtcProbeBuffer; + case 0x28u: return RdnaOpcode::SDcacheDiscard; + case 0x29u: return RdnaOpcode::SDcacheDiscardX2; default: return RdnaOpcode::Invalid; } } diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaScalarOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaScalarOpDecoder.cpp index 04f82bdcc..09ef291c1 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaScalarOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaScalarOpDecoder.cpp @@ -214,10 +214,13 @@ RdnaOpcode decodeSoppOpcode(std::uint32_t opcode) { case 0x18u: case 0x19u: case 0x1au: return RdnaOpcode::SCbranchCdbg; + case 0x1bu: + case 0x1eu: return RdnaOpcode::SEndpgm; case 0x20u: return RdnaOpcode::SInstPrefetch; case 0x21u: return RdnaOpcode::SClause; case 0x22u: return RdnaOpcode::SWaitIdle; case 0x23u: return RdnaOpcode::SWaitcntDepctr; + case 0x24u: return RdnaOpcode::SRoundMode; case 0x25u: return RdnaOpcode::SDenormMode; case 0x28u: return RdnaOpcode::STtracedata; default: throw std::invalid_argument("unsupported SOPP opcode " + std::to_string(opcode)); @@ -237,7 +240,7 @@ bool isSoppWaitOpcode(RdnaOpcode opcode) { opcode == RdnaOpcode::STrap || opcode == RdnaOpcode::STtracedata || opcode == RdnaOpcode::SInstPrefetch || opcode == RdnaOpcode::SClause || opcode == RdnaOpcode::SCbranchCdbg || opcode == RdnaOpcode::SIcacheInv || opcode == RdnaOpcode::SIncperflevel || opcode == RdnaOpcode::SDecperflevel || opcode == RdnaOpcode::SWaitIdle || - opcode == RdnaOpcode::SDenormMode; + opcode == RdnaOpcode::SRoundMode || opcode == RdnaOpcode::SDenormMode; } std::uint32_t scalarDestinationDwordCount(RdnaOpcode opcode) { diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp index e7f6a8c6e..947df3fa4 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp @@ -14,6 +14,10 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) { case RdnaOpcode::SGl1Inv: case RdnaOpcode::SDcacheInv: case RdnaOpcode::SDcacheWb: + case RdnaOpcode::SAtcProbe: + case RdnaOpcode::SAtcProbeBuffer: + case RdnaOpcode::SDcacheDiscard: + case RdnaOpcode::SDcacheDiscardX2: case RdnaOpcode::BufferGl0Inv: case RdnaOpcode::BufferGl1Inv: emitControlNop(); diff --git a/core/shader/recompiler/Translation/src/ScalarInstructions.cpp b/core/shader/recompiler/Translation/src/ScalarInstructions.cpp index 757379f4f..1cfa526ee 100644 --- a/core/shader/recompiler/Translation/src/ScalarInstructions.cpp +++ b/core/shader/recompiler/Translation/src/ScalarInstructions.cpp @@ -405,6 +405,7 @@ bool TranslationContext::emitScalar(const RdnaInstruction& inst) { case RdnaOpcode::SIcacheInv: case RdnaOpcode::SIncperflevel: case RdnaOpcode::SDecperflevel: + case RdnaOpcode::SRoundMode: emitControlNop(); return true; case RdnaOpcode::SDenormMode: diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index da31aebc2..6bc1649a1 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -19,6 +19,7 @@ Throughout the project, every function at every stage either **does exactly what - [libSceNpCommerce](../../core/libs/prx/libSceNpCommerce/Export.cpp) - the PS Store icon show/hide calls do nothing - [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) validates the priority and CPU affinity that the open param requests for the VideoOut service thread but does not apply them: the port's present and vblank threads are host threads, and guest priorities and affinities do not reach host scheduling - The shader recompiler [skips baryctric coordinates](../../core/shader/recompiler/Recompiler.cpp) (is not even passed to SpirvTargetOptions at row 212). +- The shader recompiler [ignores `s_round_mode`](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp): float operations keep rounding to nearest even after it selects another rounding mode. - [libSceAvPlayer](../../core/libs/prx/libSceAvPlayer/Export.cpp): `sceAvPlayerSetLogCallback` accepts a callback that is never called, as the player produces no log messages, and `sceAvPlayerSetAvailableBandwidth` has no effect, as it governs HLS sources, which `sceAvPlayerAddSource` does not implement. - [sceAvPlayerSetTrickSpeed](../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) with a negative speed runs the clock backwards but delivers no frames; when a forward speed is set again, playback resumes from the rewound time. From 36c2fa4ffea16fb1704b749e20f508173ba9558e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 08:30:30 +0200 Subject: [PATCH 050/164] fix(shader): translate s_round_mode only for round to nearest even Any other mode throws instead of being a silent no-op. Co-Authored-By: Claude --- .../tests/execution/ScalarProgramEnd.cpp | 40 ++++++++++++++----- .../tests/execution/ScalarProgramEndSaved.cpp | 2 +- .../Translation/src/ScalarInstructions.cpp | 5 +++ docs/dev/TechnicalDebt.md | 1 - 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEnd.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEnd.cpp index d65402bd7..023bd4896 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEnd.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEnd.cpp @@ -24,12 +24,14 @@ alignas(256) std::array Input{}; alignas(256) std::array Output{}; alignas(256) constexpr std::array Code{ - 0x34020084, 0x34060086, 0xe0301000, 0x80000401, 0xbf8c3f70, 0xbfa40003, 0xf4980000, 0xfa000000, + 0x34020084, 0x34060086, 0xe0301000, 0x80000401, 0xbf8c3f70, 0xbf800000, 0xf4980000, 0xfa000000, 0xf49c0000, 0xfa000000, 0xf4a00000, 0xfa000000, 0xf4a40000, 0xfa000000, 0xbfa40000, 0x4a0a0881, 0x7e0c02ff, 0x00000bad, 0xbf060000, 0xbf840007, 0xbf840003, 0xe0701000, 0x80010503, 0xbf9e0000, 0xe0701000, 0x80010603, 0xbf9b0000, 0xe0701004, 0x80010603, 0xbf810000, }; +alignas(256) constexpr std::array RoundTowardZeroCode{0xbfa40003, 0xbf810000}; + void Fill(std::uint32_t tid, std::uint32_t* words) { words[0] = tid * 0x01010101u + 7u; } std::array BufferDescriptor(const void* data, std::uint32_t bytes) { @@ -47,6 +49,31 @@ void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, con Require(actual == expected, std::string("scalar program end: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); } +ShaderRecompiler::RecompileRequest Request(AgcDriver::VulkanDevice& device, std::span code, std::span userData, std::span memory) { + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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 request; +} + +void ExpectRefused(AgcDriver::VulkanDevice& device) { + const std::span code(RoundTowardZeroCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const std::vector userData(8, 0u); + bool refused = false; + try { + ShaderRecompiler::Recompile(Request(device, code, userData, memory)); + } catch (const std::exception&) { + refused = true; + } + Require(refused, "scalar program end: s_round_mode 3 was translated"); +} + void Run(AgcDriver::VulkanDevice& device) { for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); Output.fill(0xdeadbeefu); @@ -57,15 +84,7 @@ void Run(AgcDriver::VulkanDevice& device) { std::copy(output.begin(), output.end(), userData.begin() + 4); const std::span code(Code); const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; - const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; - const auto result = ShaderRecompiler::Recompile(request); + const auto result = ShaderRecompiler::Recompile(Request(device, code, userData, memory)); device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); device.WaitIdle(); } @@ -87,6 +106,7 @@ int main() { if (!device) return VulkanTestSkipped; Run(*device); Check(); + ExpectRefused(*device); std::puts("scalar program end tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEndSaved.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEndSaved.cpp index 1f54ffb13..100b4552e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEndSaved.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEndSaved.cpp @@ -24,7 +24,7 @@ alignas(256) std::array Input{}; alignas(256) std::array Output{}; alignas(256) constexpr std::array Code{ - 0x34020084, 0x34060086, 0xe0301000, 0x80000401, 0xbf8c3f70, 0xbfa40003, 0xf4980000, 0xfa000000, + 0x34020084, 0x34060086, 0xe0301000, 0x80000401, 0xbf8c3f70, 0xbf800000, 0xf4980000, 0xfa000000, 0xf49c0000, 0xfa000000, 0xf4a00000, 0xfa000000, 0xf4a40000, 0xfa000000, 0xbfa40000, 0x4a0a0881, 0x7e0c02ff, 0x00000bad, 0xbf060000, 0xbf840007, 0xbf840003, 0xe0701000, 0x80010503, 0xbf9b0000, 0xe0701000, 0x80010603, 0xbf9e0000, 0xe0701004, 0x80010603, 0xbf810000, diff --git a/core/shader/recompiler/Translation/src/ScalarInstructions.cpp b/core/shader/recompiler/Translation/src/ScalarInstructions.cpp index 1cfa526ee..468ea0db6 100644 --- a/core/shader/recompiler/Translation/src/ScalarInstructions.cpp +++ b/core/shader/recompiler/Translation/src/ScalarInstructions.cpp @@ -405,7 +405,12 @@ bool TranslationContext::emitScalar(const RdnaInstruction& inst) { case RdnaOpcode::SIcacheInv: case RdnaOpcode::SIncperflevel: case RdnaOpcode::SDecperflevel: + emitControlNop(); + return true; case RdnaOpcode::SRoundMode: + if (inst.source0.value != 0u) { + throw std::runtime_error("s_round_mode " + std::to_string(inst.source0.value) + " at pc " + std::to_string(inst.programCounter) + " selects a rounding mode other than round to nearest even"); + } emitControlNop(); return true; case RdnaOpcode::SDenormMode: diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 6bc1649a1..da31aebc2 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -19,7 +19,6 @@ Throughout the project, every function at every stage either **does exactly what - [libSceNpCommerce](../../core/libs/prx/libSceNpCommerce/Export.cpp) - the PS Store icon show/hide calls do nothing - [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) validates the priority and CPU affinity that the open param requests for the VideoOut service thread but does not apply them: the port's present and vblank threads are host threads, and guest priorities and affinities do not reach host scheduling - The shader recompiler [skips baryctric coordinates](../../core/shader/recompiler/Recompiler.cpp) (is not even passed to SpirvTargetOptions at row 212). -- The shader recompiler [ignores `s_round_mode`](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp): float operations keep rounding to nearest even after it selects another rounding mode. - [libSceAvPlayer](../../core/libs/prx/libSceAvPlayer/Export.cpp): `sceAvPlayerSetLogCallback` accepts a callback that is never called, as the player produces no log messages, and `sceAvPlayerSetAvailableBandwidth` has no effect, as it governs HLS sources, which `sceAvPlayerAddSource` does not implement. - [sceAvPlayerSetTrickSpeed](../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) with a negative speed runs the clock backwards but delivers no frames; when a forward speed is set again, playback resumes from the rewound time. From 4f901b9a77a9787a958f0ea9eb25d7f270a2b550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sat, 3 Oct 2026 10:07:02 +0200 Subject: [PATCH 051/164] feat(shader): implement v_rcp/rsq/sqrt_f64 and v_trig_preop_f64 Decode and translate the remaining unary f64 transcendentals and v_trig_preop_f64. On RDNA2 hardware v_rcp_f64, v_rsq_f64 and v_sqrt_f64 are approximations (relative error up to ~2^-24.3 over 8209 inputs; sqrt(1.0) is not even exact), so they are emitted as exact double operations and the test compares against the hardware value with a 2^-23 tolerance. Special cases are exact and selected on bits: NaN inputs are quieted (RADV keeps signaling NaN on FDiv), negative inputs give the default NaN 0xfff8000000000000, rsq(-0) = -inf, sqrt(-0) = -0. v_trig_preop_f64 matches the hardware bit for bit on 24627 results plus a sweep of the table tail: the 2/PI table is 1184 bits (bits below b17 read as zero), shift = 53 * S1[4:0] + max(exp - 1077, 0), +128 scale for exp >= 1968, omod ignored. The 53-bit window is read from a private u32 table and scaled in two exact steps so only the final multiply rounds. Stacked on the f64 infrastructure of #446. Co-Authored-By: Claude --- .../tests/execution/F64Transcendental.cpp | 190 ++++++++++++++++++ .../IntermediateRepresentation/IrOpcode.hpp | 4 + .../src/IrOpcode.cpp | 4 + .../include/RdnaDecoder/RdnaOpcode.hpp | 4 + .../recompiler/RdnaDecoder/src/RdnaOpcode.cpp | 4 + .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 20 +- .../SpirvBackend/SpirvEmitterInstructions.hpp | 4 + .../src/SpirvAlu/SpirvAluEmitterFloat64.cpp | 79 ++++++++ .../SpirvBackend/src/SpirvFlowEmitter.cpp | 4 + .../Translation/src/VectorInstructions.cpp | 8 + 10 files changed, 319 insertions(+), 2 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/F64Transcendental.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F64Transcendental.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F64Transcendental.cpp new file mode 100644 index 000000000..f4e6cc55e --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F64Transcendental.cpp @@ -0,0 +1,190 @@ +#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 +#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, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e145f04, 0x7e186304, 0x7e1c6904, + 0xd5740010, 0x02020d04, 0xd5740012, 0x02010104, 0xd5740114, 0x2a020d04, 0xd5af8016, 0x02010104, + 0xd5b40118, 0x22010104, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, + 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000001u, 0x7ff00000u, 0xcad921b4u, 0x00000000u}, + {0x00000000u, 0x7ff40000u, 0x0000001fu, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000025u, 0x80000000u}, + {0xe0000000u, 0x47efffffu, 0x0000001eu, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x0000001bu, 0x00000000u}, + {0x00000000u, 0xbff00000u, 0x0000001au, 0x00000000u}, + {0x00000000u, 0x7ff00000u, 0x01c32149u, 0x00000000u}, + {0x00000000u, 0xfff00000u, 0x00000012u, 0x00000000u}, + {0x00000001u, 0xfff80000u, 0x00000012u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3ceb3ffdu, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x8b529b4au, 0x3ff00000u}, + {0x00000000u, 0x00000000u, 0x00000008u, 0xbff00000u}, + {0x00000000u, 0x00000000u, 0x5eb561a4u, 0x3fe00000u}, + {0x00000000u, 0x00000000u, 0x00000026u, 0xbfe00000u}, + {0x00000000u, 0x00000000u, 0x795b929eu, 0x3ff80000u}, + {0x00000000u, 0x00000000u, 0x00000028u, 0x40040000u}, + {0x00000000u, 0x00000000u, 0x94b2b8fdu, 0xc0040000u}, + {0x00000000u, 0x00000000u, 0x00000004u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x9b08923du, 0x800fffffu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00100000u}, + {0x00000000u, 0x00000000u, 0xe8a8529fu, 0x7fefffffu}, + {0x00000000u, 0x00000000u, 0x0000001eu, 0xffefffffu}, + {0x00000000u, 0x00000000u, 0x42650644u, 0x7ff00000u}, + {0x00000000u, 0x00000000u, 0x00000023u, 0xfff00000u}, + {0x00000000u, 0x00000000u, 0x3bfd1d33u, 0x7ff80000u}, + {0x00000000u, 0x00000000u, 0x0000000cu, 0xfff80000u}, + {0x00000000u, 0x00000000u, 0xfee29476u, 0x7ff00000u}, + {0x00000000u, 0x00000000u, 0x0000001eu, 0x7ff40000u}, + {0x00000000u, 0x00000000u, 0x8a7d43b5u, 0x41e00000u}, + {0x00000000u, 0x00000000u, 0x00000023u, 0xc1e00000u}, + {0x00000000u, 0x00000000u, 0x79f248b0u, 0xc1e00000u}, + {0x00000000u, 0x00000000u, 0x00000019u, 0x41dfffffu} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000000u, 0x00000000u, 0xf5f2f8bdu, 0x0b43dd63u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0xfff80000u}, + {0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x00000000u, 0xf5f2f8bdu, 0x0b43dd63u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffc0000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x2126e970u, 0x2f42371du, 0x6dc9c882u, 0x3fe45f30u, 0x2126e970u, 0x2f42371du, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x1af80000u, 0x37f00000u, 0x039e0000u, 0x3bf00000u, 0xed080000u, 0x43efffffu, 0x00000000u, 0x00000000u, 0xfa9a6ee0u, 0x3b43abe8u, 0x00000000u, 0x00000000u, 0x1af80000u, 0x37f00000u, 0x00000000u, 0xfff80000u}, + {0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0xbff00000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff80000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0xf5f2f8bdu, 0x0b43dd63u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff80000u}, + {0x00000000u, 0x80000000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0xf5f2f8bdu, 0x0b43dd63u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff80000u}, + {0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000000u, 0x00000000u, 0xf5f2f8bdu, 0x0b43dd63u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0xfff80000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x2fbf209cu, 0x1ec8135au, 0x6dc9c882u, 0x3fe45f30u, 0x2fbf209cu, 0x1ec8135au, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x74411af8u, 0x2544d39fu, 0x6dc9c882u, 0x3fe45f30u, 0x74411af8u, 0x2544d39fu, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0xde2b0db8u, 0x328d5ef5u, 0x6dc9c882u, 0x3fe45f30u, 0xde2b0db8u, 0x328d5ef5u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0xa8274600u, 0x2b9924bbu, 0x6dc9c882u, 0x3fe45f30u, 0xa8274600u, 0x2b9924bbu, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x74411af8u, 0x2544d39fu, 0x6dc9c882u, 0x3fe45f30u, 0x74411af8u, 0x2544d39fu, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0xde2b0db8u, 0x328d5ef5u, 0x6dc9c882u, 0x3fe45f30u, 0xde2b0db8u, 0x328d5ef5u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x6dc9c882u, 0x3fe45f30u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0xde2b0db8u, 0x328d5ef5u, 0x6dc9c882u, 0x3fe45f30u, 0xde2b0db8u, 0x328d5ef5u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x041fe516u, 0x35f3c439u, 0x6dc9c882u, 0x3fe45f30u, 0x041fe516u, 0x35f3c439u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0xd0739f78u, 0x00ff17b3u, 0x6dc9c882u, 0x3fe45f30u, 0xd0739f78u, 0x00ff17b3u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x7e2ef7e4u, 0x18127211u, 0x6dc9c882u, 0x3fe45f30u, 0x7e2ef7e4u, 0x18127211u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x041fe516u, 0x35f3c439u, 0x6dc9c882u, 0x3fe45f30u, 0x041fe516u, 0x35f3c439u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0xf2c26dd2u, 0x0adfb3c9u, 0x6dc9c882u, 0x3fe45f30u, 0xf2c26dd2u, 0x0adfb3c9u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u} +}; +constexpr const char* Names[8] = {"rcp", "rsq", "sqrt", "trig preop", "trig preop inline", "trig preop abs neg omod", "rcp clamp", "sqrt abs neg"}; +constexpr bool Approximate[8] = {true, true, true, false, false, false, true, true}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +double F64(const std::uint32_t* words) { + return std::bit_cast(static_cast(words[1]) << 32u | words[0]); +} + +bool Normal(const std::uint32_t* words) { + const auto exponent = (words[1] >> 20u) & 0x7ffu; + return exponent != 0u && exponent != 0x7ffu; +} + +void ExpectNear(std::uint32_t tid, const std::uint32_t* actual, const std::uint32_t* expected, const char* name) { + const auto error = std::abs(F64(actual) - F64(expected)) / std::abs(F64(expected)); + Require(error <= 0x1p-23, std::string("f64 transcendental: lane ") + std::to_string(tid) + " " + name + " is " + std::to_string(F64(actual)) + ", expected " + std::to_string(F64(expected))); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("f64 transcendental: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 8; ++i) { + const std::uint32_t* expected = &Expected[tid][2 * i]; + if (Approximate[i] && Normal(expected)) { + ExpectNear(tid, out + 2 * i, expected, Names[i]); + continue; + } + Expect(tid, out[2 * i], expected[0], Names[i]); + Expect(tid, out[2 * i + 1], expected[1], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("f64 transcendental tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp index 3ba9c509f..d5057d62f 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp @@ -259,6 +259,10 @@ enum class IrOpcode : std::uint16_t { FPFract64, FPFrexpMant64, FPFrexpExp64, + FPRcp64, + FPRsq64, + FPSqrt64, + FPTrigPreop64, ConvertF32F64, ConvertF64F32, ConvertF64S32, diff --git a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp index b0b7461d7..6ff239e77 100644 --- a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp +++ b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp @@ -300,6 +300,10 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("FPFract64", U64, U64), makeMeta("FPFrexpMant64", U64, U64), makeMeta("FPFrexpExp64", U32, U64), + makeMeta("FPRcp64", U64, U64), + makeMeta("FPRsq64", U64, U64), + makeMeta("FPSqrt64", U64, U64), + makeMeta("FPTrigPreop64", U64, U64, U32), makeMeta("ConvertF32F64", F32, U64), makeMeta("ConvertF64F32", U64, F32), makeMeta("ConvertF64S32", U64, U32), diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp index 077686add..90b1cd58c 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -412,6 +412,10 @@ enum class RdnaOpcode : std::uint16_t { VDivScaleF32, VDivFmasF32, VDivFixupF32, + VRcpF64, + VRsqF64, + VSqrtF64, + VTrigPreopF64, VCvtPknormI16F32, VCvtPknormU16F32, VCvtPkU16U32, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp index 25da22e12..b2c35b4fd 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp @@ -363,6 +363,10 @@ bool IsVectorAluOpcode(RdnaOpcode opcode) { case RdnaOpcode::VDivScaleF32: case RdnaOpcode::VDivFmasF32: case RdnaOpcode::VDivFixupF32: + case RdnaOpcode::VRcpF64: + case RdnaOpcode::VRsqF64: + case RdnaOpcode::VSqrtF64: + case RdnaOpcode::VTrigPreopF64: case RdnaOpcode::VCvtPknormI16F32: case RdnaOpcode::VCvtPknormU16F32: case RdnaOpcode::VCvtPkU16U32: diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index e70717129..066bfffff 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -141,6 +141,9 @@ constexpr VectorOpcodeInfo vop1Opcodes[] = { {0x3cu, RdnaOpcode::VFrexpExpI32F64}, {0x3du, RdnaOpcode::VFrexpMantF64}, {0x3eu, RdnaOpcode::VFractF64}, + {0x2fu, RdnaOpcode::VRcpF64}, + {0x31u, RdnaOpcode::VRsqF64}, + {0x34u, RdnaOpcode::VSqrtF64}, {0x25u, RdnaOpcode::VExpF32}, {0x27u, RdnaOpcode::VLogF32}, {0x2bu, RdnaOpcode::VRcpIflagF32}, @@ -222,6 +225,9 @@ constexpr VectorOpcodeInfo vop3EncodedVop1Opcodes[] = { {0x3cu, RdnaOpcode::VFrexpExpI32F64}, {0x3du, RdnaOpcode::VFrexpMantF64}, {0x3eu, RdnaOpcode::VFractF64}, + {0x2fu, RdnaOpcode::VRcpF64}, + {0x31u, RdnaOpcode::VRsqF64}, + {0x34u, RdnaOpcode::VSqrtF64}, {0x25u, RdnaOpcode::VExpF32}, {0x27u, RdnaOpcode::VLogF32}, {0x2bu, RdnaOpcode::VRcpIflagF32}, @@ -552,6 +558,7 @@ constexpr VectorOpcodeInfo vop3Opcodes[] = { {0x15fu, RdnaOpcode::VDivFixupF32}, {0x16du, RdnaOpcode::VDivScaleF32}, {0x16fu, RdnaOpcode::VDivFmasF32}, + {0x174u, RdnaOpcode::VTrigPreopF64}, {0x363u, RdnaOpcode::VBfmB32}, {0x364u, RdnaOpcode::VBcntU32B32}, {0x365u, RdnaOpcode::VMbcntLoU32B32}, @@ -759,6 +766,9 @@ bool isVop1FloatSourceOpcode(RdnaOpcode opcode) { case RdnaOpcode::VFrexpExpI32F64: case RdnaOpcode::VFrexpMantF64: case RdnaOpcode::VFractF64: + case RdnaOpcode::VRcpF64: + case RdnaOpcode::VRsqF64: + case RdnaOpcode::VSqrtF64: case RdnaOpcode::VExpF32: case RdnaOpcode::VLogF32: case RdnaOpcode::VRsqF32: @@ -811,6 +821,9 @@ bool isVop1FloatResultOpcode(RdnaOpcode opcode) { case RdnaOpcode::VFloorF64: case RdnaOpcode::VFrexpMantF64: case RdnaOpcode::VFractF64: + case RdnaOpcode::VRcpF64: + case RdnaOpcode::VRsqF64: + case RdnaOpcode::VSqrtF64: case RdnaOpcode::VFrexpMantF32: case RdnaOpcode::VExpF32: case RdnaOpcode::VLogF32: @@ -1598,6 +1611,7 @@ std::uint32_t nativeVop3SourceCount(RdnaOpcode opcode) { case RdnaOpcode::VMinF64: case RdnaOpcode::VMaxF64: case RdnaOpcode::VLdexpF64: + case RdnaOpcode::VTrigPreopF64: case RdnaOpcode::VBfmB32: case RdnaOpcode::VBcntU32B32: case RdnaOpcode::VCvtPknormI16F32: @@ -1784,7 +1798,8 @@ bool supportsNativeVop3SourceModifiers(RdnaOpcode opcode) { case RdnaOpcode::VMulF64: case RdnaOpcode::VMinF64: case RdnaOpcode::VMaxF64: - case RdnaOpcode::VLdexpF64: return true; + case RdnaOpcode::VLdexpF64: + case RdnaOpcode::VTrigPreopF64: return true; default: return false; } } @@ -1812,6 +1827,7 @@ bool supportsNativeVop3ResultModifiers(RdnaOpcode opcode) { case RdnaOpcode::VMinF64: case RdnaOpcode::VMaxF64: case RdnaOpcode::VLdexpF64: + case RdnaOpcode::VTrigPreopF64: case RdnaOpcode::VMin3F32: case RdnaOpcode::VMax3F32: case RdnaOpcode::VMed3F32: return true; @@ -1861,7 +1877,7 @@ void checkNativeVop3Modifiers(RdnaOpcode opcode, bool permlane, bool carryInOut, } return; } - if (opcode == RdnaOpcode::VLdexpF32 || opcode == RdnaOpcode::VLdexpF64) { + if (opcode == RdnaOpcode::VLdexpF32 || opcode == RdnaOpcode::VLdexpF64 || opcode == RdnaOpcode::VTrigPreopF64) { if ((abs & ~1u) != 0u || opSel != 0u || (neg & ~1u) != 0u) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); } diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp index beb5b9808..531c27dd6 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp @@ -193,6 +193,10 @@ std::uint32_t EmitFPTrunc64(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFPFract64(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFPFrexpMant64(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFPFrexpExp64(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitFPRcp64(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitFPRsq64(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitFPSqrt64(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitFPTrigPreop64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1); std::uint32_t EmitConvertF32F64(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitConvertF64F32(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitConvertF64S32(SpirvEmitterState& state, std::uint32_t arg0); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp index d7c7dfbf3..1374e12f3 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp @@ -1,8 +1,10 @@ #include "SpirvBackend/SpirvEmitterInstructions.hpp" #include #include +#include #include #include +#include namespace ShaderRecompiler { namespace { @@ -352,6 +354,83 @@ std::uint32_t EmitFPFrexpExp64(SpirvEmitterState& state, std::uint32_t arg0) { return Select(state, TypeU32(state), special, ConstantU32(state, 0u), Unary(state, spv::OpBitcast, TypeU32(state), exponent)); } +std::uint32_t EmitFPRcp64(SpirvEmitterState& state, std::uint32_t arg0) { + const auto cls = Classify(state, arg0); + const auto sign = Binary(state, spv::OpBitwiseAnd, TypeU32(state), cls.bits.high, ConstantU32(state, 0x80000000u)); + const auto quotient = Split(state, FromF64(state, Exact(state, spv::OpFDiv, ConstantF64(state, 0x3ff0000000000000ull), ToF64(state, arg0)))); + auto result = SelectBits(state, cls.zero, {ConstantU32(state, 0u), Binary(state, spv::OpBitwiseOr, TypeU32(state), sign, ConstantU32(state, 0x7ff00000u))}, quotient); + result = SelectBits(state, cls.infinite, {ConstantU32(state, 0u), sign}, result); + return Join(state, SelectBits(state, cls.nan, Quiet(state, cls.bits), result)); +} + +std::uint32_t EmitFPRsq64(SpirvEmitterState& state, std::uint32_t arg0) { + const auto cls = Classify(state, arg0); + const auto sign = Binary(state, spv::OpBitwiseAnd, TypeU32(state), cls.bits.high, ConstantU32(state, 0x80000000u)); + const auto root = Glsl(state, TypeF64(state), GLSLstd450Sqrt, ToF64(state, arg0)); + auto result = Split(state, FromF64(state, Exact(state, spv::OpFDiv, ConstantF64(state, 0x3ff0000000000000ull), root))); + result = SelectBits(state, cls.negative, {ConstantU32(state, 0u), ConstantU32(state, 0xfff80000u)}, result); + result = SelectBits(state, cls.zero, {ConstantU32(state, 0u), Binary(state, spv::OpBitwiseOr, TypeU32(state), sign, ConstantU32(state, 0x7ff00000u))}, result); + result = SelectBits(state, cls.infinite, SelectBits(state, cls.negative, result, {ConstantU32(state, 0u), ConstantU32(state, 0u)}), result); + return Join(state, SelectBits(state, cls.nan, Quiet(state, cls.bits), result)); +} + +std::uint32_t EmitFPSqrt64(SpirvEmitterState& state, std::uint32_t arg0) { + const auto cls = Classify(state, arg0); + auto result = Split(state, FromF64(state, Glsl(state, TypeF64(state), GLSLstd450Sqrt, ToF64(state, arg0)))); + result = SelectBits(state, cls.negative, {ConstantU32(state, 0u), ConstantU32(state, 0xfff80000u)}, result); + const auto passthrough = Binary(state, spv::OpLogicalOr, TypeBool(state), cls.zero, Binary(state, spv::OpLogicalAnd, TypeBool(state), cls.infinite, Unary(state, spv::OpLogicalNot, TypeBool(state), cls.negative))); + result = SelectBits(state, passthrough, cls.bits, result); + return Join(state, SelectBits(state, cls.nan, Quiet(state, cls.bits), result)); +} + +std::uint32_t EmitFPTrigPreop64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1) { + static constexpr std::array TwoOverPi{ + 0xa2f9836eu, 0x4e441529u, 0xfc2757d1u, 0xf534ddc0u, 0xdb629599u, 0x3c439041u, 0xfe5163abu, 0xdebbc561u, + 0xb7246e3au, 0x424dd2e0u, 0x06492eeau, 0x09d1921cu, 0xfe1deb1cu, 0xb129a73eu, 0xe88235f5u, 0x2ebb4484u, + 0xe99c7026u, 0xb45f7e41u, 0x3991d639u, 0x835339f4u, 0x9c845f8bu, 0xbdf9283bu, 0x1ff897ffu, 0xde05980fu, + 0xef2f118bu, 0x5a0a6d1fu, 0x6d367ecfu, 0x27cb09b7u, 0x4f463f66u, 0x9e5fea2du, 0x7527bac7u, 0xebe5f17bu, + 0x3d0739f7u, 0x8a5292eau, 0x6bfb5fb1u, 0x1f8d5d08u, 0x56033046u, 0x00000000u, 0x00000000u, 0x00000000u, + }; + std::array words{}; + for (std::size_t index = 0; index < words.size(); ++index) words[index] = ConstantU32(state, TwoOverPi[index]); + const auto arrayType = state.module.Type(spv::OpTypeArray, TypeU32(state), ConstantU32(state, static_cast(words.size()))); + const auto table = state.module.DefineGlobalVariable(TypePointer(state, spv::StorageClassPrivate, arrayType), spv::StorageClassPrivate); + state.module.AddFunction(spv::OpStore, table, state.module.Constant(spv::OpConstantComposite, arrayType, std::span(words))); + const auto load = [&](std::uint32_t index) { + const auto pointer = state.module.AllocateId(); + state.module.AddFunction(spv::OpAccessChain, TypePointer(state, spv::StorageClassPrivate, TypeU32(state)), pointer, table, index); + const auto value = state.module.AllocateId(); + state.module.AddFunction(spv::OpLoad, TypeU32(state), value, pointer); + return value; + }; + + const auto bits = Split(state, arg0); + const auto exponent = Binary(state, spv::OpBitwiseAnd, TypeU32(state), Binary(state, spv::OpShiftRightLogical, TypeU32(state), bits.high, ConstantU32(state, 20u)), ConstantU32(state, 0x7ffu)); + const auto extra = Binary(state, spv::OpISub, TypeU32(state), Glsl(state, TypeU32(state), GLSLstd450UMax, exponent, ConstantU32(state, 1077u)), ConstantU32(state, 1077u)); + const auto segment = Binary(state, spv::OpIMul, TypeU32(state), Binary(state, spv::OpBitwiseAnd, TypeU32(state), arg1, ConstantU32(state, 31u)), ConstantU32(state, 53u)); + const auto shift = Glsl(state, TypeU32(state), GLSLstd450UMin, Binary(state, spv::OpIAdd, TypeU32(state), segment, extra), ConstantU32(state, 1201u)); + const auto word = Binary(state, spv::OpShiftRightLogical, TypeU32(state), shift, ConstantU32(state, 5u)); + const auto offset = Binary(state, spv::OpBitwiseAnd, TypeU32(state), shift, ConstantU32(state, 31u)); + const auto aligned = Binary(state, spv::OpIEqual, TypeBool(state), offset, ConstantU32(state, 0u)); + const auto rest = Binary(state, spv::OpISub, TypeU32(state), ConstantU32(state, 32u), offset); + const auto funnel = [&](std::uint32_t upper, std::uint32_t lower) { + const auto merged = Binary(state, spv::OpBitwiseOr, TypeU32(state), Binary(state, spv::OpShiftLeftLogical, TypeU32(state), upper, offset), Binary(state, spv::OpShiftRightLogical, TypeU32(state), lower, rest)); + return Select(state, TypeU32(state), aligned, upper, merged); + }; + const auto first = load(word); + const auto second = load(Binary(state, spv::OpIAdd, TypeU32(state), word, ConstantU32(state, 1u))); + const auto third = load(Binary(state, spv::OpIAdd, TypeU32(state), word, ConstantU32(state, 2u))); + const auto high = Unary(state, spv::OpConvertUToF, TypeF64(state), funnel(first, second)); + const auto low = Unary(state, spv::OpConvertUToF, TypeF64(state), Binary(state, spv::OpShiftRightLogical, TypeU32(state), funnel(second, third), ConstantU32(state, 11u))); + const auto mantissa = Exact(state, spv::OpFAdd, Exact(state, spv::OpFMul, high, ConstantF64(state, 0x4140000000000000ull)), low); + + const auto large = Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), exponent, ConstantU32(state, 1968u)); + const auto bias = Select(state, TypeU32(state), large, ConstantU32(state, 1023u + 600u - 53u + 128u), ConstantU32(state, 1023u + 600u - 53u)); + const auto scaleExponent = Binary(state, spv::OpISub, TypeU32(state), bias, shift); + const auto scale = ToF64(state, Join(state, {ConstantU32(state, 0u), Binary(state, spv::OpShiftLeftLogical, TypeU32(state), scaleExponent, ConstantU32(state, 20u))})); + return FromF64(state, Exact(state, spv::OpFMul, Exact(state, spv::OpFMul, mantissa, scale), ConstantF64(state, 0x1a70000000000000ull))); +} + std::uint32_t EmitConvertF32F64(SpirvEmitterState& state, std::uint32_t arg0) { const auto converted = Unary(state, spv::OpBitcast, TypeU32(state), Unary(state, spv::OpFConvert, TypeF32(state), ToF64(state, arg0))); const auto cls = Classify(state, arg0); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp index 873ea24a1..415a2e2f0 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp @@ -433,6 +433,10 @@ void EmitDirectInstruction(SpirvValueEmitContext& ctx, const IrValue& inst) { case IrOpcode::FPFract64: return Invoke(EmitFPFract64, ctx, inst); case IrOpcode::FPFrexpMant64: return Invoke(EmitFPFrexpMant64, ctx, inst); case IrOpcode::FPFrexpExp64: return Invoke(EmitFPFrexpExp64, ctx, inst); + case IrOpcode::FPRcp64: return Invoke(EmitFPRcp64, ctx, inst); + case IrOpcode::FPRsq64: return Invoke(EmitFPRsq64, ctx, inst); + case IrOpcode::FPSqrt64: return Invoke(EmitFPSqrt64, ctx, inst); + case IrOpcode::FPTrigPreop64: return Invoke(EmitFPTrigPreop64, ctx, inst); case IrOpcode::ConvertF32F64: return Invoke(EmitConvertF32F64, ctx, inst); case IrOpcode::ConvertF64F32: return Invoke(EmitConvertF64F32, ctx, inst); case IrOpcode::ConvertF64S32: return Invoke(EmitConvertF64S32, ctx, inst); diff --git a/core/shader/recompiler/Translation/src/VectorInstructions.cpp b/core/shader/recompiler/Translation/src/VectorInstructions.cpp index 75ba73621..19ac73ab3 100644 --- a/core/shader/recompiler/Translation/src/VectorInstructions.cpp +++ b/core/shader/recompiler/Translation/src/VectorInstructions.cpp @@ -871,6 +871,14 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { return float64Operation(inst, IrOpcode::FPFloor64); case RdnaOpcode::VFractF64: return float64Operation(inst, IrOpcode::FPFract64); + case RdnaOpcode::VRcpF64: + return float64Operation(inst, IrOpcode::FPRcp64); + case RdnaOpcode::VRsqF64: + return float64Operation(inst, IrOpcode::FPRsq64); + case RdnaOpcode::VSqrtF64: + return float64Operation(inst, IrOpcode::FPSqrt64); + case RdnaOpcode::VTrigPreopF64: + return float64Operation(inst, IrOpcode::FPTrigPreop64); case RdnaOpcode::VFrexpMantF64: return float64Operation(inst, IrOpcode::FPFrexpMant64); case RdnaOpcode::VFrexpExpI32F64: From 063437cdcdad9cdc8c52c1d6590c063c342bce5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sat, 3 Oct 2026 19:39:11 +0200 Subject: [PATCH 052/164] feat(shader): implement v_div_scale_f64, v_div_fmas_f64 and v_div_fixup_f64 These make up the precise f64 division macro compilers emit for a / b. Like the f32 ops, the rules were measured on an RDNA2 GPU (41152 rows, model matches all of them): - div_scale decides from the exponent fields only: 2^128 or 2^-128 scaling and VCC for differences >= 768 or <= -768, a denormal or huge (>= 2045) denominator and a numerator exponent <= 53; a NaN source is passed through unchanged and a zero input gives 0xfff8000000000000. - div_fmas scales by 2^128 when the exponent of S2 is >= 1024, else 2^-128, before the single rounding, so it reuses the exact fma emulation with the scale folded into its final rescale. - div_fixup follows the ISA: NaN sources are quieted, a NaN quotient with finite inputs becomes +-inf, exponent differences below -1075 give a signed zero. Co-Authored-By: Claude --- .../tests/execution/F64Division.cpp | 172 ++++++++++++++++++ .../tests/execution/F64DivisionModifiers.cpp | 172 ++++++++++++++++++ .../IntermediateRepresentation/IrOpcode.hpp | 1 + .../src/IrOpcode.cpp | 1 + .../include/RdnaDecoder/RdnaOpcode.hpp | 3 + .../recompiler/RdnaDecoder/src/RdnaOpcode.cpp | 3 + .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 11 +- .../SpirvBackend/SpirvEmitterInstructions.hpp | 1 + .../src/SpirvAlu/SpirvAluEmitterFloat64.cpp | 41 +++-- .../SpirvBackend/src/SpirvFlowEmitter.cpp | 1 + .../Translation/TranslationContext.hpp | 3 + .../Translation/src/Float64Instructions.cpp | 104 +++++++++++ .../Translation/src/VectorInstructions.cpp | 6 + 13 files changed, 501 insertions(+), 18 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/F64Division.cpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/F64DivisionModifiers.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F64Division.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F64Division.cpp new file mode 100644 index 000000000..634a3ed4e --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F64Division.cpp @@ -0,0 +1,172 @@ +#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 +#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, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd56e6a0a, 0x041a0904, 0xd5010018, + 0x01a90280, 0xd56e6a0c, 0x041a0906, 0xd5010019, 0x01a90480, 0x38303318, 0xbeea03c1, 0xd570000e, + 0x04120d04, 0xd5700010, 0x041a0d04, 0xd5600012, 0x04120d04, 0xd5600014, 0x041a0906, 0xd5600016, + 0x041a08ff, 0x7ff80000, 0x7e320280, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, + 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000001u, 0x7ff00000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0x7ff40000u, 0x00000000u, 0x3ff00000u}, + {0xe61541b6u, 0xcdde1df6u, 0x652b0ed7u, 0xfddc36e5u}, + {0xc3d4a267u, 0x03d31de5u, 0x08426379u, 0xb3cb953cu}, + {0xceb52fc3u, 0xb3648a3fu, 0xd8fe4338u, 0x8367c969u}, + {0x131c99fcu, 0x7f65f30fu, 0x888c6e8bu, 0xcf72c7f8u}, + {0x00000000u, 0x7fd00000u, 0x00000000u, 0x3ca00000u}, + {0xffffffffu, 0x7fefffffu, 0x00000000u, 0x3ca00000u}, + {0x00000000u, 0x7fd00000u, 0xffffffffu, 0x7fefffffu}, + {0x00000000u, 0x7fc00000u, 0xffffffffu, 0x7fefffffu}, + {0x00000000u, 0x00100000u, 0x00000000u, 0x03500000u}, + {0x00000000u, 0x00100000u, 0x00000000u, 0x03600000u}, + {0x8a41dfb1u, 0xbffc3c7cu, 0xffffffffu, 0x7fefffffu}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xfff00000u}, + {0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xbff00000u, 0x00000000u, 0xfff00000u}, + {0x00000000u, 0x7ff00000u, 0xffffffffu, 0x7fefffffu}, + {0x00000001u, 0xfff80000u, 0x00000000u, 0x7ff80000u}, + {0x00000000u, 0x00000000u, 0x00000001u, 0xfff80000u}, + {0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xbff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00100000u}, + {0x00000000u, 0x00000000u, 0xffffffffu, 0x7fefffffu}, + {0x00000000u, 0x00000000u, 0xffffffffu, 0xffefffffu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x7ff80000u}, + {0x00000000u, 0x00000000u, 0x00000001u, 0x7ff00000u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000001u, 0x7ff00000u, 0x00000000u, 0x3ff00000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0x7ff40000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000003u, 0x00000000u}, + {0xe61541b6u, 0xd5de1df6u, 0x652b0ed7u, 0xfddc36e5u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0xe61541b6u, 0x4dde1df6u, 0x652b0ed7u, 0x7ddc36e5u, 0x00000000u, 0x7ff00000u, 0x00000003u, 0x00000000u}, + {0xc3d4a267u, 0x03d31de5u, 0x08426379u, 0xb3cb953cu, 0x00000000u, 0x00000000u, 0x08426379u, 0xabcb953cu, 0xc3d4a267u, 0x83d31de5u, 0x08426379u, 0xb3cb953cu, 0x00000000u, 0xfff00000u, 0x00000000u, 0x00000000u}, + {0xceb52fc3u, 0xb3648a3fu, 0xd8fe4338u, 0x8b67c969u, 0xceb52fc3u, 0xab648a3fu, 0x00000000u, 0x80000000u, 0xceb52fc3u, 0x33648a3fu, 0xd8fe4338u, 0x0367c969u, 0x00000000u, 0x7ff00000u, 0x00000003u, 0x00000000u}, + {0x131c99fcu, 0x7f65f30fu, 0x888c6e8bu, 0xcf72c7f8u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u, 0x131c99fcu, 0xff65f30fu, 0x888c6e8bu, 0xcf72c7f8u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x77d00000u, 0x00000000u, 0x3ca00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x74800000u, 0x00000000u, 0x7fd00000u, 0x00000000u, 0x3ca00000u, 0x00000000u, 0x7ff00000u, 0x00000003u, 0x00000000u}, + {0xffffffffu, 0x77efffffu, 0x00000000u, 0x3ca00000u, 0x00000000u, 0x7ff00000u, 0xffffffffu, 0x749fffffu, 0xffffffffu, 0x7fefffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0x77d00000u, 0xffffffffu, 0x77efffffu, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7fd00000u, 0xffffffffu, 0x7fefffffu, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x7fc00000u, 0xffffffffu, 0x7fefffffu, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7fc00000u, 0xffffffffu, 0x7fefffffu, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x08100000u, 0x00000000u, 0x0b500000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00100000u, 0x00000000u, 0x03500000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00100000u, 0x00000000u, 0x03600000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00100000u, 0x00000000u, 0x03600000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u}, + {0x8a41dfb1u, 0xc7fc3c7cu, 0xffffffffu, 0x7fefffffu, 0x8a41dfb0u, 0xf7fc3c7cu, 0x00000000u, 0xfff00000u, 0x8a41dfb1u, 0xbffc3c7cu, 0xffffffffu, 0xffefffffu, 0x00000000u, 0xfff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xc7f00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0x7ff00000u, 0xffffffffu, 0x77efffffu, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000001u, 0xfff80000u, 0x00000000u, 0x7ff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x37f00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x37f00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xb7f00000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000003u, 0x00000000u} +}; +constexpr const char* Names[7] = {"div_scale denominator", "div_scale numerator", "div_fmas a*b+a", "div_fmas a*b+b", "div_fixup a", "div_fixup b", "div_fixup nan"}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("f64 division: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 7; ++i) { + Expect(tid, out[2 * i], Expected[tid][2 * i], Names[i]); + Expect(tid, out[2 * i + 1], Expected[tid][2 * i + 1], Names[i]); + } + Expect(tid, out[14], Expected[tid][14], "div_scale vcc"); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("f64 division tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F64DivisionModifiers.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F64DivisionModifiers.cpp new file mode 100644 index 000000000..645f47a10 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F64DivisionModifiers.cpp @@ -0,0 +1,172 @@ +#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 +#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, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd56e6a0a, 0x641a0904, 0xd5010018, + 0x01a90280, 0xd56e6a0c, 0x841a0906, 0xd5010019, 0x01a90480, 0x38303318, 0xbeea03c1, 0xd570060e, + 0xa4120d04, 0xd5700010, 0x04120d06, 0xbeea0380, 0xd5700012, 0x041a0d04, 0xd5600514, 0x64120d04, + 0x36320881, 0x7d8a3280, 0xd5700016, 0x041a0906, 0x7e320280, 0xe0781000, 0x80010a03, 0xe0781010, + 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000001u, 0x7ff00000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0x7ff40000u, 0x00000000u, 0x3ff00000u}, + {0xe61541b6u, 0xcdde1df6u, 0x652b0ed7u, 0xfddc36e5u}, + {0xc3d4a267u, 0x03d31de5u, 0x08426379u, 0xb3cb953cu}, + {0xceb52fc3u, 0xb3648a3fu, 0xd8fe4338u, 0x8367c969u}, + {0x131c99fcu, 0x7f65f30fu, 0x888c6e8bu, 0xcf72c7f8u}, + {0x00000000u, 0x7fd00000u, 0x00000000u, 0x3ca00000u}, + {0xffffffffu, 0x7fefffffu, 0x00000000u, 0x3ca00000u}, + {0x00000000u, 0x7fd00000u, 0xffffffffu, 0x7fefffffu}, + {0x00000000u, 0x7fc00000u, 0xffffffffu, 0x7fefffffu}, + {0x00000000u, 0x00100000u, 0x00000000u, 0x03500000u}, + {0x00000000u, 0x00100000u, 0x00000000u, 0x03600000u}, + {0x00000000u, 0x7ff00000u, 0x00000001u, 0xfff80000u}, + {0xffffffffu, 0xffefffffu, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xfff00000u}, + {0x00000000u, 0x80000000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0x00100000u, 0x00000000u, 0x80000000u}, + {0x00000001u, 0xfff80000u, 0x00000000u, 0x7ff80000u}, + {0x00000000u, 0x3ff00000u, 0x00000000u, 0xfff00000u}, + {0x00000000u, 0xbff00000u, 0xffffffffu, 0x7fefffffu}, + {0xb0894f5au, 0x80834975u, 0x4c867062u, 0x0376ec15u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xbff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00100000u}, + {0x00000000u, 0x00000000u, 0xffffffffu, 0x7fefffffu}, + {0x00000000u, 0x00000000u, 0xffffffffu, 0xffefffffu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x7ff80000u}, + {0x00000000u, 0x00000000u, 0x00000001u, 0xfff80000u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000001u, 0xfff00000u, 0x00000000u, 0x3ff00000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff40000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0xfffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000003u, 0x00000000u}, + {0xe61541b6u, 0x55de1df6u, 0x652b0ed7u, 0xfddc36e5u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0xe61541b6u, 0x4dde1df6u, 0x00000000u, 0x7ff00000u, 0x00000003u, 0x00000000u}, + {0xc3d4a267u, 0x83d31de5u, 0x08426379u, 0xb3cb953cu, 0x00000000u, 0x80000000u, 0x456d9116u, 0x1fa7c68du, 0x08426379u, 0xb3cb953cu, 0xc3d4a267u, 0x03d31de5u, 0x08426379u, 0xabcb953cu, 0x00000000u, 0x00000000u}, + {0xceb52fc3u, 0x33648a3fu, 0xd8fe4338u, 0x8b67c969u, 0xceb52fc3u, 0xab648a3fu, 0xceb52fc3u, 0xab648a3fu, 0xd8fe4338u, 0x8367c969u, 0xceb52fc3u, 0x33648a3fu, 0x00000000u, 0x80000000u, 0x00000003u, 0x00000000u}, + {0x131c99fcu, 0xff65f30fu, 0x888c6e8bu, 0xcf72c7f8u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff00000u, 0x131c99fcu, 0x7f65f30fu, 0x00000000u, 0xfff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xf7d00000u, 0x00000000u, 0x3ca00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7c800000u, 0x00000000u, 0xffd00000u, 0x00000000u, 0x7c800000u, 0x00000003u, 0x00000000u}, + {0xffffffffu, 0xf7efffffu, 0x00000000u, 0x3ca00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u, 0xffffffffu, 0x7c9fffffu, 0xffffffffu, 0xffefffffu, 0xffffffffu, 0x749fffffu, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xf7d00000u, 0xffffffffu, 0x77efffffu, 0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xffd00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xffc00000u, 0xffffffffu, 0x7fefffffu, 0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xffc00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x88100000u, 0x00000000u, 0x0b500000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x03500000u, 0x00000000u, 0x80100000u, 0x00000000u, 0x03500000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x80100000u, 0x00000000u, 0x03600000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x03600000u, 0x00000000u, 0x80100000u, 0x00000000u, 0x03600000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff00000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0xfff80000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x00000000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff80000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x37f00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x3ff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x00000001u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xc7f00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0x47f00000u, 0xffffffffu, 0x7fefffffu, 0xffffffffu, 0x77efffffu, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbff00000u, 0x00000000u, 0x00000000u, 0x00000003u, 0x00000000u}, + {0xb0894f5au, 0x00834975u, 0x4c867062u, 0x0376ec15u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x4c867062u, 0x0376ec15u, 0xb0894f5au, 0x80834975u, 0x4c867062u, 0x0376ec15u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x37f00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x3ff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x37f00000u, 0x00000000u, 0xbff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00100000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00100000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x7ff00000u, 0xffffffffu, 0x7fefffffu, 0x00000000u, 0x80000000u, 0xffffffffu, 0x7fefffffu, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x7ff00000u, 0xffffffffu, 0xffefffffu, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffefffffu, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xfff80000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x7ff80000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0xfff80000u, 0x00000003u, 0x00000000u} +}; +constexpr const char* Names[7] = {"div_scale -a", "div_scale -b", "div_fmas modifiers", "div_fmas b*b+a", "div_fmas vcc 0", "div_fixup modifiers", "div_fmas vcc per lane"}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("f64 division modifiers: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 7; ++i) { + Expect(tid, out[2 * i], Expected[tid][2 * i], Names[i]); + Expect(tid, out[2 * i + 1], Expected[tid][2 * i + 1], Names[i]); + } + Expect(tid, out[14], Expected[tid][14], "div_scale vcc"); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("f64 division modifiers tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp index d5057d62f..1abea8a98 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp @@ -248,6 +248,7 @@ enum class IrOpcode : std::uint16_t { FPAdd64, FPMul64, FPFma64, + FPFmaScale64, FPMin64, FPMax64, FPSaturate64, diff --git a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp index 6ff239e77..158279a36 100644 --- a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp +++ b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp @@ -289,6 +289,7 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("FPAdd64", U64, U64, U64), makeMeta("FPMul64", U64, U64, U64), makeMeta("FPFma64", U64, U64, U64, U64), + makeMeta("FPFmaScale64", U64, U64, U64, U64, U32), makeMeta("FPMin64", U64, U64, U64), makeMeta("FPMax64", U64, U64, U64), makeMeta("FPSaturate64", U64, U64), diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp index 90b1cd58c..7d828913b 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -416,6 +416,9 @@ enum class RdnaOpcode : std::uint16_t { VRsqF64, VSqrtF64, VTrigPreopF64, + VDivScaleF64, + VDivFmasF64, + VDivFixupF64, VCvtPknormI16F32, VCvtPknormU16F32, VCvtPkU16U32, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp index b2c35b4fd..99ac177cf 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp @@ -367,6 +367,9 @@ bool IsVectorAluOpcode(RdnaOpcode opcode) { case RdnaOpcode::VRsqF64: case RdnaOpcode::VSqrtF64: case RdnaOpcode::VTrigPreopF64: + case RdnaOpcode::VDivScaleF64: + case RdnaOpcode::VDivFmasF64: + case RdnaOpcode::VDivFixupF64: case RdnaOpcode::VCvtPknormI16F32: case RdnaOpcode::VCvtPknormU16F32: case RdnaOpcode::VCvtPkU16U32: diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index 066bfffff..2d0192f12 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -559,6 +559,9 @@ constexpr VectorOpcodeInfo vop3Opcodes[] = { {0x16du, RdnaOpcode::VDivScaleF32}, {0x16fu, RdnaOpcode::VDivFmasF32}, {0x174u, RdnaOpcode::VTrigPreopF64}, + {0x160u, RdnaOpcode::VDivFixupF64}, + {0x16eu, RdnaOpcode::VDivScaleF64}, + {0x170u, RdnaOpcode::VDivFmasF64}, {0x363u, RdnaOpcode::VBfmB32}, {0x364u, RdnaOpcode::VBcntU32B32}, {0x365u, RdnaOpcode::VMbcntLoU32B32}, @@ -691,7 +694,7 @@ bool isVop3BCarryOutOpcode(RdnaOpcode opcode) { } bool isVop3BMadU64Opcode(RdnaOpcode opcode) { - return opcode == RdnaOpcode::VMadU64U32 || opcode == RdnaOpcode::VMadI64I32 || opcode == RdnaOpcode::VDivScaleF32; + return opcode == RdnaOpcode::VMadU64U32 || opcode == RdnaOpcode::VMadI64I32 || opcode == RdnaOpcode::VDivScaleF32 || opcode == RdnaOpcode::VDivScaleF64; } bool isPermlaneOpcode(RdnaOpcode opcode) { @@ -1794,6 +1797,8 @@ bool supportsNativeVop3SourceModifiers(RdnaOpcode opcode) { case RdnaOpcode::VDivFixupF32: case RdnaOpcode::VLdexpF32: case RdnaOpcode::VFmaF64: + case RdnaOpcode::VDivFmasF64: + case RdnaOpcode::VDivFixupF64: case RdnaOpcode::VAddF64: case RdnaOpcode::VMulF64: case RdnaOpcode::VMinF64: @@ -1866,7 +1871,7 @@ void checkNativeVop3Modifiers(RdnaOpcode opcode, bool permlane, bool carryInOut, } if (carryInOut || scalarDst) { const bool floatCompare = scalarDst && isVopcFloatCompareOpcode(opcode); - if (clamp != 0u || omod != 0u || (neg != 0u && !floatCompare && opcode != RdnaOpcode::VDivScaleF32)) { + if (clamp != 0u || omod != 0u || (neg != 0u && !floatCompare && opcode != RdnaOpcode::VDivScaleF32 && opcode != RdnaOpcode::VDivScaleF64)) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); } return; @@ -2146,7 +2151,7 @@ RdnaInstruction DecodeRdnaVop3(std::uint32_t programCounter, std::span(-768))})); + const IrU1 denominatorDenormal(ir.IEqual(denominatorExponent.Value(), ir.Constant(0u))); + const IrU1 denominatorHuge(ir.Emit(IrOpcode::UGreaterThanEqual32, IrType::U1, {&denominatorExponent.Value(), &ir.Constant(2045u)})); + const IrU1 numeratorTiny(ir.Emit(IrOpcode::ULessThanEqual32, IrType::U1, {&numeratorExponent.Value(), &ir.Constant(53u)})); + const IrU1 isDenominator(ir.LogicalAnd(ir.IEqual(value[0].Value(), denominator[0].Value()), ir.IEqual(value[1].Value(), denominator[1].Value()))); + const IrU1 notNearMax(ir.LogicalNot(nearMax.Value())); + const IrU1 notDenormal(ir.LogicalNot(denominatorDenormal.Value())); + const IrU1 normalRange(ir.LogicalAnd(notNearMax.Value(), notDenormal.Value())); + const IrU1 belowHuge(ir.LogicalAnd(normalRange.Value(), ir.LogicalNot(denominatorHuge.Value()))); + const IrU1 scaleUp(ir.LogicalOr( + ir.LogicalOr(ir.LogicalAnd(nearMax.Value(), isDenominator.Value()), ir.LogicalAnd(notNearMax.Value(), denominatorDenormal.Value())), + ir.LogicalAnd(belowHuge.Value(), ir.LogicalOr(ir.LogicalAnd(nearMin.Value(), ir.LogicalNot(isDenominator.Value())), + ir.LogicalAnd(ir.LogicalNot(nearMin.Value()), numeratorTiny.Value()))))); + const IrU1 scaleDown(ir.LogicalAnd(ir.LogicalAnd(normalRange.Value(), denominatorHuge.Value()), + ir.LogicalOr(ir.LogicalNot(nearMin.Value()), isDenominator.Value()))); + IrValue& bits = ir.ConstructU64(value[0].Value(), value[1].Value()); + IrValue& power = ir.Select(scaleUp.Value(), ir.Constant(128u), ir.Constant(static_cast(-128))); + IrValue& scaled = ir.Emit(IrOpcode::FPLdexp64, IrType::U64, {&bits, &power}); + const auto magnitude = [&](IrU32 high) { return IrU32(ir.BitwiseAnd(high.Value(), ir.Constant(0x7fffffffu))); }; + const IrU1 valueNan(ir.LogicalOr(ir.UGreaterThan(magnitude(value[1]).Value(), ir.Constant(0x7ff00000u)), + ir.LogicalAnd(ir.IEqual(magnitude(value[1]).Value(), ir.Constant(0x7ff00000u)), ir.INotEqual(value[0].Value(), ir.Constant(0u))))); + const IrU1 rescale(ir.LogicalAnd(ir.LogicalOr(scaleUp.Value(), scaleDown.Value()), ir.LogicalNot(valueNan.Value()))); + const auto isZero = [&](const std::array& word) { + return IrU1(ir.IEqual(ir.BitwiseOr(magnitude(word[1]).Value(), word[0].Value()), ir.Constant(0u))); + }; + const IrU1 zero(ir.LogicalOr(isZero(denominator).Value(), isZero(numerator).Value())); + std::array result{IrU32(ir.CompositeExtract(scaled, 0u)), IrU32(ir.CompositeExtract(scaled, 1u))}; + result[0] = IrU32(ir.Select(rescale.Value(), result[0].Value(), value[0].Value())); + result[1] = IrU32(ir.Select(rescale.Value(), result[1].Value(), value[1].Value())); + result[0] = IrU32(ir.Select(zero.Value(), ir.Constant(0u), result[0].Value())); + result[1] = IrU32(ir.Select(zero.Value(), ir.Constant(0xfff80000u), result[1].Value())); + writeOperand(inst.destination, &ir.ConstructU64(result[0].Value(), result[1].Value())); + writeMask(inst.destination2, IrU1(ir.LogicalOr(nearMax.Value(), ir.LogicalAnd(notDenormal.Value(), nearMin.Value())))); + return true; +} + +bool TranslationContext::vDivFmasF64(const RdnaInstruction& inst) { + RdnaOperand vcc{}; + vcc.kind = RdnaOperandKind::VccLo; + const IrU1 scale = readMask(vcc); + const auto addend = readF64Bits(sourceAt(inst, 2u)); + IrValue& addendExponent = ir.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&addend[1].Value(), &ir.Constant(20u), &ir.Constant(11u)}); + const IrU1 up(ir.Emit(IrOpcode::UGreaterThanEqual32, IrType::U1, {&addendExponent, &ir.Constant(1024u)})); + IrValue& power = ir.Select(scale.Value(), ir.Select(up.Value(), ir.Constant(128u), ir.Constant(static_cast(-128))), ir.Constant(0u)); + IrValue& result = ir.Emit(IrOpcode::FPFmaScale64, IrType::U64, + {&readF64(sourceAt(inst, 0u)).Value(), &readF64(sourceAt(inst, 1u)).Value(), &ir.ConstructU64(addend[0].Value(), addend[1].Value()), &power}); + writeOperand(inst.destination, &result); + return true; +} + +bool TranslationContext::vDivFixupF64(const RdnaInstruction& inst) { + const auto quotient = readF64Bits(sourceAt(inst, 0u)); + const auto denominator = readF64Bits(sourceAt(inst, 1u)); + const auto numerator = readF64Bits(sourceAt(inst, 2u)); + const auto magnitude = [&](IrU32 high) { return IrU32(ir.BitwiseAnd(high.Value(), ir.Constant(0x7fffffffu))); }; + const auto lowNonzero = [&](const std::array& word) { return IrU1(ir.INotEqual(word[0].Value(), ir.Constant(0u))); }; + const auto isNan = [&](const std::array& word) { + return IrU1(ir.LogicalOr(ir.UGreaterThan(magnitude(word[1]).Value(), ir.Constant(0x7ff00000u)), + ir.LogicalAnd(ir.IEqual(magnitude(word[1]).Value(), ir.Constant(0x7ff00000u)), lowNonzero(word).Value()))); + }; + const auto isInf = [&](const std::array& word) { + return IrU1(ir.LogicalAnd(ir.IEqual(magnitude(word[1]).Value(), ir.Constant(0x7ff00000u)), ir.LogicalNot(lowNonzero(word).Value()))); + }; + const auto isZero = [&](const std::array& word) { + return IrU1(ir.IEqual(ir.BitwiseOr(magnitude(word[1]).Value(), word[0].Value()), ir.Constant(0u))); + }; + const auto exponent = [&](IrU32 high) { + return IrU32(ir.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&high.Value(), &ir.Constant(20u), &ir.Constant(11u)})); + }; + const IrU32 sign(ir.BitwiseAnd(ir.BitwiseXor(denominator[1].Value(), numerator[1].Value()), ir.Constant(0x80000000u))); + const std::array zero{IrU32(ir.Constant(0u)), sign}; + const std::array infinity{IrU32(ir.Constant(0u)), IrU32(ir.BitwiseOr(sign.Value(), ir.Constant(0x7ff00000u)))}; + const std::array defaultNan{IrU32(ir.Constant(0u)), IrU32(ir.Constant(0xfff80000u))}; + const auto quiet = [&](const std::array& word) { + return std::array{word[0], IrU32(ir.BitwiseOr(word[1].Value(), ir.Constant(0x00080000u)))}; + }; + const IrU32 difference(ir.ISub(exponent(numerator[1]).Value(), exponent(denominator[1]).Value())); + const IrU1 underflow(ir.Emit(IrOpcode::SLessThan32, IrType::U1, {&difference.Value(), &ir.Constant(static_cast(-1075))})); + std::array result{quotient[0], IrU32(ir.BitwiseOr(sign.Value(), magnitude(quotient[1]).Value()))}; + const auto choose = [&](IrU1 condition, const std::array& word) { + result = {IrU32(ir.Select(condition.Value(), word[0].Value(), result[0].Value())), IrU32(ir.Select(condition.Value(), word[1].Value(), result[1].Value()))}; + }; + choose(isNan(quotient), infinity); + choose(underflow, zero); + choose(IrU1(ir.LogicalOr(isInf(denominator).Value(), isZero(numerator).Value())), zero); + choose(IrU1(ir.LogicalOr(isZero(denominator).Value(), isInf(numerator).Value())), infinity); + choose(IrU1(ir.LogicalOr(ir.LogicalAnd(isZero(denominator).Value(), isZero(numerator).Value()), ir.LogicalAnd(isInf(denominator).Value(), isInf(numerator).Value()))), defaultNan); + choose(isNan(denominator), quiet(denominator)); + choose(isNan(numerator), quiet(numerator)); + writeOperand(inst.destination, &ir.ConstructU64(result[0].Value(), result[1].Value())); + return true; +} + } diff --git a/core/shader/recompiler/Translation/src/VectorInstructions.cpp b/core/shader/recompiler/Translation/src/VectorInstructions.cpp index 19ac73ab3..037d5832d 100644 --- a/core/shader/recompiler/Translation/src/VectorInstructions.cpp +++ b/core/shader/recompiler/Translation/src/VectorInstructions.cpp @@ -940,6 +940,12 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { return vDivFmasF32(inst); case RdnaOpcode::VDivFixupF32: return vDivFixupF32(inst); + case RdnaOpcode::VDivScaleF64: + return vDivScaleF64(inst); + case RdnaOpcode::VDivFmasF64: + return vDivFmasF64(inst); + case RdnaOpcode::VDivFixupF64: + return vDivFixupF64(inst); case RdnaOpcode::VLdexpF32: return floatBinary(inst, IrOpcode::FPLdexp, false); case RdnaOpcode::VMacF32: From b94b1f96dc84ddee7d33ecb0eb14295d29d5c65e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Fri, 2 Oct 2026 07:51:23 +0200 Subject: [PATCH 053/164] fix(shader): apply clamp but not omod to f16 results, as RDNA does writeF16 dropped the clamp bit, so VOP3/SDWA f16 results with clamp (v_rcp_f16, v_fma_f16, v_cvt_f16_f32, v_add_f16_sdwa, ...) were not saturated. Clamp now limits to [0, 1] with NaN and -0 going to +0. The f16 sqrt/rsq/log and sin/cos paths applied omod, which the hardware ignores on f16 results in the default float mode, and v_cvt_pkrtz_f16_f32 applied clamp and omod to its inputs, which the hardware ignores as well. Co-Authored-By: Claude --- .../execution/Float16ResultModifiers.cpp | 175 ++++++++++++++++++ .../Translation/TranslationContext.hpp | 1 + .../Translation/src/ConvertInstructions.cpp | 4 +- .../Translation/src/FloatInstructions.cpp | 4 +- .../src/TranslationContext/OperandAccess.cpp | 10 + .../src/TranslationContext/SubwordPacking.cpp | 2 +- 6 files changed, 191 insertions(+), 5 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Float16ResultModifiers.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Float16ResultModifiers.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Float16ResultModifiers.cpp new file mode 100644 index 000000000..af3b1c9e2 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Float16ResultModifiers.cpp @@ -0,0 +1,175 @@ +#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 +#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, 0x34060086, 0xe0301000, 0x80000401, 0xe0301004, 0x80000501, 0xe0301008, 0x80000601, + 0xe030100c, 0x80000701, 0x7e140280, 0x7e160280, 0x7e180280, 0x7e1a0280, 0x7e1c0280, 0x7e1e0280, + 0x7e200280, 0x7e220280, 0x7e240280, 0x7e260280, 0x7e280280, 0x7e2a0280, 0x7e2c0280, 0x7e2e0280, + 0x7e300280, 0x7e320280, 0xbf8c3f70, 0xd5d4800a, 0x00000104, 0xd5d4000b, 0x08000104, 0xd5d5800c, + 0x08000104, 0xd5e0000d, 0x08000104, 0xd5e0800e, 0x00000105, 0xd58a800f, 0x00000107, 0xd58a0010, + 0x18000107, 0xd5d08011, 0x00000106, 0xd74b8012, 0x041a0b04, 0xd74b0013, 0x0c1a0b04, 0x64280af9, + 0x04042404, 0x6a2a0af9, 0x04044404, 0xd52f8016, 0x00020d07, 0xd52f0017, 0x18020d07, 0xd5db8018, + 0x00000105, 0x64320af9, 0x04043504, 0xe0701000, 0x80010a03, 0xe0701004, 0x80010b03, 0xe0701008, + 0x80010c03, 0xe070100c, 0x80010d03, 0xe0701010, 0x80010e03, 0xe0701014, 0x80010f03, 0xe0701018, + 0x80011003, 0xe070101c, 0x80011103, 0xe0701020, 0x80011203, 0xe0701024, 0x80011303, 0xe0701028, + 0x80011403, 0xe070102c, 0x80011503, 0xe0701030, 0x80011603, 0xe0701034, 0x80011703, 0xe0701038, + 0x80011803, 0xe070103c, 0x80011903, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00003400u, 0x00003800u, 0x00003000u, 0x3e99999au}, + {0x00003e00u, 0x0000b400u, 0x00004000u, 0xbfd9999au}, + {0x00007b53u, 0x000070e2u, 0x00007bffu, 0x47ffdc00u}, + {0x00000400u, 0x00003800u, 0x00000000u, 0x33000000u}, + {0x00007e00u, 0x00003c00u, 0x00003c00u, 0x7fc00000u}, + {0x00007c00u, 0x00003c00u, 0x0000bc00u, 0x7f800000u}, + {0x0000c000u, 0x0000c200u, 0x00004400u, 0x80000000u}, + {0x00000001u, 0x00003a00u, 0x00008001u, 0x477ff000u}, + {0x00002265u, 0x000091b7u, 0x0000d8f1u, 0xcd613e30u}, + {0x0000c386u, 0x00001027u, 0x0000414cu, 0xc2ce6f44u}, + {0x00007311u, 0x000078e5u, 0x0000a6ceu, 0xc9e9c616u}, + {0x000035bfu, 0x00001807u, 0x00000741u, 0xc324c985u}, + {0x0000c464u, 0x0000b222u, 0x00007204u, 0x442e3d43u}, + {0x0000b8b6u, 0x0000cd44u, 0x00003a90u, 0x37730edfu}, + {0x0000f813u, 0x00006c0fu, 0x0000b9d1u, 0x38c0c8fdu}, + {0x0000c381u, 0x00007019u, 0x0000f06du, 0x3bab6c39u}, + {0x0000587fu, 0x00003b1au, 0x0000ad45u, 0x380208a9u}, + {0x0000c2cdu, 0x000075a8u, 0x0000f3c6u, 0x4a2f20aau}, + {0x0000ed2fu, 0x00000580u, 0x00006a8au, 0xb94067edu}, + {0x0000dc25u, 0x00004be0u, 0x00001ef2u, 0xbe3edc0au}, + {0x0000552bu, 0x0000e544u, 0x0000b8b3u, 0xb610a9f7u}, + {0x0000efbau, 0x0000f79bu, 0x00006c0fu, 0x4da98f1du}, + {0x000048beu, 0x0000966bu, 0x0000f934u, 0x3e2434e3u}, + {0x0000be65u, 0x0000cc22u, 0x0000677fu, 0xb3fa7aa7u}, + {0x0000c69du, 0x0000acabu, 0x0000bcfbu, 0xc74803e3u}, + {0x000029e8u, 0x0000855cu, 0x0000d707u, 0xbb968a43u}, + {0x00000792u, 0x00007825u, 0x00000b21u, 0x4efbc8d6u}, + {0x0000b410u, 0x0000d92au, 0x0000fbb2u, 0x3a1890c7u}, + {0x0000fb69u, 0x0000c541u, 0x00003313u, 0x3b6fe507u}, + {0x0000678au, 0x00005804u, 0x0000f3d4u, 0x44ef7febu}, + {0x0000a8c2u, 0x00008c49u, 0x00009be3u, 0xbab9f87fu}, + {0x00006239u, 0x0000c89du, 0x0000db61u, 0xbd91a1b7u}, +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00003c00u, 0x00004400u, 0x00003800u, 0x00003c00u, 0x00000000u, 0x000034cdu, 0x000034cdu, 0x00003c00u, 0x00003400u, 0x00003400u, 0x00003a00u, 0x00003000u, 0x000034ccu, 0x000034ccu, 0x00000000u, 0x3a000000u}, + {0x00003955u, 0x00003955u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000becdu, 0x00003c00u, 0x00003c00u, 0x00003e80u, 0x00003c00u, 0x0000b600u, 0x0000beccu, 0x0000beccu, 0x00000000u, 0x3c000000u}, + {0x00000118u, 0x00000118u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00007c00u, 0x00003c00u, 0x00003c00u, 0x00007c00u, 0x00003c00u, 0x00007c00u, 0x00007bffu, 0x00007bffu, 0x00003c00u, 0x3c000000u}, + {0x00003c00u, 0x00007400u, 0x00002000u, 0x00000e48u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000200u, 0x00000200u, 0x00003800u, 0x00000200u, 0x00000000u, 0x00000000u, 0x00000000u, 0x38000000u}, + {0x00000000u, 0x00007e00u, 0x00000000u, 0x00007e00u, 0x00000000u, 0x00000000u, 0x00007e00u, 0x00003c00u, 0x00000000u, 0x00007e00u, 0x00000000u, 0x00007e00u, 0x00007e00u, 0x00007e00u, 0x00003c00u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00003c00u, 0x0000fe00u, 0x00000000u, 0x00003c00u, 0x00007c00u, 0x00003c00u, 0x00003c00u, 0x00007c00u, 0x00003c00u, 0x00007c00u, 0x00007c00u, 0x00007c00u, 0x00003c00u, 0x3c000000u}, + {0x00000000u, 0x0000b800u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00008000u, 0x00003c00u, 0x00003c00u, 0x00004900u, 0x00000000u, 0x00004600u, 0x00008000u, 0x00008000u, 0x00000000u, 0x00000000u}, + {0x00003c00u, 0x00007c00u, 0x00000c00u, 0x00000006u, 0x00000000u, 0x00003c00u, 0x00007c00u, 0x00003c00u, 0x00000000u, 0x00008000u, 0x00003a00u, 0x00000001u, 0x00007bffu, 0x00007bffu, 0x00000000u, 0x3a000000u}, + {0x00003c00u, 0x00005501u, 0x00002f27u, 0x00002d04u, 0x00000000u, 0x00000000u, 0x0000fc00u, 0x00003c00u, 0x00000000u, 0x0000d8f1u, 0x0000220au, 0x00008092u, 0x0000fbffu, 0x0000fbffu, 0x00000000u, 0x220a0000u}, + {0x00000000u, 0x0000b441u, 0x00000000u, 0x00003bfau, 0x00001a86u, 0x00000000u, 0x0000d673u, 0x00003c00u, 0x00003c00u, 0x0000414bu, 0x00000000u, 0x000097cfu, 0x0000d673u, 0x0000d673u, 0x00000000u, 0x00000000u}, + {0x00000487u, 0x00000487u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000fc00u, 0x00003c00u, 0x00003c00u, 0x00007c00u, 0x00003c00u, 0x00007c00u, 0x0000fbffu, 0x0000fbffu, 0x00003c00u, 0x3c000000u}, + {0x00003c00u, 0x00004192u, 0x000038cbu, 0x00003a31u, 0x00002253u, 0x00000000u, 0x0000d926u, 0x00003c00u, 0x000012b1u, 0x000012b1u, 0x000035c7u, 0x000011c9u, 0x0000d926u, 0x0000d926u, 0x00000000u, 0x35c70000u}, + {0x00000000u, 0x0000b34au, 0x00000000u, 0x0000b913u, 0x00000000u, 0x00003c00u, 0x00006172u, 0x00003c00u, 0x00003c00u, 0x00007204u, 0x00000000u, 0x00003abbu, 0x00006171u, 0x00006171u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x0000becbu, 0x00000000u, 0x0000383du, 0x00000000u, 0x000000f3u, 0x000000f3u, 0x00003c00u, 0x00003c00u, 0x00004a9du, 0x00000000u, 0x00004a34u, 0x000000f3u, 0x000000f3u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x000081f7u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000606u, 0x00000606u, 0x00003c00u, 0x00000000u, 0x0000fc00u, 0x00000000u, 0x0000fc00u, 0x00000606u, 0x00000606u, 0x00003c00u, 0x00000000u}, + {0x00000000u, 0x0000b444u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00001d5bu, 0x00001d5bu, 0x00003c00u, 0x00000000u, 0x0000f8f3u, 0x00003c00u, 0x0000f7b0u, 0x00001d5bu, 0x00001d5bu, 0x00003c00u, 0x3c000000u}, + {0x00001f1eu, 0x00001f1eu, 0x00003c00u, 0x0000b9a8u, 0x00000000u, 0x00000208u, 0x00000208u, 0x00003c00u, 0x00003c00u, 0x000057fau, 0x00003c00u, 0x000057fbu, 0x00000208u, 0x00000208u, 0x00000000u, 0x3c000000u}, + {0x00000000u, 0x0000b4b5u, 0x00000000u, 0x0000b8b0u, 0x00000000u, 0x00003c00u, 0x00007c00u, 0x00003c00u, 0x00000000u, 0x0000fc00u, 0x00003c00u, 0x0000fc00u, 0x00007bffu, 0x00007bffu, 0x00003c00u, 0x3c000000u}, + {0x00000000u, 0x00008a2cu, 0x00000000u, 0x00000000u, 0x00001052u, 0x00000000u, 0x00008a03u, 0x00003c00u, 0x00003c00u, 0x00006a8au, 0x00000000u, 0x0000b721u, 0x00008a03u, 0x00008a03u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00009bb9u, 0x00000000u, 0x0000bc00u, 0x00000000u, 0x00000000u, 0x0000b1f7u, 0x00003c00u, 0x00000000u, 0x0000ec14u, 0x00000000u, 0x0000ec14u, 0x0000b1f6u, 0x0000b1f6u, 0x00003c00u, 0x00000000u}, + {0x00002231u, 0x00002231u, 0x00003c00u, 0x0000bb64u, 0x00000000u, 0x00000000u, 0x00008024u, 0x00003c00u, 0x00000000u, 0x0000fc00u, 0x00000000u, 0x0000fc00u, 0x00008024u, 0x00008024u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00008824u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00007c00u, 0x00003c00u, 0x00003c00u, 0x00007c00u, 0x00000000u, 0x00007c00u, 0x00007bffu, 0x00007bffu, 0x00000000u, 0x00000000u}, + {0x00002ebfu, 0x00002ebfu, 0x00003c00u, 0x00002e46u, 0x00000000u, 0x00003122u, 0x00003122u, 0x00003c00u, 0x00000000u, 0x0000f934u, 0x00003c00u, 0x0000a39cu, 0x00003121u, 0x00003121u, 0x00000000u, 0x3c000000u}, + {0x00000000u, 0x0000b901u, 0x00000000u, 0x000038a6u, 0x0000323eu, 0x00000000u, 0x00008002u, 0x00003c00u, 0x00003c00u, 0x00006799u, 0x00000000u, 0x00004e9bu, 0x00008001u, 0x00008001u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x0000b0d7u, 0x00000000u, 0x0000393au, 0x00000000u, 0x00000000u, 0x0000fa40u, 0x00003c00u, 0x00000000u, 0x0000ba1au, 0x00000000u, 0x000037b8u, 0x0000fa40u, 0x0000fa40u, 0x00000000u, 0x00000000u}, + {0x00003c00u, 0x00004d6bu, 0x000032e0u, 0x00003493u, 0x00000000u, 0x00000000u, 0x00009cb4u, 0x00003c00u, 0x00000000u, 0x0000d707u, 0x000029e5u, 0x0000803fu, 0x00009cb4u, 0x00009cb4u, 0x00000000u, 0x29e50000u}, + {0x00003c00u, 0x0000703au, 0x00002181u, 0x000011f2u, 0x00000000u, 0x00003c00u, 0x00007c00u, 0x00003c00u, 0x00003c00u, 0x000043d8u, 0x00003c00u, 0x000043d8u, 0x00007bffu, 0x00007bffu, 0x00003c00u, 0x3c000000u}, + {0x00000000u, 0x0000c3e0u, 0x00000000u, 0x0000bbffu, 0x00000000u, 0x000010c5u, 0x000010c5u, 0x00003c00u, 0x00000000u, 0x0000fbb1u, 0x00000000u, 0x0000513fu, 0x000010c4u, 0x000010c4u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00008114u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00001b7fu, 0x00001b7fu, 0x00003c00u, 0x00003c00u, 0x00007c00u, 0x00000000u, 0x00007c00u, 0x00001b7fu, 0x00001b7fu, 0x00000000u, 0x00000000u}, + {0x0000103fu, 0x0000103fu, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x0000677cu, 0x00003c00u, 0x00003c00u, 0x00007c00u, 0x00003c00u, 0x00007c00u, 0x0000677bu, 0x0000677bu, 0x00003c00u, 0x3c000000u}, + {0x00000000u, 0x0000cebau, 0x00000000u, 0x0000b368u, 0x00000000u, 0x00000000u, 0x000095d0u, 0x00003c00u, 0x00000000u, 0x00009bdeu, 0x00000000u, 0x000000a3u, 0x000095cfu, 0x000095cfu, 0x00000000u, 0x00000000u}, + {0x00001524u, 0x00001524u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000ac8du, 0x00003c00u, 0x00000000u, 0x0000ef68u, 0x00003c00u, 0x0000ef2du, 0x0000ac8du, 0x0000ac8du, 0x00000000u, 0x3c000000u}, +}; +constexpr const char* Names[16] = {"rcp_clamp", "rcp_mul2", "sqrt_clamp_mul2", "sin_mul2", "sin_clamp", "cvt_f16_f32_clamp", "cvt_f16_f32_div2", "cvt_f16_u16_clamp", "fma_clamp", "fma_mul2", "add_sdwa_clamp", "mul_sdwa_mul2", "pkrtz_clamp", "pkrtz_div2", "floor_clamp", "add_sdwa_hi_clamp"}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("float16 result modifiers: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t index = 0; index < 16; ++index) Expect(tid, out[index], Expected[tid][index], Names[index]); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("float16 result modifiers tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index c78accaee..6f0a1dea9 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -44,6 +44,7 @@ private: IrU1 threadBit(const std::array& mask); void writeRawU32(const RdnaOperand& operand, IrU32 value); IrF32 applyF32ResultModifiers(const RdnaOperand& operand, IrF32 value); + IrF32 applyF16ResultModifiers(const RdnaOperand& operand, IrF32 value); void writeOperand(const RdnaOperand& operand, IrValue* value); IrU32 packHalf2x16(IrF32 low, IrF32 high); void write16Bits(const RdnaOperand& operand, IrU32 value); diff --git a/core/shader/recompiler/Translation/src/ConvertInstructions.cpp b/core/shader/recompiler/Translation/src/ConvertInstructions.cpp index 0a03104de..00842b3b2 100644 --- a/core/shader/recompiler/Translation/src/ConvertInstructions.cpp +++ b/core/shader/recompiler/Translation/src/ConvertInstructions.cpp @@ -145,8 +145,8 @@ void TranslationContext::vCvtOffF32I4(const RdnaInstruction& inst) { } void TranslationContext::vCvtPkrtzF16F32(const RdnaInstruction& inst) { - const IrF32 lhs = applyF32ResultModifiers(inst.destination, IrF32(*readOperand(sourceAt(inst, 0u), IrType::F32))); - const IrF32 rhs = applyF32ResultModifiers(inst.destination, IrF32(*readOperand(sourceAt(inst, 1u), IrType::F32))); + const IrF32 lhs(*readOperand(sourceAt(inst, 0u), IrType::F32)); + const IrF32 rhs(*readOperand(sourceAt(inst, 1u), IrType::F32)); const IrU32 result(ir.Emit(IrOpcode::PackFloat2x16Rtz, IrType::U32, {&lhs.Value(), &rhs.Value()})); writeOperand(inst.destination, &result.Value()); } diff --git a/core/shader/recompiler/Translation/src/FloatInstructions.cpp b/core/shader/recompiler/Translation/src/FloatInstructions.cpp index b11dbe80d..76f628728 100644 --- a/core/shader/recompiler/Translation/src/FloatInstructions.cpp +++ b/core/shader/recompiler/Translation/src/FloatInstructions.cpp @@ -56,7 +56,7 @@ bool TranslationContext::float16Unary(const RdnaInstruction& inst, IrOpcode opco return true; } const IrU1 negative(ir.Emit(IrOpcode::FPOrdLessThan32, IrType::U1, {&argument.Value(), &ir.ConstantF32(0.0f)})); - const IrF32 modified = applyF32ResultModifiers(inst.destination, result); + const IrF32 modified = applyF16ResultModifiers(inst.destination, result); const IrU32 bits = packHalf2x16(modified, IrF32(ir.ConstantF32(0.0f))); const IrU32 invalid(ir.Constant(inst.destination.clamp ? 0u : 0xfe00u)); write16Bits(inst.destination, IrU32(ir.Select(negative.Value(), invalid.Value(), bits.Value()))); @@ -115,7 +115,7 @@ bool TranslationContext::float16Trig(const RdnaInstruction& inst, IrOpcode opcod result = selectF32(quarterCycle, zero, raw); } const IrU1 infinite(ir.IEqual(magnitude.Value(), ir.Constant(0x7f800000u))); - const IrF32 modified = applyF32ResultModifiers(inst.destination, result); + const IrF32 modified = applyF16ResultModifiers(inst.destination, result); const IrU32 bits = packHalf2x16(modified, zero); const IrU32 invalid(ir.Constant(inst.destination.clamp ? 0u : 0xfe00u)); write16Bits(inst.destination, IrU32(ir.Select(infinite.Value(), invalid.Value(), bits.Value()))); diff --git a/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp b/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp index 235393ebe..d27ea5491 100644 --- a/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp +++ b/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp @@ -260,6 +260,16 @@ IrF32 TranslationContext::applyF32ResultModifiers(const RdnaOperand& operand, Ir return value; } +IrF32 TranslationContext::applyF16ResultModifiers(const RdnaOperand& operand, IrF32 value) { + if (!operand.clamp) { + return value; + } + const IrF32 zero(ir.ConstantF32(0.0f)); + const IrU1 positive(ir.Emit(IrOpcode::FPOrdGreaterThan32, IrType::U1, {&value.Value(), &zero.Value()})); + const IrF32 limited(ir.Emit(IrOpcode::FPMin32, IrType::F32, {&value.Value(), &ir.ConstantF32(1.0f)})); + return selectF32(positive, limited, zero); +} + IrU32 TranslationContext::readScalarCode(std::uint32_t code) { if (code < NumScalarRegs) { return IrU32(ir.GetScalarReg(static_cast(code))); diff --git a/core/shader/recompiler/Translation/src/TranslationContext/SubwordPacking.cpp b/core/shader/recompiler/Translation/src/TranslationContext/SubwordPacking.cpp index 903d821ee..fc3a74a66 100644 --- a/core/shader/recompiler/Translation/src/TranslationContext/SubwordPacking.cpp +++ b/core/shader/recompiler/Translation/src/TranslationContext/SubwordPacking.cpp @@ -157,7 +157,7 @@ void TranslationContext::write16Bits(const RdnaOperand& operand, IrU32 value) { } void TranslationContext::writeF16(const RdnaOperand& operand, IrF32 value) { - const IrF16 half(ir.Emit(IrOpcode::ConvertF16F32, IrType::F16, {&value.Value()})); + const IrF16 half(ir.Emit(IrOpcode::ConvertF16F32, IrType::F16, {&applyF16ResultModifiers(operand, value).Value()})); const IrU16 bits(ir.Emit(IrOpcode::BitCastU16F16, IrType::U16, {&half.Value()})); write16Bits(operand, IrU32(ir.Emit(IrOpcode::ConvertU32U16, IrType::U32, {&bits.Value()}))); } From 30fdc07cd5b8cf5ca48daf957e7eeb11f0138d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 08:26:50 +0200 Subject: [PATCH 054/164] test(shader): accept any NaN where the driver picks the f16 result payload Co-Authored-By: Claude --- .../tests/execution/Float16ResultModifiers.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Float16ResultModifiers.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Float16ResultModifiers.cpp index af3b1c9e2..17b66745f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/Float16ResultModifiers.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Float16ResultModifiers.cpp @@ -123,8 +123,16 @@ std::string Hex(std::uint32_t value) { return text; } +bool IsNan16(std::uint32_t bits) { + return (bits & 0x7fffu) > 0x7c00u; +} + +bool SameHalf(std::uint32_t actual, std::uint32_t expected) { + return actual == expected || (IsNan16(actual) && IsNan16(expected)); +} + void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { - Require(actual == expected, std::string("float16 result modifiers: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); + Require(SameHalf(actual & 0xffffu, expected & 0xffffu) && SameHalf(actual >> 16u, expected >> 16u), std::string("float16 result modifiers: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); } void Run(AgcDriver::VulkanDevice& device) { From 6686bff4939511dee4d056eb248187120a13fb96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 23:34:47 +0200 Subject: [PATCH 055/164] test(shader): compare f16 sine results within the Vulkan error bound Co-Authored-By: Claude --- .../execution/Float16ResultModifiers.cpp | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Float16ResultModifiers.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Float16ResultModifiers.cpp index 17b66745f..45b992a20 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/Float16ResultModifiers.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Float16ResultModifiers.cpp @@ -131,6 +131,22 @@ bool SameHalf(std::uint32_t actual, std::uint32_t expected) { return actual == expected || (IsNan16(actual) && IsNan16(expected)); } +double HalfValue(std::uint32_t bits) { + const double magnitude = (bits & 0x7c00u) == 0u ? std::ldexp(static_cast(bits & 0x3ffu), -24) : std::ldexp(static_cast((bits & 0x3ffu) | 0x400u), static_cast((bits >> 10u) & 0x1fu) - 25); + return (bits & 0x8000u) != 0u ? -magnitude : magnitude; +} + +bool NearHalf(std::uint32_t actual, std::uint32_t expected, bool clamped) { + if (clamped && (actual & 0x8000u) != 0u) return false; + if (!clamped && (expected & 0x7fffu) == 0u) return actual == expected; + if (IsNan16(actual) || IsNan16(expected) || (actual & 0x7fffu) == 0x7c00u || (expected & 0x7fffu) == 0x7c00u) return SameHalf(actual, expected); + return std::abs(HalfValue(actual) - HalfValue(expected)) <= 0x1p-10; +} + +void ExpectNear(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name, bool clamped) { + Require((actual >> 16u) == (expected >> 16u) && NearHalf(actual & 0xffffu, expected & 0xffffu, clamped), std::string("float16 result modifiers: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected near " + Hex(expected)); +} + void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { Require(SameHalf(actual & 0xffffu, expected & 0xffffu) && SameHalf(actual >> 16u, expected >> 16u), std::string("float16 result modifiers: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); } @@ -162,7 +178,10 @@ void Check() { for (std::uint32_t tid = 0; tid < Threads; ++tid) { const std::uint32_t* in = &Input[tid * Inputs]; const std::uint32_t* out = &Output[tid * Results]; - for (std::uint32_t index = 0; index < 16; ++index) Expect(tid, out[index], Expected[tid][index], Names[index]); + for (std::uint32_t index = 0; index < 16; ++index) { + if (index == 3u || index == 4u) ExpectNear(tid, out[index], Expected[tid][index], Names[index], index == 4u); + else Expect(tid, out[index], Expected[tid][index], Names[index]); + } } } From f3b74e2cef351b006f0054c472377a59de33b93b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 18:04:00 +0200 Subject: [PATCH 056/164] fix(shader): choose unary f32/f16 special results on the bits, as RDNA does v_ceil/floor/trunc/rndne/fract/sqrt/rsq/rcp/rcp_iflag/log/exp/sin/cos (f32 and f16) and v_frexp_mant_f32 left NaN, invalid, zero and infinite results to the driver: signalling NaNs came back unquieted, lavapipe lost the NaN sign and payload, invalid results were +NaN instead of -NaN, f32 sin/cos at whole and half cycles returned tiny values instead of +0, and v_fract_f16 of a tiny negative rounded up to 1.0. Adds an execution test for the 27 opcodes with expected values from hardware; approximate ops are compared with tolerances from the Vulkan precision table. Co-Authored-By: Claude --- .../tests/execution/Vop1FloatUnary.cpp | 220 ++++++++++++++++++ .../Translation/TranslationContext.hpp | 5 +- .../Translation/src/FloatInstructions.cpp | 133 +++++++---- .../Translation/src/VectorInstructions.cpp | 24 +- docs/dev/TechnicalDebt.md | 1 + 5 files changed, 327 insertions(+), 56 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Vop1FloatUnary.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop1FloatUnary.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop1FloatUnary.cpp new file mode 100644 index 000000000..4832b5c96 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop1FloatUnary.cpp @@ -0,0 +1,220 @@ +#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 +#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 = 64; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060088, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x2c100c90, 0x7e144504, 0x7e164505, + 0x7e184904, 0x7e1a4905, 0x7e1c4304, 0x7e1e4305, 0x7e204704, 0x7e224705, 0x7e244104, 0x7e264105, + 0x7e287f04, 0x7e2a7f05, 0x7e2c8104, 0x7e2e8105, 0x7e306704, 0x7e326705, 0x7e345d04, 0x7e365d05, + 0x7e385504, 0x7e3a5505, 0x7e3c5704, 0x7e3e5705, 0x7e404f04, 0x7e424f05, 0x7e444b04, 0x7e464b05, + 0x7e486b04, 0x7e4a6b05, 0x7e4c6d04, 0x7e4e6d05, 0x7e500307, 0x7e50b906, 0x7e520307, 0x7e52b908, + 0x7e540307, 0x7e54b706, 0x7e560307, 0x7e56b708, 0x7e580307, 0x7e58bb06, 0x7e5a0307, 0x7e5abb08, + 0x7e5c0307, 0x7e5cbd06, 0x7e5e0307, 0x7e5ebd08, 0x7e600307, 0x7e60bf06, 0x7e620307, 0x7e62bf08, + 0x7e640307, 0x7e64ab06, 0x7e660307, 0x7e66ab08, 0x7e680307, 0x7e68ad06, 0x7e6a0307, 0x7e6aad08, + 0x7e6c0307, 0x7e6ca906, 0x7e6e0307, 0x7e6ea908, 0x7e700307, 0x7e70af06, 0x7e720307, 0x7e72af08, + 0x7e740307, 0x7e74b106, 0x7e760307, 0x7e76b108, 0x7e780307, 0x7e78c106, 0x7e7a0307, 0x7e7ac108, + 0x7e7c0307, 0x7e7cc306, 0x7e7e0307, 0x7e7ec308, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, + 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xe0781040, 0x80011a03, 0xe0781050, 0x80011e03, + 0xe0781060, 0x80012203, 0xe0781070, 0x80012603, 0xe0781080, 0x80012a03, 0xe0781090, 0x80012e03, + 0xe07810a0, 0x80013203, 0xe07810b0, 0x80013603, 0xe07810c0, 0x80013a03, 0xe07810d0, 0x80013e03, + 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x807fffffu, 0x007fffffu, 0x3c003800u, 0x13579bdfu}, + {0x7f800001u, 0xffc00000u, 0x7c01fe00u, 0x12345678u}, + {0xff800000u, 0x80000000u, 0xfc008000u, 0x9abcdef0u}, + {0xb0800000u, 0x3f800000u, 0x84003400u, 0x5555aaaau}, + {0x7f800000u, 0x3e2b6c1au, 0xbe007c00u, 0x2de0a56au}, + {0xbfc00000u, 0x43000000u, 0x00006401u, 0x7274aa88u}, + {0xc2fc0000u, 0x7fc00000u, 0xc0c1fc01u, 0x94d3d2ffu}, + {0x7fa00000u, 0xff800001u, 0xebdb4800u, 0xfb20bf6cu}, + {0xbf800000u, 0x3ab77039u, 0x7e0063ffu, 0xeb609511u}, + {0x00000000u, 0x3f400000u, 0x9ba1236cu, 0x8a4ee80du}, + {0x3dcccccdu, 0x3f400000u, 0x0000ba00u, 0xe8a8529fu}, + {0x7fa00000u, 0x40b5e831u, 0x54ed5567u, 0xe779c470u}, + {0xbfa4797cu, 0x43000000u, 0x3800c85au, 0x691406beu}, + {0x4036ac94u, 0xba85a805u, 0x53869182u, 0xec007b1bu}, + {0xbeffcf24u, 0x00000000u, 0xa7e21400u, 0x686e80a9u}, + {0x7fa00000u, 0x3f800000u, 0xbd813570u, 0x75dd67deu}, + {0xbb27aea0u, 0xbf9e8d90u, 0x1a67bbffu, 0x526c5cc5u}, + {0xbe381e7fu, 0xbc0a3bbfu, 0x2ce6fe00u, 0xc9794969u}, + {0xc2fc0000u, 0x3cee8ea0u, 0x5484b800u, 0x6bd881fdu}, + {0x421c4347u, 0x3dcccccdu, 0xf08320f5u, 0xd2d0d097u}, + {0x40200000u, 0xbf800000u, 0x4e023c00u, 0x7d7015fcu}, + {0x384fbc0eu, 0x42d05018u, 0x3bff8400u, 0x75f99ac4u}, + {0xffc00000u, 0xb966cd34u, 0x8000bbffu, 0xefbd6b85u}, + {0xb3800000u, 0x3abb690bu, 0xf181eaa6u, 0xaae550d5u}, + {0xc2473fefu, 0xbf000000u, 0x6bdefc01u, 0x291e6ca0u}, + {0x3e720beau, 0x3f000000u, 0xac8cacf7u, 0x2feb67afu}, + {0xb8732b55u, 0x38281e0au, 0x185eb800u, 0x43a10696u}, + {0xbb96ece4u, 0xbe800000u, 0x64004248u, 0xa51d4257u}, + {0xbe800000u, 0xc2fc0000u, 0xc4725640u, 0x90e5ac8du}, + {0xc0200000u, 0xbf000000u, 0x95fe0d70u, 0x89f40abfu}, + {0x3df74341u, 0x7fc00000u, 0x3e007c01u, 0xc9717e02u}, + {0x3f000000u, 0xbc321c7bu, 0x26983800u, 0x7d3055d3u} +}; +constexpr std::uint32_t Expected[32][54] = { + {0x80000000u, 0x3f800000u, 0xbf800000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x3f7fffffu, 0x007fffffu, 0xffffff82u, 0xffffff82u, 0xbf7ffffeu, 0x3f7ffffeu, 0x80000000u, 0x00000000u, 0xff800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0xff800000u, 0xff800000u, 0x3f800000u, 0x3f800000u, 0x81c90fd3u, 0x01c90fd3u, 0x3f800000u, 0x3f800000u, 0x13573c00u, 0x13573c00u, 0x13570000u, 0x13573c00u, 0x13570000u, 0x13573c00u, 0x13570000u, 0x13573c00u, 0x13573800u, 0x13570000u, 0x135739a8u, 0x13573c00u, 0x13573da8u, 0x13573c00u, 0x13574000u, 0x13573c00u, 0x1357bc00u, 0x13570000u, 0x13573da8u, 0x13574000u, 0x13570000u, 0x13570000u, 0x1357bc00u, 0x13573c00u}, + {0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x00000000u, 0x00000000u, 0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x1234fe00u, 0x12347e01u, 0x1234fe00u, 0x12347e01u, 0x1234fe00u, 0x12347e01u, 0x1234fe00u, 0x12347e01u, 0x1234fe00u, 0x12347e01u, 0x1234fe00u, 0x12347e01u, 0x1234fe00u, 0x12347e01u, 0x1234fe00u, 0x12347e01u, 0x1234fe00u, 0x12347e01u, 0x1234fe00u, 0x12347e01u, 0x1234fe00u, 0x12347e01u, 0x1234fe00u, 0x12347e01u}, + {0xff800000u, 0x80000000u, 0xff800000u, 0x80000000u, 0xff800000u, 0x80000000u, 0xff800000u, 0x80000000u, 0xffc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff800000u, 0x80000000u, 0xffc00000u, 0x80000000u, 0xffc00000u, 0xff800000u, 0x80000000u, 0xff800000u, 0x80000000u, 0xff800000u, 0xffc00000u, 0xff800000u, 0x00000000u, 0x3f800000u, 0xffc00000u, 0x80000000u, 0xffc00000u, 0x3f800000u, 0x9abc8000u, 0x9abcfc00u, 0x9abc8000u, 0x9abcfc00u, 0x9abc8000u, 0x9abcfc00u, 0x9abc8000u, 0x9abcfc00u, 0x9abc0000u, 0x9abcfe00u, 0x9abc8000u, 0x9abcfe00u, 0x9abcfc00u, 0x9abcfe00u, 0x9abcfc00u, 0x9abc8000u, 0x9abcfc00u, 0x9abcfe00u, 0x9abc3c00u, 0x9abc0000u, 0x9abc8000u, 0x9abcfe00u, 0x9abc3c00u, 0x9abcfe00u}, + {0x80000000u, 0x3f800000u, 0xbf800000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x3f7fffffu, 0x00000000u, 0xffffffe3u, 0x00000001u, 0xbf000000u, 0x3f000000u, 0xffc00000u, 0x3f800000u, 0xffc00000u, 0x3f800000u, 0xce800000u, 0x3f800000u, 0xce800000u, 0x3f800000u, 0xffc00000u, 0x00000000u, 0x3f800000u, 0x40000000u, 0xb1c90fd5u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x55553c00u, 0x55558000u, 0x55550000u, 0x5555bc00u, 0x55550000u, 0x55558000u, 0x55550000u, 0x55558000u, 0x55553400u, 0x55553bffu, 0x55553800u, 0x5555fe00u, 0x55554000u, 0x5555fe00u, 0x55554400u, 0x5555f400u, 0x5555c000u, 0x5555fe00u, 0x55553cc2u, 0x55553c00u, 0x55553c00u, 0x55558e48u, 0x55550000u, 0x55553c00u}, + {0x7f800000u, 0x3f800000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0xffc00000u, 0x3e2b6c1au, 0x00000000u, 0xfffffffeu, 0x7f800000u, 0x3f2b6c1au, 0x7f800000u, 0x3ed17c3eu, 0x00000000u, 0x401c6be5u, 0x00000000u, 0x40bf2759u, 0x00000000u, 0x40bf2759u, 0x7f800000u, 0xc025079cu, 0x7f800000u, 0x3f8fbfa7u, 0xffc00000u, 0x3f5e4b27u, 0xffc00000u, 0x3efdf103u, 0x2de07c00u, 0x2de0bc00u, 0x2de07c00u, 0x2de0c000u, 0x2de07c00u, 0x2de0bc00u, 0x2de07c00u, 0x2de0c000u, 0x2de0fe00u, 0x2de03800u, 0x2de07c00u, 0x2de0fe00u, 0x2de00000u, 0x2de0fe00u, 0x2de00000u, 0x2de0b955u, 0x2de07c00u, 0x2de0fe00u, 0x2de07c00u, 0x2de035a8u, 0x2de0fe00u, 0x2de00000u, 0x2de0fe00u, 0x2de0bc00u}, + {0xbf800000u, 0x43000000u, 0xc0000000u, 0x43000000u, 0xbf800000u, 0x43000000u, 0xc0000000u, 0x43000000u, 0x3f000000u, 0x00000000u, 0x00000001u, 0x00000008u, 0xbf400000u, 0x3f000000u, 0xffc00000u, 0x413504f3u, 0xffc00000u, 0x3db504f3u, 0xbf2aaaaau, 0x3c000000u, 0xbf2aaaaau, 0x3c000000u, 0xffc00000u, 0x40e00000u, 0x3eb504f3u, 0x7f800000u, 0x00000000u, 0x00000000u, 0xbf800000u, 0x3f800000u, 0x72746401u, 0x72740000u, 0x72746401u, 0x72740000u, 0x72746401u, 0x72740000u, 0x72746401u, 0x72740000u, 0x72740000u, 0x72740000u, 0x72745000u, 0x72740000u, 0x727427ffu, 0x72747c00u, 0x727413feu, 0x72747c00u, 0x72744900u, 0x7274fc00u, 0x72747c00u, 0x72743c00u, 0x72740000u, 0x72740000u, 0x72743c00u, 0x72743c00u}, + {0xc2fc0000u, 0x7fc00000u, 0xc2fc0000u, 0x7fc00000u, 0xc2fc0000u, 0x7fc00000u, 0xc2fc0000u, 0x7fc00000u, 0x00000000u, 0x7fc00000u, 0x00000007u, 0x00000000u, 0xbf7c0000u, 0x7fc00000u, 0xffc00000u, 0x7fc00000u, 0xffc00000u, 0x7fc00000u, 0xbc020820u, 0x7fc00000u, 0xbc020820u, 0x7fc00000u, 0xffc00000u, 0x7fc00000u, 0x00800000u, 0x7fc00000u, 0x00000000u, 0x7fc00000u, 0x3f800000u, 0x7fc00000u, 0x94d3fe01u, 0x94d3c000u, 0x94d3fe01u, 0x94d3c200u, 0x94d3fe01u, 0x94d3c000u, 0x94d3fe01u, 0x94d3c000u, 0x94d3fe01u, 0x94d338fcu, 0x94d3fe01u, 0x94d3fe00u, 0x94d3fe01u, 0x94d3fe00u, 0x94d3fe01u, 0x94d3b6bbu, 0x94d3fe01u, 0x94d3fe00u, 0x94d3fe01u, 0x94d33229u, 0x94d3fe01u, 0x94d3b996u, 0x94d3fe01u, 0x94d3b9bau}, + {0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0x00000000u, 0x00000000u, 0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0xfb204800u, 0xfb20ebdbu, 0xfb204800u, 0xfb20ebdbu, 0xfb204800u, 0xfb20ebdbu, 0xfb204800u, 0xfb20ebdbu, 0xfb200000u, 0xfb200000u, 0xfb2041a8u, 0xfb20fe00u, 0xfb2035a8u, 0xfb20fe00u, 0xfb203000u, 0xfb208c13u, 0xfb204200u, 0xfb20fe00u, 0xfb205c00u, 0xfb200000u, 0xfb200000u, 0xfb200000u, 0xfb203c00u, 0xfb203c00u}, + {0xbf800000u, 0x3f800000u, 0xbf800000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0x00000000u, 0x3ab77039u, 0x00000001u, 0xfffffff7u, 0xbf000000u, 0x3f377039u, 0xffc00000u, 0x3d193b72u, 0xffc00000u, 0x41d5d86bu, 0xbf800000u, 0x4432a1d8u, 0xbf800000u, 0x4432a1d8u, 0xffc00000u, 0xc117b18fu, 0x3f000000u, 0x3f801fceu, 0x00000000u, 0x3c101202u, 0x3f800000u, 0x3f7ffd77u, 0xeb606400u, 0xeb607e00u, 0xeb6063feu, 0xeb607e00u, 0xeb6063feu, 0xeb607e00u, 0xeb606400u, 0xeb607e00u, 0xeb603800u, 0xeb607e00u, 0xeb604fffu, 0xeb607e00u, 0xeb602800u, 0xeb607e00u, 0xeb601401u, 0xeb607e00u, 0xeb604900u, 0xeb607e00u, 0xeb607c00u, 0xeb607e00u, 0xeb600000u, 0xeb607e00u, 0xeb60bc00u, 0xeb607e00u}, + {0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f400000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f400000u, 0x00000000u, 0x3f5db3d7u, 0x7f800000u, 0x3f93cd3au, 0x7f800000u, 0x3faaaaaau, 0x7f800000u, 0x3faaaaaau, 0xff800000u, 0xbed47fccu, 0x3f800000u, 0x3fd744fdu, 0x00000000u, 0xbf800000u, 0x3f800000u, 0x00000000u, 0x8a4e3c00u, 0x8a4e8000u, 0x8a4e0000u, 0x8a4ebc00u, 0x8a4e0000u, 0x8a4e8000u, 0x8a4e0000u, 0x8a4e8000u, 0x8a4e236cu, 0x8a4e3bf8u, 0x8a4e2fb5u, 0x8a4efe00u, 0x8a4e4827u, 0x8a4efe00u, 0x8a4e5450u, 0x8a4edc32u, 0x8a4ec61cu, 0x8a4efe00u, 0x8a4e3c0au, 0x8a4e3bfbu, 0x8a4e2dd2u, 0x8a4ea5feu, 0x8a4e3bf8u, 0x8a4e3bffu}, + {0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3dcccccdu, 0x3f400000u, 0xfffffffdu, 0x00000000u, 0x3f4ccccdu, 0x3f400000u, 0x3ea1e89bu, 0x3f5db3d7u, 0x404a62c2u, 0x3f93cd3au, 0x41200000u, 0x3faaaaaau, 0x41200000u, 0x3faaaaaau, 0xc0549a79u, 0xbed47fccu, 0x3f892fdfu, 0x3fd744fdu, 0x3f167919u, 0xbf800000u, 0x3f4f1bbdu, 0x00000000u, 0xe8a88000u, 0xe8a80000u, 0xe8a8bc00u, 0xe8a80000u, 0xe8a88000u, 0xe8a80000u, 0xe8a8bc00u, 0xe8a80000u, 0xe8a83400u, 0xe8a80000u, 0xe8a8fe00u, 0xe8a80000u, 0xe8a8fe00u, 0xe8a87c00u, 0xe8a8bd55u, 0xe8a87c00u, 0xe8a8fe00u, 0xe8a8fc00u, 0xe8a838c2u, 0xe8a83c00u, 0xe8a83c00u, 0xe8a80000u, 0xe8a80000u, 0xe8a83c00u}, + {0x7fe00000u, 0x40c00000u, 0x7fe00000u, 0x40a00000u, 0x7fe00000u, 0x40a00000u, 0x7fe00000u, 0x40c00000u, 0x7fe00000u, 0x3f2f4188u, 0x00000000u, 0x00000003u, 0x7fe00000u, 0x3f35e831u, 0x7fe00000u, 0x4018975du, 0x7fe00000u, 0x3ed6be5du, 0x7fe00000u, 0x3e3422d1u, 0x7fe00000u, 0x3e3422d1u, 0x7fe00000u, 0x402073a0u, 0x7fe00000u, 0x424dba58u, 0x7fe00000u, 0xbf6aaf52u, 0x7fe00000u, 0xbecc89e7u, 0xe7795570u, 0xe77954f0u, 0xe7795560u, 0xe77954e0u, 0xe7795560u, 0xe77954e0u, 0xe7795560u, 0xe77954f0u, 0xe7793700u, 0xe7793a80u, 0xe77948a6u, 0xe7794870u, 0xe7792ee2u, 0xe7792f36u, 0xe77921ecu, 0xe779227fu, 0xe779466fu, 0xe779464du, 0xe7797c00u, 0xe7797c00u, 0xe779361fu, 0xe779bb64u, 0xe779bb64u, 0xe779361fu}, + {0xbf800000u, 0x43000000u, 0xc0000000u, 0x43000000u, 0xbf800000u, 0x43000000u, 0xbf800000u, 0x43000000u, 0x3f370d08u, 0x00000000u, 0x00000001u, 0x00000008u, 0xbf24797cu, 0x3f000000u, 0xffc00000u, 0x413504f3u, 0xffc00000u, 0x3db504f3u, 0xbf473a78u, 0x3c000000u, 0xbf473a78u, 0x3c000000u, 0xffc00000u, 0x40e00000u, 0x3ed21db6u, 0x7f800000u, 0xbf79d980u, 0x00000000u, 0xbe5f1c7bu, 0x3f800000u, 0x6914c800u, 0x69143c00u, 0x6914c880u, 0x69140000u, 0x6914c800u, 0x69140000u, 0x6914c880u, 0x69140000u, 0x691434c0u, 0x69143800u, 0x6914fe00u, 0x691439a8u, 0x6914fe00u, 0x69143da8u, 0x6914af5bu, 0x69144000u, 0x6914fe00u, 0x6914bc00u, 0x691418eau, 0x69143da8u, 0x69143ba8u, 0x69140000u, 0x6914b4a5u, 0x6914bc00u}, + {0x40400000u, 0x80000000u, 0x40000000u, 0xbf800000u, 0x40000000u, 0x80000000u, 0x40400000u, 0x80000000u, 0x3f5ab250u, 0x3f7fbd2cu, 0x00000002u, 0xfffffff7u, 0x3f36ac94u, 0xbf05a805u, 0x3fd8404eu, 0xffc00000u, 0x3f17870au, 0xffc00000u, 0x3eb36129u, 0xc4752a85u, 0x3eb36129u, 0xc4752a85u, 0x3fc1ae32u, 0xffc00000u, 0x40e767f9u, 0x3f7fd1b2u, 0xbf4afbf1u, 0xbbd1f20bu, 0x3f1bfde3u, 0x3f7ffea7u, 0xec008000u, 0xec0053a0u, 0xec00bc00u, 0xec005380u, 0xec008000u, 0xec005380u, 0xec008000u, 0xec005380u, 0xec003bffu, 0xec003200u, 0xec00fe00u, 0xec0047c2u, 0xec00fe00u, 0xec003020u, 0xec00e5cfu, 0xec002441u, 0xec00fe00u, 0xec0045e9u, 0xec003bffu, 0xec007c00u, 0xec009c53u, 0xec003b64u, 0xec003c00u, 0xec00361fu}, + {0x80000000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x3f00186eu, 0x00000000u, 0xffffffffu, 0x00000000u, 0xbf7fcf24u, 0x00000000u, 0xffc00000u, 0x00000000u, 0xffc00000u, 0x7f800000u, 0xc0001873u, 0x7f800000u, 0xc0001873u, 0x7f800000u, 0xffc00000u, 0xff800000u, 0x3f3510edu, 0x3f800000u, 0xbb197f05u, 0x00000000u, 0xbf7fffd1u, 0x3f800000u, 0x686e3c00u, 0x686e8000u, 0x686e0000u, 0x686ebc00u, 0x686e0000u, 0x686e8000u, 0x686e0000u, 0x686e8000u, 0x686e1400u, 0x686e3bc1u, 0x686e2800u, 0x686efe00u, 0x686e5000u, 0x686efe00u, 0x686e6400u, 0x686ed00fu, 0x686ec900u, 0x686efe00u, 0x686e3c01u, 0x686e3bd5u, 0x686e1e48u, 0x686eb227u, 0x686e3c00u, 0x686e3bdau}, + {0x7fe00000u, 0x3f800000u, 0x7fe00000u, 0x3f800000u, 0x7fe00000u, 0x3f800000u, 0x7fe00000u, 0x3f800000u, 0x7fe00000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x7fe00000u, 0x3f000000u, 0x7fe00000u, 0x3f800000u, 0x7fe00000u, 0x3f800000u, 0x7fe00000u, 0x3f800000u, 0x7fe00000u, 0x3f800000u, 0x7fe00000u, 0x00000000u, 0x7fe00000u, 0x40000000u, 0x7fe00000u, 0x00000000u, 0x7fe00000u, 0x3f800000u, 0x75dd3c00u, 0x75ddbc00u, 0x75dd0000u, 0x75ddc000u, 0x75dd0000u, 0x75ddbc00u, 0x75dd0000u, 0x75ddbc00u, 0x75dd3570u, 0x75dd38feu, 0x75dd38aau, 0x75ddfe00u, 0x75dd3eddu, 0x75ddfe00u, 0x75dd41e3u, 0x75ddb9d0u, 0x75ddbe3au, 0x75ddfe00u, 0x75dd3d10u, 0x75dd362au, 0x75dd3ac2u, 0x75ddb99fu, 0x75ddb848u, 0x75ddb9b1u}, + {0x80000000u, 0xbf800000u, 0xbf800000u, 0xc0000000u, 0x80000000u, 0xbf800000u, 0x80000000u, 0xbf800000u, 0x3f7f5851u, 0x3f42e4e0u, 0xfffffff8u, 0x00000001u, 0xbf27aea0u, 0xbf1e8d90u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xc3c36ad8u, 0xbf4eab4au, 0xc3c36ad8u, 0xbf4eab4au, 0xffc00000u, 0xffc00000u, 0x3f7f8be0u, 0x3ed8f684u, 0xbc83b10eu, 0xbf7f5abeu, 0x3f7ff787u, 0x3d91591fu, 0x526c8000u, 0x526c3c00u, 0x526cbc00u, 0x526c0000u, 0x526c8000u, 0x526c0000u, 0x526cbc00u, 0x526c0000u, 0x526c1000u, 0x526c1a67u, 0x526cfe00u, 0x526c2b28u, 0x526cfe00u, 0x526c4c79u, 0x526cbc01u, 0x526c5d00u, 0x526cfe00u, 0x526cc829u, 0x526c3800u, 0x526c3c02u, 0x526c1a48u, 0x526c2507u, 0x526c3c00u, 0x526c3c00u}, + {0x80000000u, 0x80000000u, 0xbf800000u, 0xbf800000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x3f51f860u, 0x3f7dd711u, 0xfffffffeu, 0xfffffffau, 0xbf381e7fu, 0xbf0a3bbfu, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xc0b1f8c4u, 0xc2ed0c63u, 0xc0b1f8c4u, 0xc2ed0c63u, 0xffc00000u, 0xffc00000u, 0x3f6200b1u, 0x3f7e81dbu, 0xbf67804fu, 0xbd5908e3u, 0x3eda91e0u, 0x3f7fa3efu, 0xc979fe00u, 0xc9793c00u, 0xc979fe00u, 0xc9790000u, 0xc979fe00u, 0xc9790000u, 0xc979fe00u, 0xc9790000u, 0xc979fe00u, 0xc9792ce6u, 0xc979fe00u, 0xc979346du, 0xc979fe00u, 0xc979433bu, 0xc979fe00u, 0xc9794a88u, 0xc979fe00u, 0xc979c36au, 0xc979fe00u, 0xc9793c38u, 0xc979fe00u, 0xc9793767u, 0xc979fe00u, 0xc9793b18u}, + {0xc2fc0000u, 0x3f800000u, 0xc2fc0000u, 0x00000000u, 0xc2fc0000u, 0x00000000u, 0xc2fc0000u, 0x00000000u, 0x00000000u, 0x3cee8ea0u, 0x00000007u, 0xfffffffbu, 0xbf7c0000u, 0x3f6e8ea0u, 0xffc00000u, 0x3e2ebe5au, 0xffc00000u, 0x40bb853fu, 0xbc020820u, 0x42095bf0u, 0xbc020820u, 0x42095bf0u, 0xffc00000u, 0xc0a34205u, 0x00800000u, 0x3f829c24u, 0x00000000u, 0x3e3a5195u, 0x3f800000u, 0x3f7bba09u, 0x6bd88000u, 0x6bd85490u, 0x6bd8bc00u, 0x6bd85480u, 0x6bd88000u, 0x6bd85480u, 0x6bd88000u, 0x6bd85480u, 0x6bd83800u, 0x6bd83400u, 0x6bd8fe00u, 0x6bd84840u, 0x6bd8fe00u, 0x6bd82f88u, 0x6bd8c000u, 0x6bd82316u, 0x6bd8fe00u, 0x6bd8462du, 0x6bd839a8u, 0x6bd87c00u, 0x6bd80000u, 0x6bd83c00u, 0x6bd8bc00u, 0x6bd80000u}, + {0x42200000u, 0x3f800000u, 0x421c0000u, 0x00000000u, 0x421c0000u, 0x00000000u, 0x421c0000u, 0x00000000u, 0x3d868e00u, 0x3dcccccdu, 0x00000006u, 0xfffffffdu, 0x3f1c4347u, 0x3f4ccccdu, 0x40c80219u, 0x3ea1e89bu, 0x3e23d552u, 0x404a62c2u, 0x3cd1b2b1u, 0x41200000u, 0x3cd1b2b1u, 0x41200000u, 0x40a935e8u, 0xc0549a79u, 0x5305f6c3u, 0x3f892fdfu, 0x3ecd67efu, 0x3f167919u, 0x3f6a7ed2u, 0x3f4f1bbdu, 0xd2d03c00u, 0xd2d0f083u, 0xd2d00000u, 0xd2d0f083u, 0xd2d00000u, 0xd2d0f083u, 0xd2d00000u, 0xd2d0f083u, 0xd2d020f5u, 0xd2d00000u, 0xd2d02e4cu, 0xd2d0fe00u, 0xd2d04915u, 0xd2d0fe00u, 0xd2d05675u, 0xd2d08718u, 0xd2d0c6b1u, 0xd2d0fe00u, 0xd2d03c07u, 0xd2d00000u, 0xd2d02bc8u, 0xd2d00000u, 0xd2d03bfcu, 0xd2d03c00u}, + {0x40400000u, 0xbf800000u, 0x40000000u, 0xbf800000u, 0x40000000u, 0xbf800000u, 0x40000000u, 0xbf800000u, 0x3f000000u, 0x00000000u, 0x00000002u, 0x00000001u, 0x3f200000u, 0xbf000000u, 0x3fca62c2u, 0xffc00000u, 0x3f21e89bu, 0xffc00000u, 0x3ecccccdu, 0xbf800000u, 0x3ecccccdu, 0xbf800000u, 0x3fa934f0u, 0xffc00000u, 0x40b504f3u, 0x3f000000u, 0x00000000u, 0x00000000u, 0xbf800000u, 0x3f800000u, 0x7d703c00u, 0x7d704e40u, 0x7d703c00u, 0x7d704e00u, 0x7d703c00u, 0x7d704e00u, 0x7d703c00u, 0x7d704e00u, 0x7d700000u, 0x7d702800u, 0x7d703c00u, 0x7d7044e7u, 0x7d703c00u, 0x7d703287u, 0x7d703c00u, 0x7d702954u, 0x7d700000u, 0x7d704496u, 0x7d704000u, 0x7d707c00u, 0x7d700000u, 0x7d70323eu, 0x7d703c00u, 0x7d703bd9u}, + {0x3f800000u, 0x42d20000u, 0x00000000u, 0x42d00000u, 0x00000000u, 0x42d00000u, 0x00000000u, 0x42d00000u, 0x384fbc0eu, 0x3e203000u, 0xfffffff2u, 0x00000007u, 0x3f4fbc0eu, 0x3f505018u, 0x3be69ba6u, 0x41234a92u, 0x430e180cu, 0x3dc8ac0cu, 0x469dbd5fu, 0x3c1d4d46u, 0x469dbd5fu, 0x3c1d4d46u, 0xc164d28cu, 0x40d67bc4u, 0x3f800120u, 0x738ea8dcu, 0x39a32780u, 0x3f55050cu, 0x3f7fffffu, 0x3f0dfb22u, 0x75f98000u, 0x75f93c00u, 0x75f9bc00u, 0x75f90000u, 0x75f98000u, 0x75f90000u, 0x75f98000u, 0x75f93c00u, 0x75f93bffu, 0x75f93bffu, 0x75f9fe00u, 0x75f93bffu, 0x75f9fe00u, 0x75f93c00u, 0x75f9f400u, 0x75f93c01u, 0x75f9fe00u, 0x75f991c6u, 0x75f93c00u, 0x75f93fffu, 0x75f98e48u, 0x75f99a48u, 0x75f93c00u, 0x75f93c00u}, + {0xffc00000u, 0x80000000u, 0xffc00000u, 0xbf800000u, 0xffc00000u, 0x80000000u, 0xffc00000u, 0x80000000u, 0xffc00000u, 0x3f7ff193u, 0x00000000u, 0xfffffff4u, 0xffc00000u, 0xbf66cd34u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xc58df98au, 0xffc00000u, 0xc58df98au, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0x3f7ff600u, 0xffc00000u, 0xbab54564u, 0xffc00000u, 0x3f7fffefu, 0xefbd8000u, 0xefbd8000u, 0xefbdbc00u, 0xefbd8000u, 0xefbd8000u, 0xefbd8000u, 0xefbdbc00u, 0xefbd8000u, 0xefbd1000u, 0xefbd0000u, 0xefbdfe00u, 0xefbd8000u, 0xefbdfe00u, 0xefbdfc00u, 0xefbdbc01u, 0xefbdfc00u, 0xefbdfe00u, 0xefbdfc00u, 0xefbd3800u, 0xefbd3c00u, 0xefbd1a48u, 0xefbd8000u, 0xefbd3c00u, 0xefbd3c00u}, + {0x80000000u, 0x3f800000u, 0xbf800000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x3f7fffffu, 0x3abb690bu, 0xffffffe9u, 0xfffffff7u, 0xbf000000u, 0x3f3b690bu, 0xffc00000u, 0x3d1ae1ddu, 0xffc00000u, 0x41d3912fu, 0xcb800000u, 0x442ed8a7u, 0xcb800000u, 0x442ed8a7u, 0xffc00000u, 0xc11732f8u, 0x3f7fffffu, 0x3f80207eu, 0xb4c90fd5u, 0x3c133094u, 0x3f800000u, 0x3f7ffd5au, 0xaae5eaa6u, 0xaae5f181u, 0xaae5eaa6u, 0xaae5f181u, 0xaae5eaa6u, 0xaae5f181u, 0xaae5eaa6u, 0xaae5f181u, 0xaae50000u, 0xaae50000u, 0xaae5fe00u, 0xaae5fe00u, 0xaae5fe00u, 0xaae5fe00u, 0xaae58cd0u, 0xaae585d0u, 0xaae5fe00u, 0xaae5fe00u, 0xaae50000u, 0xaae50000u, 0xaae50000u, 0xaae50000u, 0xaae53c00u, 0xaae53c00u}, + {0xc2440000u, 0x80000000u, 0xc2480000u, 0xbf800000u, 0xc2440000u, 0x80000000u, 0xc2480000u, 0x80000000u, 0x3e401100u, 0x3f000000u, 0x00000006u, 0x00000000u, 0xbf473fefu, 0xbf000000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xbca474f9u, 0xc0000000u, 0xbca474f9u, 0xc0000000u, 0xffc00000u, 0xffc00000u, 0x2691c581u, 0x3f3504f3u, 0x3f6c8d95u, 0x00000000u, 0x3ec3bdbcu, 0xbf800000u, 0x291efe01u, 0x291e6bdeu, 0x291efe01u, 0x291e6bdeu, 0x291efe01u, 0x291e6bdeu, 0x291efe01u, 0x291e6bdeu, 0x291efe01u, 0x291e0000u, 0x291efe01u, 0x291e53efu, 0x291efe01u, 0x291e2409u, 0x291efe01u, 0x291e0c11u, 0x291efe01u, 0x291e49fdu, 0x291efe01u, 0x291e7c00u, 0x291efe01u, 0x291e0000u, 0x291efe01u, 0x291e3c00u}, + {0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3e720beau, 0x3f000000u, 0xfffffffeu, 0x00000000u, 0x3f720beau, 0x3f000000u, 0x3ef8ecefu, 0x3f3504f3u, 0x4003a345u, 0x3fb504f3u, 0x40876101u, 0x40000000u, 0x40876101u, 0x40000000u, 0xc0052ccdu, 0xbf800000u, 0x3f96c99du, 0x3fb504f3u, 0x3f7f0ff2u, 0x00000000u, 0x3daf2153u, 0xbf800000u, 0x2feb8000u, 0x2feb8000u, 0x2febbc00u, 0x2febbc00u, 0x2feb8000u, 0x2feb8000u, 0x2feb8000u, 0x2feb8000u, 0x2feb3b61u, 0x2feb3b6eu, 0x2febfe00u, 0x2febfe00u, 0x2febfe00u, 0x2febfe00u, 0x2febca72u, 0x2febcb0au, 0x2febfe00u, 0x2febfe00u, 0x2feb3b95u, 0x2feb3b9eu, 0x2febb77eu, 0x2febb6e8u, 0x2feb3b11u, 0x2feb3b37u}, + {0x80000000u, 0x3f800000u, 0xbf800000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x3f7ffc33u, 0x38281e0au, 0xfffffff2u, 0xfffffff2u, 0xbf732b55u, 0x3f281e0au, 0xffc00000u, 0x3bcf74c4u, 0xffc00000u, 0x431df394u, 0xc686c0feu, 0x46c2e957u, 0xc686c0feu, 0x46c2e957u, 0xffc00000u, 0xc169b4f2u, 0x3f7ffd5eu, 0x3f8000e9u, 0xb9befc10u, 0x398409fbu, 0x3f7ffffeu, 0x3f7fffffu, 0x43a18000u, 0x43a13c00u, 0x43a1bc00u, 0x43a10000u, 0x43a18000u, 0x43a10000u, 0x43a18000u, 0x43a10000u, 0x43a13800u, 0x43a1185eu, 0x43a1fe00u, 0x43a129e9u, 0x43a1fe00u, 0x43a14d6au, 0x43a1c000u, 0x43a15f54u, 0x43a1fe00u, 0x43a1c870u, 0x43a139a8u, 0x43a13c02u, 0x43a10000u, 0x43a122dcu, 0x43a1bc00u, 0x43a13c00u}, + {0x80000000u, 0x80000000u, 0xbf800000u, 0xbf800000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x3f7ed226u, 0x3f400000u, 0xfffffff9u, 0xffffffffu, 0xbf16ece4u, 0xbf000000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xc3591d2cu, 0xc0800000u, 0xc3591d2cu, 0xc0800000u, 0xffc00000u, 0xffc00000u, 0x3f7f2f1bu, 0x3f5744fdu, 0xbced0a3eu, 0xbf800000u, 0x3f7fe48eu, 0x00000000u, 0xa51d4400u, 0xa51d6400u, 0xa51d4200u, 0xa51d6400u, 0xa51d4200u, 0xa51d6400u, 0xa51d4200u, 0xa51d6400u, 0xa51d3080u, 0xa51d0000u, 0xa51d3f17u, 0xa51d5000u, 0xa51d3884u, 0xa51d2800u, 0xa51d3518u, 0xa51d1400u, 0xa51d3e9bu, 0xa51d4900u, 0xa51d4869u, 0xa51d7c00u, 0xa51d3a2fu, 0xa51d0000u, 0xa51d3913u, 0xa51d3c00u}, + {0x80000000u, 0xc2fc0000u, 0xbf800000u, 0xc2fc0000u, 0x80000000u, 0xc2fc0000u, 0x80000000u, 0xc2fc0000u, 0x3f400000u, 0x00000000u, 0xffffffffu, 0x00000007u, 0xbf000000u, 0xbf7c0000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xc0800000u, 0xbc020820u, 0xc0800000u, 0xbc020820u, 0xffc00000u, 0xffc00000u, 0x3f5744fdu, 0x00800000u, 0xbf800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x90e55640u, 0x90e5c400u, 0x90e55640u, 0x90e5c500u, 0x90e55640u, 0x90e5c400u, 0x90e55640u, 0x90e5c400u, 0x90e50000u, 0x90e53870u, 0x90e54900u, 0x90e5fe00u, 0x90e52e66u, 0x90e5fe00u, 0x90e5211fu, 0x90e5b333u, 0x90e546a5u, 0x90e5fe00u, 0x90e57c00u, 0x90e529e0u, 0x90e50000u, 0x90e5b564u, 0x90e53c00u, 0x90e5bb88u}, + {0xc0000000u, 0x80000000u, 0xc0400000u, 0xbf800000u, 0xc0000000u, 0x80000000u, 0xc0000000u, 0x80000000u, 0x3f000000u, 0x3f000000u, 0x00000002u, 0x00000000u, 0xbf200000u, 0xbf000000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xbecccccdu, 0xc0000000u, 0xbecccccdu, 0xc0000000u, 0xffc00000u, 0xffc00000u, 0x3e3504f3u, 0x3f3504f3u, 0x00000000u, 0x00000000u, 0xbf800000u, 0xbf800000u, 0x89f43c00u, 0x89f48000u, 0x89f40000u, 0x89f4bc00u, 0x89f40000u, 0x89f48000u, 0x89f40000u, 0x89f48000u, 0x89f40d70u, 0x89f43bfdu, 0x89f424aau, 0x89f4fe00u, 0x89f452ddu, 0x89f4fe00u, 0x89f469e3u, 0x89f4e157u, 0x89f4c9c7u, 0x89f4fe00u, 0x89f43c00u, 0x89f43bfeu, 0x89f41845u, 0x89f4a0b5u, 0x89f43c00u, 0x89f43c00u}, + {0x3f800000u, 0x7fc00000u, 0x00000000u, 0x7fc00000u, 0x00000000u, 0x7fc00000u, 0x00000000u, 0x7fc00000u, 0x3df74341u, 0x7fc00000u, 0xfffffffdu, 0x00000000u, 0x3f774341u, 0x7fc00000u, 0x3eb1e746u, 0x7fc00000u, 0x40383098u, 0x7fc00000u, 0x410485e4u, 0x7fc00000u, 0x410485e4u, 0x7fc00000u, 0xc04334d3u, 0x7fc00000u, 0x3f8b2c3eu, 0x7fc00000u, 0x3f301a40u, 0x7fc00000u, 0x3f39ce5au, 0x7fc00000u, 0xc9717e01u, 0xc9714000u, 0xc9717e01u, 0xc9713c00u, 0xc9717e01u, 0xc9713c00u, 0xc9717e01u, 0xc9714000u, 0xc9717e01u, 0xc9713800u, 0xc9717e01u, 0xc9713ce6u, 0xc9717e01u, 0xc9713a88u, 0xc9717e01u, 0xc9713955u, 0xc9717e01u, 0xc97138aeu, 0xc9717e01u, 0xc97141a8u, 0xc9717e01u, 0xc9710000u, 0xc9717e01u, 0xc971bc00u}, + {0x3f800000u, 0x80000000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x3f000000u, 0x3f7d378eu, 0x00000000u, 0xfffffffau, 0x3f000000u, 0xbf321c7bu, 0x3f3504f3u, 0xffc00000u, 0x3fb504f3u, 0xffc00000u, 0x40000000u, 0xc2b7f993u, 0x40000000u, 0xc2b7f993u, 0xbf800000u, 0xffc00000u, 0x3fb504f3u, 0x3f7e1407u, 0x00000000u, 0xbd8bc78bu, 0xbf800000u, 0x3f7f672du, 0x7d303c00u, 0x7d303c00u, 0x7d300000u, 0x7d300000u, 0x7d300000u, 0x7d300000u, 0x7d300000u, 0x7d300000u, 0x7d303800u, 0x7d302698u, 0x7d3039a8u, 0x7d303123u, 0x7d303da8u, 0x7d30463bu, 0x7d304000u, 0x7d3050dau, 0x7d30bc00u, 0x7d30c547u, 0x7d303da8u, 0x7d303c12u, 0x7d300000u, 0x7d303128u, 0x7d30bc00u, 0x7d303be5u} +}; +constexpr const char* Names[54] = {"ceil_f32 v4", "ceil_f32 v5", "floor_f32 v4", "floor_f32 v5", "trunc_f32 v4", "trunc_f32 v5", "rndne_f32 v4", "rndne_f32 v5", "fract_f32 v4", "fract_f32 v5", "frexp_exp_i32_f32 v4", "frexp_exp_i32_f32 v5", "frexp_mant_f32 v4", "frexp_mant_f32 v5", "sqrt_f32 v4", "sqrt_f32 v5", "rsq_f32 v4", "rsq_f32 v5", "rcp_f32 v4", "rcp_f32 v5", "rcp_iflag_f32 v4", "rcp_iflag_f32 v5", "log_f32 v4", "log_f32 v5", "exp_f32 v4", "exp_f32 v5", "sin_f32 v4", "sin_f32 v5", "cos_f32 v4", "cos_f32 v5", "ceil_f16 v6", "ceil_f16 v8", "floor_f16 v6", "floor_f16 v8", "trunc_f16 v6", "trunc_f16 v8", "rndne_f16 v6", "rndne_f16 v8", "fract_f16 v6", "fract_f16 v8", "sqrt_f16 v6", "sqrt_f16 v8", "rsq_f16 v6", "rsq_f16 v8", "rcp_f16 v6", "rcp_f16 v8", "log_f16 v6", "log_f16 v8", "exp_f16 v6", "exp_f16 v8", "sin_f16 v6", "sin_f16 v8", "cos_f16 v6", "cos_f16 v8"}; +constexpr std::uint64_t Checked[32] = {0x003fffffc3ffc000ull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull}; +constexpr std::uint32_t Tolerance[54] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::string Hex(std::uint32_t value); +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name); + +double Half(std::uint32_t bits) { + const auto exponent = static_cast((bits >> 10u) & 0x1fu); + const double magnitude = exponent == 0x1f ? ((bits & 0x3ffu) ? NAN : INFINITY) : std::ldexp(static_cast((bits & 0x3ffu) | (exponent ? 0x400u : 0u)), (exponent ? exponent : 1) - 25); + return (bits & 0x8000u) ? -magnitude : magnitude; +} + +bool Finite(double value) { + return std::isfinite(value) && value != 0.0; +} + +void ExpectNear(std::uint32_t tid, std::uint32_t index, std::uint32_t actual, std::uint32_t expected) { + const bool half = Tolerance[index] >= 5u; + const double a = half ? Half(actual & 0xffffu) : std::bit_cast(actual); + const double e = half ? Half(expected & 0xffffu) : std::bit_cast(expected); + if (!Finite(e) || (half && (actual >> 16u) != (expected >> 16u))) { + Expect(tid, actual, expected, Names[index]); + return; + } + const double x = half ? Half((Rows[tid][2] >> (index % 2u ? 16u : 0u)) & 0xffffu) : std::bit_cast(Rows[tid][index % 2u]); + const double error = std::abs(a - e); + const double relative = error / std::abs(e); + bool near = false; + switch (Tolerance[index]) { + case 1u: near = relative <= 0x1p-20; break; + case 2u: near = error <= 0x1p-21 || relative <= 0x1p-20; break; + case 3u: near = relative <= (4.0 + 2.0 * std::abs(x)) * 0x1p-23; break; + case 4u: near = error <= 0x1p-11; break; + case 5u: near = relative <= 0x1p-9; break; + default: near = error <= 0x1p-10; break; + } + Require(near, std::string("vop1 float unary: lane ") + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected near " + Hex(expected)); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop1 float unary: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 54; ++i) { + if (((Checked[tid] >> i) & 1u) == 0u) continue; + if (Tolerance[i] == 0u) Expect(tid, out[i], Expected[tid][i], Names[i]); + else ExpectNear(tid, i, out[i], Expected[tid][i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop1 float unary tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index 6f0a1dea9..a81980a90 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -4,6 +4,7 @@ #include "Translation/InstructionTranslator.hpp" #include #include +#include namespace ShaderRecompiler { @@ -150,8 +151,8 @@ private: void vCvtPkU8F32(const RdnaInstruction& inst); void vPackB32F16(const RdnaInstruction& inst); bool packedFloat16(const RdnaInstruction& inst, IrOpcode opcode, bool accumulator, bool quietSnan); - bool float16Unary(const RdnaInstruction& inst, IrOpcode opcode, bool invalidNegative); - bool float16Trig(const RdnaInstruction& inst, IrOpcode opcode); + bool float16Unary(const RdnaInstruction& inst, IrOpcode opcode); + std::pair unaryFloatSpecials(IrOpcode opcode, IrF32 argument, IrF32 result); bool vDivFixupF16(const RdnaInstruction& inst); bool float16Binary(const RdnaInstruction& inst, IrOpcode opcode, bool reverse); bool float16Ternary(const RdnaInstruction& inst, IrOpcode opcode, bool accumulator, bool mix); diff --git a/core/shader/recompiler/Translation/src/FloatInstructions.cpp b/core/shader/recompiler/Translation/src/FloatInstructions.cpp index 76f628728..6d79de99e 100644 --- a/core/shader/recompiler/Translation/src/FloatInstructions.cpp +++ b/core/shader/recompiler/Translation/src/FloatInstructions.cpp @@ -48,21 +48,92 @@ bool TranslationContext::packedFloat16(const RdnaInstruction& inst, IrOpcode opc return true; } -bool TranslationContext::float16Unary(const RdnaInstruction& inst, IrOpcode opcode, bool invalidNegative) { - const IrF32 argument = readF16AsF32(sourceAt(inst, 0u)); - const IrF32 result(ir.Emit(opcode, IrType::F32, {&argument.Value()})); - if (!invalidNegative) { - writeF16(inst.destination, result); - return true; +bool TranslationContext::float16Unary(const RdnaInstruction& inst, IrOpcode opcode) { + const RdnaOperand& operand = sourceAt(inst, 0u); + const IrF32 argument = readF16AsF32(operand); + const auto [result, invalid] = unaryFloatSpecials(opcode, argument, IrF32(ir.Emit(opcode, IrType::F32, {&argument.Value()}))); + IrU32 bits = packHalf2x16(applyF16ResultModifiers(inst.destination, result), IrF32(ir.ConstantF32(0.0f))); + if (opcode == IrOpcode::FPFract32) { + bits = IrU32(ir.Select(ir.IEqual(bits.Value(), ir.Constant(0x3c00u)), ir.Constant(0x3bffu), bits.Value())); } - const IrU1 negative(ir.Emit(IrOpcode::FPOrdLessThan32, IrType::U1, {&argument.Value(), &ir.ConstantF32(0.0f)})); - const IrF32 modified = applyF16ResultModifiers(inst.destination, result); - const IrU32 bits = packHalf2x16(modified, IrF32(ir.ConstantF32(0.0f))); - const IrU32 invalid(ir.Constant(inst.destination.clamp ? 0u : 0xfe00u)); - write16Bits(inst.destination, IrU32(ir.Select(negative.Value(), invalid.Value(), bits.Value()))); + const IrU32 half = readF16Bits(operand); + const IrU1 nan(ir.UGreaterThan(ir.BitwiseAnd(half.Value(), ir.Constant(0x7fffu)), ir.Constant(0x7c00u))); + const IrU32 special(ir.Select(nan.Value(), ir.BitwiseOr(half.Value(), ir.Constant(0x0200u)), ir.Constant(0xfe00u))); + const IrU32 value(ir.Select(ir.LogicalOr(nan.Value(), invalid.Value()), inst.destination.clamp ? ir.Constant(0u) : special.Value(), bits.Value())); + write16Bits(inst.destination, value); return true; } +std::pair TranslationContext::unaryFloatSpecials(IrOpcode opcode, IrF32 argument, IrF32 result) { + const IrU32 bits(ir.BitCastU32(argument.Value())); + const IrU32 magnitude(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffffffu))); + const IrU32 sign(ir.BitwiseAnd(bits.Value(), ir.Constant(0x80000000u))); + const bool flushes = opcode == IrOpcode::FPRecip32 || opcode == IrOpcode::FPRecipIFlag32 || opcode == IrOpcode::FPRecipSqrt32 || opcode == IrOpcode::FPSqrt || opcode == IrOpcode::FPLog2 || opcode == IrOpcode::FPExp2; + const IrU1 zero(flushes ? ir.ULessThan(magnitude.Value(), ir.Constant(0x00800000u)) : ir.IEqual(magnitude.Value(), ir.Constant(0u))); + const IrU1 infinite(ir.IEqual(magnitude.Value(), ir.Constant(0x7f800000u))); + const IrU1 negative(ir.LogicalAnd(ir.INotEqual(sign.Value(), ir.Constant(0u)), ir.LogicalNot(zero.Value()))); + const IrU32 signedInfinity(ir.BitwiseOr(sign.Value(), ir.Constant(0x7f800000u))); + const auto pick = [&](IrU1 condition, IrValue& value, IrU32 current) { return IrU32(ir.Select(condition.Value(), value, current.Value())); }; + IrU32 value(ir.BitCastU32(result.Value())); + IrU1 invalid(ir.ConstantBool(false)); + switch (opcode) { + case IrOpcode::FPRecip32: + case IrOpcode::FPRecipIFlag32: + value = pick(infinite, sign.Value(), value); + value = pick(zero, signedInfinity.Value(), value); + break; + case IrOpcode::FPRecipSqrt32: + value = pick(infinite, ir.Constant(0u), value); + value = pick(zero, signedInfinity.Value(), value); + invalid = negative; + break; + case IrOpcode::FPSqrt: + value = pick(infinite, bits.Value(), value); + value = pick(zero, sign.Value(), value); + invalid = negative; + break; + case IrOpcode::FPLog2: + value = pick(infinite, bits.Value(), value); + value = pick(IrU1(ir.IEqual(bits.Value(), ir.Constant(0x3f800000u))), ir.Constant(0u), value); + value = pick(zero, ir.Constant(0xff800000u), value); + invalid = negative; + break; + case IrOpcode::FPExp2: { + const IrU1 overflow(ir.LogicalAnd(ir.IEqual(sign.Value(), ir.Constant(0u)), ir.UGreaterThan(magnitude.Value(), ir.Constant(0x42ffffffu)))); + const IrU1 underflow(ir.LogicalAnd(negative.Value(), ir.UGreaterThan(magnitude.Value(), ir.Constant(0x42fc0000u)))); + value = pick(overflow, ir.Constant(0x7f800000u), value); + value = pick(underflow, ir.Constant(0u), value); + value = pick(zero, ir.Constant(0x3f800000u), value); + break; + } + case IrOpcode::FPFract32: { + const IrU32 unsignedValue(ir.BitwiseAnd(value.Value(), ir.Constant(0x7fffffffu))); + value = pick(IrU1(ir.UGreaterThan(unsignedValue.Value(), ir.Constant(0x3f7fffffu))), ir.Constant(0x3f7fffffu), unsignedValue); + invalid = infinite; + break; + } + case IrOpcode::FPSin: + case IrOpcode::FPCos: { + const IrF32 whole(ir.Emit(IrOpcode::FPTrunc32, IrType::F32, {&argument.Value()})); + const IrF32 difference(ir.Emit(IrOpcode::FPSub32, IrType::F32, {&argument.Value(), &whole.Value()})); + const IrF32 fraction(ir.Emit(IrOpcode::FPAbs32, IrType::F32, {&difference.Value()})); + const auto fractionIs = [&](float cycle) { return IrU1(ir.Emit(IrOpcode::FPOrdEqual32, IrType::U1, {&fraction.Value(), &ir.ConstantF32(cycle)})); }; + if (opcode == IrOpcode::FPSin) { + const IrU1 cardinal(ir.LogicalOr(fractionIs(0.0f).Value(), fractionIs(0.5f).Value())); + value = pick(cardinal, ir.Constant(0u), value); + value = pick(zero, bits.Value(), value); + } else { + value = pick(IrU1(ir.LogicalOr(fractionIs(0.25f).Value(), fractionIs(0.75f).Value())), ir.Constant(0u), value); + } + invalid = infinite; + break; + } + default: + break; + } + return {IrF32(ir.BitCastF32(value.Value())), invalid}; +} + bool TranslationContext::vDivFixupF16(const RdnaInstruction& inst) { std::array bits{}; std::array magnitude{}; @@ -95,33 +166,6 @@ bool TranslationContext::vDivFixupF16(const RdnaInstruction& inst) { return true; } -bool TranslationContext::float16Trig(const RdnaInstruction& inst, IrOpcode opcode) { - const IrF32 argument = readF16AsF32(sourceAt(inst, 0u)); - const IrU32 magnitude(ir.BitwiseAnd(ir.BitCastU32(argument.Value()), ir.Constant(0x7fffffffu))); - const IrF32 raw(ir.Emit(opcode, IrType::F32, {&argument.Value()})); - const IrF32 fraction(ir.Emit(IrOpcode::FPFract32, IrType::F32, {&argument.Value()})); - const IrF32 zero(ir.ConstantF32(0.0f)); - IrF32 result = raw; - if (opcode == IrOpcode::FPSin) { - const IrU1 whole(ir.Emit(IrOpcode::FPOrdEqual32, IrType::U1, {&fraction.Value(), &zero.Value()})); - const IrU1 half(ir.Emit(IrOpcode::FPOrdEqual32, IrType::U1, {&fraction.Value(), &ir.ConstantF32(0.5f)})); - const IrU1 nonzero(ir.INotEqual(magnitude.Value(), ir.Constant(0u))); - const IrU1 cardinal(ir.LogicalOr(ir.LogicalAnd(whole.Value(), nonzero.Value()), half.Value())); - result = selectF32(cardinal, zero, raw); - } else { - const IrU1 quarter(ir.Emit(IrOpcode::FPOrdEqual32, IrType::U1, {&fraction.Value(), &ir.ConstantF32(0.25f)})); - const IrU1 threeQuarters(ir.Emit(IrOpcode::FPOrdEqual32, IrType::U1, {&fraction.Value(), &ir.ConstantF32(0.75f)})); - const IrU1 quarterCycle(ir.LogicalOr(quarter.Value(), threeQuarters.Value())); - result = selectF32(quarterCycle, zero, raw); - } - const IrU1 infinite(ir.IEqual(magnitude.Value(), ir.Constant(0x7f800000u))); - const IrF32 modified = applyF16ResultModifiers(inst.destination, result); - const IrU32 bits = packHalf2x16(modified, zero); - const IrU32 invalid(ir.Constant(inst.destination.clamp ? 0u : 0xfe00u)); - write16Bits(inst.destination, IrU32(ir.Select(infinite.Value(), invalid.Value(), bits.Value()))); - return true; -} - bool TranslationContext::float16Binary(const RdnaInstruction& inst, IrOpcode opcode, bool reverse) { const IrF32 lhs = readF16AsF32(sourceAt(inst, reverse ? 1u : 0u)); const IrF32 rhs = readF16AsF32(sourceAt(inst, reverse ? 0u : 1u)); @@ -365,9 +409,13 @@ bool TranslationContext::vDivFixupF32(const RdnaInstruction& inst) { } bool TranslationContext::floatUnary(const RdnaInstruction& inst, IrOpcode opcode) { - IrValue* argument = readOperand(sourceAt(inst, 0u), IrOpcodeArgumentType(opcode, 0u)); - IrValue& result = ir.Emit(opcode, IrOpcodeType(opcode), {argument}); - writeOperand(inst.destination, &result); + const IrU32 bits = readU32(sourceAt(inst, 0u)); + const IrF32 argument(ir.BitCastF32(bits.Value())); + const auto [result, invalid] = unaryFloatSpecials(opcode, argument, IrF32(ir.Emit(opcode, IrType::F32, {&argument.Value()}))); + const IrU1 nan(ir.UGreaterThan(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffffffu)), ir.Constant(0x7f800000u))); + const IrU32 special(ir.Select(nan.Value(), ir.BitwiseOr(bits.Value(), ir.Constant(0x00400000u)), ir.Constant(0xffc00000u))); + const IrF32 value(ir.Select(ir.LogicalOr(nan.Value(), invalid.Value()), ir.BitCastF32(special.Value()), result.Value())); + writeOperand(inst.destination, &value.Value()); return true; } @@ -410,7 +458,8 @@ bool TranslationContext::vFrexpMantF32(const RdnaInstruction& inst) { const IrU32 zeroOrSubnormal(ir.Select(zero.Value(), bits.Value(), subnormal.Value())); const IrU32 finite(ir.Select(exponentNonZero.Value(), normal.Value(), zeroOrSubnormal.Value())); const IrU1 exponentAllOnes(ir.IEqual(exponent.Value(), ir.Constant(0xffu))); - const IrU32 result(ir.Select(exponentAllOnes.Value(), bits.Value(), finite.Value())); + const IrU32 special(ir.Select(zero.Value(), bits.Value(), ir.BitwiseOr(bits.Value(), ir.Constant(0x00400000u)))); + const IrU32 result(ir.Select(exponentAllOnes.Value(), special.Value(), finite.Value())); writeOperand(inst.destination, &ir.BitCastF32(result.Value())); return true; } diff --git a/core/shader/recompiler/Translation/src/VectorInstructions.cpp b/core/shader/recompiler/Translation/src/VectorInstructions.cpp index 75ba73621..a34d2e0d9 100644 --- a/core/shader/recompiler/Translation/src/VectorInstructions.cpp +++ b/core/shader/recompiler/Translation/src/VectorInstructions.cpp @@ -816,29 +816,29 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { case RdnaOpcode::VMadMixhiF16: return float16Ternary(inst, IrOpcode::FPFma32, false, true); case RdnaOpcode::VRcpF16: - return float16Unary(inst, IrOpcode::FPRecip32, false); + return float16Unary(inst, IrOpcode::FPRecip32); case RdnaOpcode::VSqrtF16: - return float16Unary(inst, IrOpcode::FPSqrt, true); + return float16Unary(inst, IrOpcode::FPSqrt); case RdnaOpcode::VRsqF16: - return float16Unary(inst, IrOpcode::FPRecipSqrt32, true); + return float16Unary(inst, IrOpcode::FPRecipSqrt32); case RdnaOpcode::VLogF16: - return float16Unary(inst, IrOpcode::FPLog2, true); + return float16Unary(inst, IrOpcode::FPLog2); case RdnaOpcode::VExpF16: - return float16Unary(inst, IrOpcode::FPExp2, false); + return float16Unary(inst, IrOpcode::FPExp2); case RdnaOpcode::VFloorF16: - return float16Unary(inst, IrOpcode::FPFloor32, false); + return float16Unary(inst, IrOpcode::FPFloor32); case RdnaOpcode::VCeilF16: - return float16Unary(inst, IrOpcode::FPCeil32, false); + return float16Unary(inst, IrOpcode::FPCeil32); case RdnaOpcode::VTruncF16: - return float16Unary(inst, IrOpcode::FPTrunc32, false); + return float16Unary(inst, IrOpcode::FPTrunc32); case RdnaOpcode::VRndneF16: - return float16Unary(inst, IrOpcode::FPRoundEven32, false); + return float16Unary(inst, IrOpcode::FPRoundEven32); case RdnaOpcode::VFractF16: - return float16Unary(inst, IrOpcode::FPFract32, false); + return float16Unary(inst, IrOpcode::FPFract32); case RdnaOpcode::VSinF16: - return float16Trig(inst, IrOpcode::FPSin); + return float16Unary(inst, IrOpcode::FPSin); case RdnaOpcode::VCosF16: - return float16Trig(inst, IrOpcode::FPCos); + return float16Unary(inst, IrOpcode::FPCos); case RdnaOpcode::VMin3F16: return float16Ternary(inst, IrOpcode::FPMinTri32, false, false); case RdnaOpcode::VMax3F16: diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index da31aebc2..5e7471b7c 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -77,6 +77,7 @@ Throughout the project, every function at every stage either **does exactly what - [v_fma_f64](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp) is fused exactly only for normal inputs: with a subnormal input it is a separate multiply and add, which rounds twice. - The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability. - [libSceJpegEnc](../../core/libs/prx/libSceJpegEnc/Export.cpp) encodes 4:2:2 sampling requests with 4:2:0 chroma subsampling, and grayscale requests as a 3-component JPEG with neutral chroma instead of a single-component one: the [JPEG encoder](../../core/Decoder/Jpeg/src/Jpeg.cpp) (stb) only produces 3-component 4:2:0 and 4:4:4 images. +- [v_rcp, v_rsq, v_sqrt, v_log, v_exp, v_sin and v_cos](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (f32 and f16) use the driver's approximation for finite results, not the console's: the low bits can differ. Only NaN, invalid, zero and infinite results and the exact sin/cos zeros are chosen on the bits. v_sin_f32/v_cos_f32 with inputs beyond ±256 cycles were not compared with the console. - `--to-intel` does not lower RDPRU/MCOMMIT; the [matcher](../../core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp) fails the relink instead. SHA-1 and SHA-256 instructions with a RIP-relative operand fail the relink. - The length-changing path of the [instruction rewriter](../../core/relinker/codegen/src/x86/X64InstructionRewriter.cpp) is not used by the [converter](../../core/relinker/codegen/src/Amd64OnlyConverter.cpp): it does not adjust VEX/0F38/0F3A RIP-relative operands, data-to-code references (relocations, FDEs, jump tables) or segment sizes, so every substitution keeps the instruction length. - The Linux placement of the `--to-intel` stubs in the [ELF patcher](../../core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp) is covered only by a synthetic test. From e18d4292a287116f3c7d70309feb42ed24682b4a Mon Sep 17 00:00:00 2001 From: Michon <231849@buas.nl> Date: Fri, 2 Oct 2026 19:52:08 +0200 Subject: [PATCH 057/164] fix(shader): decode image_sample_cd as a derivative sample image_sample_cd takes its derivatives in the image_sample_d address layout; only the hardware LOD selection differs (one LOD per quad instead of per pixel). The decoder refused it. It now samples with the given gradients per lane, which matches whenever the lanes of a quad pass equal derivatives. Co-Authored-By: Claude Opus 5.5 (1M context) --- core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp index a019dd480..54ff7e8e2 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp @@ -127,9 +127,6 @@ void validateFlags(std::uint32_t flags) { if ((flags & ~known) != 0u) { throw std::runtime_error("unknown image address flags"); } - if ((flags & RdnaImageSampleFlagCd) != 0u) { - throw std::runtime_error("unsupported image coarse derivative (_cd) address layout"); - } const auto lodModes = flags & (RdnaImageSampleFlagLod | RdnaImageSampleFlagBias | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLevelZero); if (std::popcount(lodModes) > 1) { throw std::runtime_error("conflicting image LOD modes"); From 6138494928d874c2b26993b078301b855696ca1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sat, 3 Oct 2026 20:03:55 +0200 Subject: [PATCH 058/164] feat(shader): implement image_sample_cd/_g16 variants and fix a16 gradient layout Co-Authored-By: Claude --- .../execution/ImageSampleDerivatives.cpp | 323 ++++++++++++++++++ .../include/RdnaDecoder/RdnaInstruction.hpp | 4 + .../include/RdnaDecoder/RdnaOpcode.hpp | 24 ++ .../RdnaDecoder/src/RdnaImageOpDecoder.cpp | 77 ++++- .../recompiler/RdnaDecoder/src/RdnaOpcode.cpp | 24 ++ .../MemoryInstructionDispatch.cpp | 24 ++ .../MemoryResourceHelpers.cpp | 17 + 7 files changed, 476 insertions(+), 17 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp new file mode 100644 index 000000000..5f2d50d3a --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp @@ -0,0 +1,323 @@ +#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 +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 12; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Width = 64; +constexpr std::uint32_t Height = 4; +constexpr std::uint32_t Depth = 2; +constexpr std::uint32_t Format32Float = 22; + +alignas(256) constexpr std::array Input{ + 0x3ce00000, 0x3e000000, 0x3e800000, 0x00000000, 0x3f800000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3d300000, 0x3ec00000, 0x3e800000, 0x00000000, + 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x00000000, + 0x3d700000, 0x3f200000, 0x3e800000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, + 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x3d980000, 0x3f600000, 0x3e800000, 0x00000000, + 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x00000000, + 0x3db80000, 0x3e000000, 0x3f400000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x3a800000, 0x3a800000, 0x00000000, 0x3dd80000, 0x3ec00000, 0x3f400000, 0x00000000, + 0x3f800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3df80000, 0x3f200000, 0x3f400000, 0x00000000, 0x00000000, 0x3a800000, 0x3a800000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3e0c0000, 0x3f600000, 0x3f400000, 0x00000000, + 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, + 0x3e1c0000, 0x3e000000, 0x3e800000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, + 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3e2c0000, 0x3ec00000, 0x3e800000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x3a800000, 0x00000000, + 0x3e3c0000, 0x3f200000, 0x3e800000, 0x00000000, 0x3f800000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3e4c0000, 0x3f600000, 0x3e800000, 0x00000000, + 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3e5c0000, 0x3e000000, 0x3f400000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3a800000, 0x3a800000, 0x00000000, 0x00000000, 0x3e6c0000, 0x3ec00000, 0x3f400000, 0x00000000, + 0x3a800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, + 0x3e7c0000, 0x3f200000, 0x3f400000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, + 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x3e860000, 0x3f600000, 0x3f400000, 0x00000000, + 0x3f800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3e8e0000, 0x3e000000, 0x3e800000, 0x00000000, 0x00000000, 0x3a800000, 0x3a800000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3e960000, 0x3ec00000, 0x3e800000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3f800000, 0x00000000, 0x00000000, 0x00000000, + 0x3e9e0000, 0x3f200000, 0x3e800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x3a800000, 0x3a800000, 0x00000000, 0x3ea60000, 0x3f600000, 0x3e800000, 0x00000000, + 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, + 0x3eae0000, 0x3e000000, 0x3f400000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, + 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3eb60000, 0x3ec00000, 0x3f400000, 0x00000000, + 0x3a800000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3ebe0000, 0x3f200000, 0x3f400000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3f800000, 0x00000000, 0x00000000, 0x00000000, 0x3ec60000, 0x3f600000, 0x3f400000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, + 0x3ece0000, 0x3e000000, 0x3e800000, 0x00000000, 0x3a800000, 0x3a800000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3ed60000, 0x3ec00000, 0x3e800000, 0x00000000, + 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x00000000, + 0x3ede0000, 0x3f200000, 0x3e800000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, + 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x3ee60000, 0x3f600000, 0x3e800000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3f800000, 0x00000000, 0x00000000, 0x00000000, + 0x3eee0000, 0x3e000000, 0x3f400000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x3a800000, 0x3a800000, 0x00000000, 0x3ef60000, 0x3ec00000, 0x3f400000, 0x00000000, + 0x3f800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3efe0000, 0x3f200000, 0x3f400000, 0x00000000, 0x00000000, 0x3a800000, 0x3a800000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3f030000, 0x3f600000, 0x3f400000, 0x00000000, + 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, +}; +alignas(256) std::array Buffer{}; +alignas(256) std::array Texels{}; + +alignas(256) constexpr std::array Code2D{ + 0x160200b0, 0x34060086, 0xe0381000, 0x80000401, 0xe0381010, 0x80002801, 0xe0381020, 0x80003201, + 0xbe900404, 0xbe920406, 0xbe940408, 0xbe96040a, 0xbe98040c, 0xbe9a040e, 0x7e140280, 0x7e160280, + 0x7e180280, 0x7e1a0280, 0x7e1c0280, 0x7e1e0280, 0x7e200280, 0x7e220280, 0x7e240280, 0x7e260280, + 0x7e280280, 0x7e2a0280, 0x7e2c0280, 0x7e2e0280, 0x7e300280, 0x7e320280, 0xbf8c3f70, 0x5e585328, + 0xd52f002d, 0x0201012a, 0x5e5c6732, 0xd52f002f, 0x02010134, 0x5e600b04, 0xd52f0031, 0x02010106, + 0xd52f0036, 0x02010128, 0xd52f0037, 0x02010132, 0xd52f003b, 0x02010104, 0x7e780281, 0x7e7a02f6, + 0x7e7c02ff, 0x00000101, 0x7e800328, 0x7e820329, 0x7e840332, 0x7e860333, 0x7e880304, 0x7e8a0305, + 0xf0880108, 0x00c40a40, 0x7e800328, 0x7e820329, 0x7e840332, 0x7e860333, 0x7e880304, 0x7e8a0305, + 0xf1a00108, 0x00c40b40, 0x7e800328, 0x7e820329, 0x7e840332, 0x7e860333, 0x7e880304, 0x7e8a0305, + 0xbe9e037e, 0xbefe03ff, 0xaeeeffff, 0xf1a00108, 0x00c40c40, 0xbefe031e, 0x7e80032c, 0x7e82032e, + 0x7e840304, 0x7e860305, 0xf0880109, 0x00c40d40, 0x7e80032c, 0x7e82032e, 0x7e840304, 0x7e860305, + 0xf1a00109, 0x00c40e40, 0x7e800328, 0x7e820329, 0x7e840332, 0x7e860333, 0x7e880330, 0xf0880108, + 0x40c40f40, 0x7e80032c, 0x7e82032e, 0x7e840330, 0xf0880109, 0x40c41040, 0x7e80033c, 0x7e820328, + 0x7e840329, 0x7e860332, 0x7e880333, 0x7e8a0304, 0x7e8c0305, 0xf1b00108, 0x00c41140, 0x7e80033e, + 0x7e82032c, 0x7e84032e, 0x7e860304, 0x7e880305, 0xf0c80109, 0x00c41240, 0x7e80033c, 0x7e82032c, + 0x7e84032e, 0x7e860304, 0x7e880305, 0xf1b00109, 0x00c41340, 0x7e80033c, 0x7e82032c, 0x7e84032e, + 0x7e860330, 0xf0c80109, 0x40c41440, 0x7e80033c, 0x7e82032c, 0x7e84032e, 0x7e860304, 0x7e880305, + 0xbe9e037e, 0xbefe03ff, 0xaeeeffff, 0xf1b00109, 0x00c41540, 0xbefe031e, 0xbf8c3f70, 0xe0701600, + 0x80000a03, 0xe0701604, 0x80000b03, 0xe0701608, 0x80000c03, 0xe070160c, 0x80000d03, 0xe0701610, + 0x80000e03, 0xe0701614, 0x80000f03, 0xe0701618, 0x80001003, 0xe070161c, 0x80001103, 0xe0701620, + 0x80001203, 0xe0701624, 0x80001303, 0xe0701628, 0x80001403, 0xe070162c, 0x80001503, 0xbf810000, +}; + +alignas(256) constexpr std::array Code3D{ + 0x160200b0, 0x34060086, 0xe0381000, 0x80000401, 0xe0381010, 0x80002801, 0xe0381020, 0x80003201, + 0xbe900404, 0xbe920406, 0xbe940408, 0xbe96040a, 0xbe98040c, 0xbe9a040e, 0x7e140280, 0x7e160280, + 0x7e180280, 0x7e1a0280, 0x7e1c0280, 0x7e1e0280, 0x7e200280, 0x7e220280, 0x7e240280, 0x7e260280, + 0x7e280280, 0x7e2a0280, 0x7e2c0280, 0x7e2e0280, 0x7e300280, 0x7e320280, 0xbf8c3f70, 0x5e585328, + 0xd52f002d, 0x0201012a, 0x5e5c6732, 0xd52f002f, 0x02010134, 0x5e600b04, 0xd52f0031, 0x02010106, + 0xd52f0036, 0x02010128, 0xd52f0037, 0x02010132, 0xd52f003b, 0x02010104, 0x7e780281, 0x7e7a02f6, + 0x7e7c02ff, 0x00000101, 0x7e800328, 0x7e820329, 0x7e84032a, 0x7e860332, 0x7e880333, 0x7e8a0334, + 0x7e8c0304, 0x7e8e0305, 0x7e900306, 0xf0880110, 0x00c40a40, 0x7e800328, 0x7e820329, 0x7e84032a, + 0x7e860332, 0x7e880333, 0x7e8a0334, 0x7e8c0304, 0x7e8e0305, 0x7e900306, 0xf1a00110, 0x00c40b40, + 0x7e80032c, 0x7e82032d, 0x7e84032e, 0x7e86032f, 0x7e880304, 0x7e8a0305, 0x7e8c0306, 0xf0880111, + 0x00c40c40, 0x7e80032c, 0x7e82032d, 0x7e84032e, 0x7e86032f, 0x7e880304, 0x7e8a0305, 0x7e8c0306, + 0xf1a00111, 0x00c40d40, 0x7e800328, 0x7e820329, 0x7e84032a, 0x7e860332, 0x7e880333, 0x7e8a0334, + 0x7e8c0330, 0x7e8e0331, 0xf0880110, 0x40c40e40, 0x7e80032c, 0x7e82032d, 0x7e84032e, 0x7e86032f, + 0x7e880330, 0x7e8a0331, 0xf0880111, 0x40c40f40, 0xbf8c3f70, 0xe0701600, 0x80000a03, 0xe0701604, + 0x80000b03, 0xe0701608, 0x80000c03, 0xe070160c, 0x80000d03, 0xe0701610, 0x80000e03, 0xe0701614, + 0x80000f03, 0xbf810000, +}; + +alignas(256) constexpr std::array Code1D{ + 0x160200b0, 0x34060086, 0xe0381000, 0x80000401, 0xe0381010, 0x80002801, 0xe0381020, 0x80003201, + 0xbe900404, 0xbe920406, 0xbe940408, 0xbe96040a, 0xbe98040c, 0xbe9a040e, 0x7e140280, 0x7e160280, + 0x7e180280, 0x7e1a0280, 0x7e1c0280, 0x7e1e0280, 0x7e200280, 0x7e220280, 0x7e240280, 0x7e260280, + 0x7e280280, 0x7e2a0280, 0x7e2c0280, 0x7e2e0280, 0x7e300280, 0x7e320280, 0xbf8c3f70, 0x5e585328, + 0xd52f002d, 0x0201012a, 0x5e5c6732, 0xd52f002f, 0x02010134, 0x5e600b04, 0xd52f0031, 0x02010106, + 0xd52f0036, 0x02010128, 0xd52f0037, 0x02010132, 0xd52f003b, 0x02010104, 0x7e780281, 0x7e7a02f6, + 0x7e7c02ff, 0x00000101, 0x7e800328, 0x7e820332, 0x7e840304, 0xf0880100, 0x00c40a40, 0x7e800336, + 0x7e820337, 0x7e840304, 0xf0880101, 0x00c40b40, 0x7e800336, 0x7e820337, 0x7e840304, 0xf1a00101, + 0x00c40c40, 0x7e800328, 0x7e820332, 0x7e84033b, 0xf0880100, 0x40c40d40, 0x7e800336, 0x7e820337, + 0x7e84033b, 0xf1a00101, 0x40c40e40, 0xbf8c3f70, 0xe0701600, 0x80000a03, 0xe0701604, 0x80000b03, + 0xe0701608, 0x80000c03, 0xe070160c, 0x80000d03, 0xe0701610, 0x80000e03, 0xbf810000, +}; + +constexpr std::array Names2D{"image_sample_d", "image_sample_cd", "image_sample_cd (exec)", "image_sample_d_g16", "image_sample_cd_g16", "image_sample_d a16", "image_sample_d_g16 a16", "image_sample_cd_o", "image_sample_d_o_g16", "image_sample_cd_o_g16", "image_sample_d_o_g16 a16", "image_sample_cd_o_g16 (exec)"}; +constexpr std::array Masked2D{false, false, true, false, false, false, false, false, false, false, false, true}; +constexpr std::array Expected2D{ + 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x40100000, + 0x42cc8000, 0x40100000, 0x40100000, 0x40100000, 0x42cc0000, 0x42cc8000, 0x42cc8000, 0x42cc0000, + 0x42cc8000, 0x42cc0000, 0x42cc0000, 0x42ce8000, 0x434b0000, 0x42ce8000, 0x42ce0000, 0x42ce8000, + 0x434b0000, 0x434b4000, 0x434b4000, 0x434b0000, 0x434b4000, 0x434b0000, 0x434b0000, 0x434c4000, + 0x43980000, 0x434c4000, 0x434c0000, 0x434c4000, 0x43980000, 0x43982000, 0x43982000, 0x43980000, + 0x43982000, 0x43980000, 0x43980000, 0x4398a000, 0x43988000, 0x4398a000, 0x43988000, 0x4398a000, + 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40c00000, + 0x42d40000, 0x40c00000, 0x40c00000, 0x40c00000, 0x42d48000, 0x42d40000, 0x42d40000, 0x42d48000, + 0x42d40000, 0x42d48000, 0x42d48000, 0x42d60000, 0x434f4000, 0x42d60000, 0x42d68000, 0x42d60000, + 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x43500000, + 0x439a0000, 0x43500000, 0x43500000, 0x43500000, 0x439a0000, 0x439a0000, 0x439a0000, 0x439a0000, + 0x439a0000, 0x439a0000, 0x439a0000, 0x439a8000, 0x439a8000, 0x439a8000, 0x439a8000, 0x439a8000, + 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41200000, + 0x42dc0000, 0x41200000, 0x41200000, 0x41200000, 0x42dc0000, 0x42dc0000, 0x42dc0000, 0x42dc0000, + 0x42dc0000, 0x42dc0000, 0x42dc0000, 0x42de0000, 0x43530000, 0x42de0000, 0x42de0000, 0x42de0000, + 0x43534000, 0x43530000, 0x43530000, 0x43534000, 0x43530000, 0x43534000, 0x43534000, 0x43540000, + 0x439c2000, 0x43540000, 0x43544000, 0x43540000, 0x439c0000, 0x439c0000, 0x439c0000, 0x439c0000, + 0x439c0000, 0x439c0000, 0x439c0000, 0x439c8000, 0x439c8000, 0x439c8000, 0x439c8000, 0x439c8000, + 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41600000, + 0x42e40000, 0x41600000, 0x41600000, 0x41600000, 0x42e40000, 0x42e40000, 0x42e40000, 0x42e40000, + 0x42e40000, 0x42e40000, 0x42e40000, 0x42e60000, 0x43570000, 0x42e60000, 0x42e60000, 0x42e60000, + 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43580000, + 0x439e0000, 0x43580000, 0x43580000, 0x43580000, 0x439e2000, 0x439e0000, 0x439e0000, 0x439e2000, + 0x439e0000, 0x439e2000, 0x439e2000, 0x439e8000, 0x439ea000, 0x439e8000, 0x439ea000, 0x439e8000, + 0x41880000, 0x41880000, 0x00000000, 0x41880000, 0x41880000, 0x41880000, 0x41880000, 0x41900000, + 0x42ec0000, 0x41900000, 0x41900000, 0x00000000, 0x42ec8000, 0x42ec0000, 0x42ec8000, 0x42ec8000, + 0x42ec0000, 0x42ec8000, 0x42ec8000, 0x42ee0000, 0x435b4000, 0x42ee0000, 0x42ee8000, 0x42ee8000, + 0x435b0000, 0x435b0000, 0x435b4000, 0x435b0000, 0x435b0000, 0x435b0000, 0x435b0000, 0x435c0000, + 0x43a00000, 0x435c0000, 0x435c0000, 0x435c4000, 0x43a00000, 0x43a00000, 0x43a02000, 0x43a00000, + 0x43a00000, 0x43a00000, 0x43a00000, 0x43a08000, 0x43a08000, 0x43a08000, 0x43a08000, 0x43a0a000, + 0x41a80000, 0x41a80000, 0x00000000, 0x41a80000, 0x41a80000, 0x41a80000, 0x41a80000, 0x41b00000, + 0x42f40000, 0x41b00000, 0x41b00000, 0x00000000, 0x42f40000, 0x42f40000, 0x42f40000, 0x42f40000, + 0x42f40000, 0x42f40000, 0x42f40000, 0x42f60000, 0x435f0000, 0x42f60000, 0x42f60000, 0x42f60000, + 0x435f4000, 0x435f0000, 0x435f0000, 0x435f4000, 0x435f0000, 0x435f4000, 0x435f4000, 0x43600000, + 0x43a22000, 0x43600000, 0x43604000, 0x43600000, 0x43a20000, 0x43a20000, 0x43a20000, 0x43a20000, + 0x43a20000, 0x43a20000, 0x43a20000, 0x43a28000, 0x43a28000, 0x43a28000, 0x43a28000, 0x43a28000, + 0x41c80000, 0x41c80000, 0x00000000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41d00000, + 0x42fc0000, 0x41d00000, 0x41d00000, 0x00000000, 0x42fc0000, 0x42fc0000, 0x42fc0000, 0x42fc0000, + 0x42fc0000, 0x42fc0000, 0x42fc0000, 0x42fe0000, 0x43630000, 0x42fe0000, 0x42fe0000, 0x42fe0000, + 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43640000, + 0x43a40000, 0x43640000, 0x43640000, 0x43640000, 0x43a42000, 0x43a40000, 0x43a40000, 0x43a42000, + 0x43a40000, 0x43a42000, 0x43a42000, 0x43a48000, 0x43a4a000, 0x43a48000, 0x43a4a000, 0x43a48000, + 0x41e80000, 0x41e80000, 0x00000000, 0x41e80000, 0x41e80000, 0x41e80000, 0x41e80000, 0x41f00000, + 0x43020000, 0x41f00000, 0x41f00000, 0x00000000, 0x43024000, 0x43020000, 0x43024000, 0x43024000, + 0x43020000, 0x43024000, 0x43024000, 0x43030000, 0x43674000, 0x43030000, 0x43034000, 0x43034000, + 0x43670000, 0x43670000, 0x00000000, 0x43670000, 0x43670000, 0x43670000, 0x43670000, 0x43680000, + 0x43a60000, 0x43680000, 0x43680000, 0x00000000, 0x43a60000, 0x43a60000, 0x43a62000, 0x43a60000, + 0x43a60000, 0x43a60000, 0x43a60000, 0x43a68000, 0x43a68000, 0x43a68000, 0x43a68000, 0x43a6a000, +}; +constexpr std::array Names3D{"image_sample_d 3D", "image_sample_cd 3D", "image_sample_d_g16 3D", "image_sample_cd_g16 3D", "image_sample_d a16 3D", "image_sample_d_g16 a16 3D"}; +constexpr std::array Masked3D{false, false, false, false, false, false}; +constexpr std::array Expected3D{ + 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x42cc0000, 0x42cc8000, + 0x42cc0000, 0x42cc8000, 0x42cc0000, 0x42cc0000, 0x434b0000, 0x434b4000, 0x434b0000, 0x434b4000, + 0x434b0000, 0x434b0000, 0x43980000, 0x43982000, 0x43980000, 0x43982000, 0x43980000, 0x43980000, + 0x447b4000, 0x447b4000, 0x447b4000, 0x447b4000, 0x447b4000, 0x447b4000, 0x448a4800, 0x448a4000, + 0x448a4800, 0x448a4000, 0x448a4800, 0x448a4800, 0x4496e000, 0x4496e000, 0x4496e000, 0x4496e000, + 0x4496e000, 0x4496e000, 0x44a38000, 0x44a38000, 0x44a38000, 0x44a38000, 0x44a38000, 0x44a38000, + 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x42dc0000, 0x42dc0000, + 0x42dc0000, 0x42dc0000, 0x42dc0000, 0x42dc0000, 0x43534000, 0x43530000, 0x43534000, 0x43530000, + 0x43534000, 0x43534000, 0x439c0000, 0x439c0000, 0x439c0000, 0x439c0000, 0x439c0000, 0x439c0000, + 0x447d4000, 0x447d4000, 0x447d4000, 0x447d4000, 0x447d4000, 0x447d4000, 0x448b4000, 0x448b4000, + 0x448b4000, 0x448b4000, 0x448b4000, 0x448b4000, 0x4497e000, 0x4497e000, 0x4497e000, 0x4497e000, + 0x4497e000, 0x4497e000, 0x44a48800, 0x44a48000, 0x44a48800, 0x44a48000, 0x44a48800, 0x44a48800, + 0x41880000, 0x41880000, 0x41880000, 0x41880000, 0x41880000, 0x41880000, 0x42ec8000, 0x42ec0000, + 0x42ec8000, 0x42ec0000, 0x42ec8000, 0x42ec8000, 0x435b0000, 0x435b0000, 0x435b0000, 0x435b0000, + 0x435b0000, 0x435b0000, 0x43a00000, 0x43a00000, 0x43a00000, 0x43a00000, 0x43a00000, 0x43a00000, + 0x447f4000, 0x447f4000, 0x447f4000, 0x447f4000, 0x447f4000, 0x447f4000, 0x448c4000, 0x448c4000, + 0x448c4000, 0x448c4000, 0x448c4000, 0x448c4000, 0x4498e800, 0x4498e000, 0x4498e800, 0x4498e000, + 0x4498e800, 0x4498e800, 0x44a58000, 0x44a58000, 0x44a58000, 0x44a58000, 0x44a58000, 0x44a58000, + 0x41c80000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41c80000, 0x42fc0000, 0x42fc0000, + 0x42fc0000, 0x42fc0000, 0x42fc0000, 0x42fc0000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, + 0x43630000, 0x43630000, 0x43a42000, 0x43a40000, 0x43a42000, 0x43a40000, 0x43a42000, 0x43a42000, + 0x4480a000, 0x4480a000, 0x4480a000, 0x4480a000, 0x4480a000, 0x4480a000, 0x448d4800, 0x448d4000, + 0x448d4800, 0x448d4000, 0x448d4800, 0x448d4800, 0x4499e000, 0x4499e000, 0x4499e000, 0x4499e000, + 0x4499e000, 0x4499e000, 0x44a68000, 0x44a68000, 0x44a68000, 0x44a68000, 0x44a68000, 0x44a68000, +}; +constexpr std::array Names1D{"image_sample_d 1D", "image_sample_d_g16 1D", "image_sample_cd_g16 1D", "image_sample_d a16 1D", "image_sample_cd_g16 a16 1D"}; +constexpr std::array Masked1D{false, false, false, false, false}; +constexpr std::array Expected1D{ + 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x40000000, 0x40000000, 0x40100000, + 0x40000000, 0x40100000, 0x40400000, 0x40400000, 0x40500000, 0x40400000, 0x40500000, 0x40800000, + 0x40800000, 0x40880000, 0x40800000, 0x40880000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, + 0x40a00000, 0x40c80000, 0x40c80000, 0x40c00000, 0x40c80000, 0x40c00000, 0x40e00000, 0x40e00000, + 0x40e00000, 0x40e00000, 0x40e00000, 0x41000000, 0x41000000, 0x41000000, 0x41000000, 0x41000000, + 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41200000, 0x41200000, 0x41200000, + 0x41200000, 0x41200000, 0x41340000, 0x41340000, 0x41300000, 0x41340000, 0x41300000, 0x41400000, + 0x41400000, 0x41400000, 0x41400000, 0x41400000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, + 0x41500000, 0x41600000, 0x41600000, 0x41600000, 0x41600000, 0x41600000, 0x41700000, 0x41700000, + 0x41700000, 0x41700000, 0x41700000, 0x41820000, 0x41820000, 0x41800000, 0x41820000, 0x41800000, + 0x41880000, 0x41880000, 0x41880000, 0x41880000, 0x41880000, 0x41920000, 0x41920000, 0x41900000, + 0x41920000, 0x41900000, 0x41980000, 0x41980000, 0x41980000, 0x41980000, 0x41980000, 0x41a00000, + 0x41a00000, 0x41a00000, 0x41a00000, 0x41a00000, 0x41a80000, 0x41a80000, 0x41a80000, 0x41a80000, + 0x41a80000, 0x41b00000, 0x41b00000, 0x41b00000, 0x41b00000, 0x41b00000, 0x41ba0000, 0x41ba0000, + 0x41b80000, 0x41ba0000, 0x41b80000, 0x41c00000, 0x41c00000, 0x41c00000, 0x41c00000, 0x41c00000, + 0x41c80000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41d00000, 0x41d00000, 0x41d00000, + 0x41d00000, 0x41d00000, 0x41d80000, 0x41d80000, 0x41d80000, 0x41d80000, 0x41d80000, 0x41e20000, + 0x41e20000, 0x41e00000, 0x41e20000, 0x41e00000, 0x41e80000, 0x41e80000, 0x41e80000, 0x41e80000, + 0x41e80000, 0x41f20000, 0x41f20000, 0x41f00000, 0x41f20000, 0x41f00000, 0x41f80000, 0x41f80000, + 0x41f80000, 0x41f80000, 0x41f80000, 0x42000000, 0x42000000, 0x42000000, 0x42000000, 0x42000000, +}; + +std::array TextureDescriptor(std::uint32_t type, std::uint32_t height, std::uint32_t depth) { + const auto address = static_cast(reinterpret_cast(Texels.data())); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format32Float << 20u) | (((Width - 1u) & 3u) << 30u), + ((Width - 1u) >> 2u) | ((height - 1u) << 14u) | (1u << 31u), + 0xfacu | (type << 28u), + depth - 1u, 0u, 0u, 0u, + }; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +template +void Run(AgcDriver::VulkanDevice& device, const std::array& code, std::uint32_t type, std::uint32_t height, std::uint32_t depth, + const std::array& names, const std::array& masked, const std::array& expected) { + std::copy(Input.begin(), Input.end(), Buffer.begin()); + std::fill(Buffer.begin() + Threads * Inputs, Buffer.end(), 0xdeadbeefu); + for (std::uint32_t z = 0; z < depth; ++z) { + for (std::uint32_t y = 0; y < height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + Texels[(z * height + y) * Width + x] = static_cast(x + 100u * y + 1000u * z); + } + } + } + const auto address = reinterpret_cast(Buffer.data()); + const std::array buffer{static_cast(address), static_cast((address >> 32u) & 0xffffu), static_cast(Buffer.size() * 4u), 0x01016facu}; + const auto texture = TextureDescriptor(type, height, depth); + const std::array sampler{0x92u, (4u * 256u) << 12u, (1u << 22u) | (1u << 26u), 0u}; + std::vector userData(16, 0u); + std::copy(buffer.begin(), buffer.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + std::copy(sampler.begin(), sampler.end(), userData.begin() + 12); + const std::span span(code); + const std::array memory{{{reinterpret_cast(span.data()), std::as_bytes(span)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(span.data()), span, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(span.data())); + device.WaitIdle(); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + for (std::uint32_t k = 0; k < Cases; ++k) { + if (masked[k] && device.Target().subgroupSize < Threads) continue; + const auto actual = Buffer[Threads * Inputs + tid * Results + k]; + const auto wanted = expected[tid * Cases + k]; + const bool close = std::fabs(std::bit_cast(actual) - std::bit_cast(wanted)) <= 1.0f / 64.0f; + Require(close, std::string(names[k]) + ": lane " + std::to_string(tid) + " is " + Hex(actual) + ", expected " + Hex(wanted)); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < Threads) { + std::printf("EXEC-masked cases skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); + } + Run(*device, Code2D, 9u, Height, 1u, Names2D, Masked2D, Expected2D); + Run(*device, Code3D, 10u, Height, Depth, Names3D, Masked3D, Expected3D); + Run(*device, Code1D, 8u, 1u, 1u, Names1D, Masked1D, Expected1D); + std::puts("image sample derivatives 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/RdnaInstruction.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaInstruction.hpp index e27d5fb18..3521bf1df 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaInstruction.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaInstruction.hpp @@ -73,8 +73,12 @@ enum RdnaImageSampleFlag : std::uint32_t { RdnaImageSampleFlagCd = 1u << 8u, RdnaImageSampleFlagGatherHorizontal = 1u << 9u, RdnaImageSampleFlagAdjust = 1u << 10u, + RdnaImageSampleFlagG16 = 1u << 11u, }; +inline constexpr std::uint32_t RdnaImageSampleGradientCountShift = 12u; +inline constexpr std::uint32_t RdnaImageSampleGradientCountMask = 3u << RdnaImageSampleGradientCountShift; + inline constexpr std::uint32_t MaxRdnaInstructionRawWords = 5; inline constexpr std::uint32_t MaxRdnaImageNsaAddressComponents = 12; diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp index 077686add..e3d02397f 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -140,6 +140,30 @@ enum class RdnaOpcode : std::uint16_t { ImageSampleDO, ImageSampleLzO, ImageSampleO, + ImageSampleCd, + ImageSampleCdCl, + ImageSampleCCd, + ImageSampleCCdCl, + ImageSampleCdO, + ImageSampleCdClO, + ImageSampleCCdO, + ImageSampleCCdClO, + ImageSampleDG16, + ImageSampleDClG16, + ImageSampleCDG16, + ImageSampleCDClG16, + ImageSampleDOG16, + ImageSampleDClOG16, + ImageSampleCDOG16, + ImageSampleCDClOG16, + ImageSampleCdG16, + ImageSampleCdClG16, + ImageSampleCCdG16, + ImageSampleCCdClG16, + ImageSampleCdOG16, + ImageSampleCdClOG16, + ImageSampleCCdOG16, + ImageSampleCCdClOG16, ImageLoad, ImageStore, ImageGetResinfo, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp index 54ff7e8e2..f72be342e 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp @@ -40,7 +40,30 @@ constexpr ImageOpcodeInfo imageOpcodes[] = { {0x35u, RdnaOpcode::ImageSampleBO, "image_sample_b_o", RdnaImageSampleFlagBias | RdnaImageSampleFlagOffset, true, false, false}, {0x37u, RdnaOpcode::ImageSampleLzO, "image_sample_lz_o", RdnaImageSampleFlagLevelZero | RdnaImageSampleFlagOffset, true, false, false}, {0x38u, RdnaOpcode::ImageSampleCO, "image_sample_c_o", RdnaImageSampleFlagCompare | RdnaImageSampleFlagOffset, true, false, false}, - {0x68u, RdnaOpcode::ImageSample, "image_sample_cd", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagCd, true, false, false}, + {0x68u, RdnaOpcode::ImageSampleCd, "image_sample_cd", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagCd, true, false, false}, + {0x69u, RdnaOpcode::ImageSampleCdCl, "image_sample_cd_cl", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagCd, true, false, false}, + {0x6au, RdnaOpcode::ImageSampleCCd, "image_sample_c_cd", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagCd, true, false, false}, + {0x6bu, RdnaOpcode::ImageSampleCCdCl, "image_sample_c_cd_cl", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagCd, true, false, false}, + {0x6cu, RdnaOpcode::ImageSampleCdO, "image_sample_cd_o", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd, true, false, false}, + {0x6du, RdnaOpcode::ImageSampleCdClO, "image_sample_cd_cl_o", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd, true, false, false}, + {0x6eu, RdnaOpcode::ImageSampleCCdO, "image_sample_c_cd_o", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd, true, false, false}, + {0x6fu, RdnaOpcode::ImageSampleCCdClO, "image_sample_c_cd_cl_o", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd, true, false, false}, + {0xa2u, RdnaOpcode::ImageSampleDG16, "image_sample_d_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagG16, true, false, false}, + {0xa3u, RdnaOpcode::ImageSampleDClG16, "image_sample_d_cl_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagG16, true, false, false}, + {0xaau, RdnaOpcode::ImageSampleCDG16, "image_sample_c_d_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagG16, true, false, false}, + {0xabu, RdnaOpcode::ImageSampleCDClG16, "image_sample_c_d_cl_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagG16, true, false, false}, + {0xb2u, RdnaOpcode::ImageSampleDOG16, "image_sample_d_o_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagOffset | RdnaImageSampleFlagG16, true, false, false}, + {0xb3u, RdnaOpcode::ImageSampleDClOG16, "image_sample_d_cl_o_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagOffset | RdnaImageSampleFlagG16, true, false, false}, + {0xbau, RdnaOpcode::ImageSampleCDOG16, "image_sample_c_d_o_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagOffset | RdnaImageSampleFlagG16, true, false, false}, + {0xbbu, RdnaOpcode::ImageSampleCDClOG16, "image_sample_c_d_cl_o_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagOffset | RdnaImageSampleFlagG16, true, false, false}, + {0xe8u, RdnaOpcode::ImageSampleCdG16, "image_sample_cd_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, + {0xe9u, RdnaOpcode::ImageSampleCdClG16, "image_sample_cd_cl_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, + {0xeau, RdnaOpcode::ImageSampleCCdG16, "image_sample_c_cd_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, + {0xebu, RdnaOpcode::ImageSampleCCdClG16, "image_sample_c_cd_cl_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, + {0xecu, RdnaOpcode::ImageSampleCdOG16, "image_sample_cd_o_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, + {0xedu, RdnaOpcode::ImageSampleCdClOG16, "image_sample_cd_cl_o_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, + {0xeeu, RdnaOpcode::ImageSampleCCdOG16, "image_sample_c_cd_o_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, + {0xefu, RdnaOpcode::ImageSampleCCdClOG16, "image_sample_c_cd_cl_o_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, {0x33u, RdnaOpcode::ImageSample, "image_sample_d_cl_o", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagOffset, true, false, false}, {0xa0u, RdnaOpcode::ImageSample, "image_sample_a", RdnaImageSampleFlagAdjust, true, false, false}, {0xa1u, RdnaOpcode::ImageSample, "image_sample_cl_a", RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagAdjust, true, false, false}, @@ -123,37 +146,54 @@ const ImageOpcodeInfo& lookupOpcode(std::uint32_t opcode) { } void validateFlags(std::uint32_t flags) { - constexpr std::uint32_t known = (1u << 11u) - 1u; + constexpr std::uint32_t known = ((1u << 12u) - 1u) | RdnaImageSampleGradientCountMask; if ((flags & ~known) != 0u) { throw std::runtime_error("unknown image address flags"); } + if (((flags & RdnaImageSampleGradientCountMask) != 0u) != ((flags & RdnaImageSampleFlagDerivative) != 0u)) { + throw std::runtime_error("image derivative flags without a gradient count"); + } const auto lodModes = flags & (RdnaImageSampleFlagLod | RdnaImageSampleFlagBias | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLevelZero); if (std::popcount(lodModes) > 1) { throw std::runtime_error("conflicting image LOD modes"); } } -std::uint32_t componentWidth(std::uint32_t flags, std::uint32_t component) { - if ((flags & RdnaImageSampleFlagA16) == 0u) { - return 32; - } +struct ComponentShape { + std::uint32_t width; + bool startsDword; +}; + +ComponentShape componentShape(std::uint32_t flags, std::uint32_t component) { + const bool a16 = (flags & RdnaImageSampleFlagA16) != 0u; std::uint32_t cursor = 0; if ((flags & RdnaImageSampleFlagOffset) != 0u) { if (component == cursor) { - return 32; + return {32, true}; } ++cursor; } if ((flags & RdnaImageSampleFlagBias) != 0u) { if (component == cursor) { - return 16; + return {a16 ? 16u : 32u, !a16}; } ++cursor; } - if ((flags & RdnaImageSampleFlagCompare) != 0u && component == cursor) { - return 32; + if ((flags & RdnaImageSampleFlagCompare) != 0u) { + if (component == cursor) { + return {32, true}; + } + ++cursor; } - return 16; + const auto gradients = (flags & RdnaImageSampleGradientCountMask) >> RdnaImageSampleGradientCountShift; + if (component < cursor + gradients * 2u) { + if ((flags & RdnaImageSampleFlagG16) == 0u) { + return {32, true}; + } + return {16, (component - cursor) % gradients == 0u}; + } + cursor += gradients * 2u; + return {a16 ? 16u : 32u, !a16 || (gradients != 0u && component == cursor)}; } bool isBiasComponent(std::uint32_t flags, std::uint32_t component) { @@ -232,14 +272,14 @@ RdnaImageAddressComponent GetRdnaImageAddressComponentLayout(std::uint32_t flags } std::uint32_t offset = 0; for (std::uint32_t index = 0; index <= component; ++index) { - const auto width = componentWidth(flags, index); - if (width == 32u) { + const auto shape = componentShape(flags, index); + if (shape.startsDword) { offset = (offset + 31u) & ~31u; } if (index == component) { - return {offset, width}; + return {offset, shape.width}; } - offset += isBiasComponent(flags, index) ? 32u : width; + offset += isBiasComponent(flags, index) ? 32u : shape.width; } throw std::runtime_error("invalid image address component"); } @@ -288,13 +328,16 @@ RdnaInstruction DecodeRdnaMimg(std::uint32_t programCounter, std::span> 3u) & 7u); const bool multisampled = dimension == RdnaImageDimension::Dim2DMsaa || dimension == RdnaImageDimension::Dim2DMsaaArray; if (multisampled && (info.sample || info.gather || opcode == 0x60u || (opcode >= 1u && opcode <= 5u) || opcode == 9u || opcode == 0x0bu)) { throw std::runtime_error("unsupported multisampled MIMG operation"); } + auto flags = info.flags | (a16 ? RdnaImageSampleFlagA16 : 0u); + if ((info.flags & RdnaImageSampleFlagDerivative) != 0u) { + flags |= gradientCount(dimension) << RdnaImageSampleGradientCountShift; + } + validateFlags(flags); const auto dmask = (word0 >> 8u) & 15u; const bool compareSwap = info.opcode == RdnaOpcode::ImageAtomicCmpswap || info.opcode == RdnaOpcode::ImageAtomicFcmpswap; if (dmask == 0u || (compareSwap ? dmask != 3u : (info.gather || info.atomic) && !std::has_single_bit(dmask))) { diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp index 25da22e12..e602e3bf3 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp @@ -775,6 +775,30 @@ bool IsImageOpcode(RdnaOpcode opcode) { case RdnaOpcode::ImageSampleDO: case RdnaOpcode::ImageSampleLzO: case RdnaOpcode::ImageSampleO: + case RdnaOpcode::ImageSampleCd: + case RdnaOpcode::ImageSampleCdCl: + case RdnaOpcode::ImageSampleCCd: + case RdnaOpcode::ImageSampleCCdCl: + case RdnaOpcode::ImageSampleCdO: + case RdnaOpcode::ImageSampleCdClO: + case RdnaOpcode::ImageSampleCCdO: + case RdnaOpcode::ImageSampleCCdClO: + case RdnaOpcode::ImageSampleDG16: + case RdnaOpcode::ImageSampleDClG16: + case RdnaOpcode::ImageSampleCDG16: + case RdnaOpcode::ImageSampleCDClG16: + case RdnaOpcode::ImageSampleDOG16: + case RdnaOpcode::ImageSampleDClOG16: + case RdnaOpcode::ImageSampleCDOG16: + case RdnaOpcode::ImageSampleCDClOG16: + case RdnaOpcode::ImageSampleCdG16: + case RdnaOpcode::ImageSampleCdClG16: + case RdnaOpcode::ImageSampleCCdG16: + case RdnaOpcode::ImageSampleCCdClG16: + case RdnaOpcode::ImageSampleCdOG16: + case RdnaOpcode::ImageSampleCdClOG16: + case RdnaOpcode::ImageSampleCCdOG16: + case RdnaOpcode::ImageSampleCCdClOG16: case RdnaOpcode::ImageSampleLz: case RdnaOpcode::ImageLoad: case RdnaOpcode::ImageStore: diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp index e7f6a8c6e..e43411820 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp @@ -431,6 +431,30 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) { case RdnaOpcode::ImageSampleDO: case RdnaOpcode::ImageSampleLzO: case RdnaOpcode::ImageSampleO: + case RdnaOpcode::ImageSampleCd: + case RdnaOpcode::ImageSampleCdCl: + case RdnaOpcode::ImageSampleCCd: + case RdnaOpcode::ImageSampleCCdCl: + case RdnaOpcode::ImageSampleCdO: + case RdnaOpcode::ImageSampleCdClO: + case RdnaOpcode::ImageSampleCCdO: + case RdnaOpcode::ImageSampleCCdClO: + case RdnaOpcode::ImageSampleDG16: + case RdnaOpcode::ImageSampleDClG16: + case RdnaOpcode::ImageSampleCDG16: + case RdnaOpcode::ImageSampleCDClG16: + case RdnaOpcode::ImageSampleDOG16: + case RdnaOpcode::ImageSampleDClOG16: + case RdnaOpcode::ImageSampleCDOG16: + case RdnaOpcode::ImageSampleCDClOG16: + case RdnaOpcode::ImageSampleCdG16: + case RdnaOpcode::ImageSampleCdClG16: + case RdnaOpcode::ImageSampleCCdG16: + case RdnaOpcode::ImageSampleCCdClG16: + case RdnaOpcode::ImageSampleCdOG16: + case RdnaOpcode::ImageSampleCdClOG16: + case RdnaOpcode::ImageSampleCCdOG16: + case RdnaOpcode::ImageSampleCCdClOG16: return imageSample(inst); case RdnaOpcode::ImageGather4Lz: case RdnaOpcode::ImageGather4C: diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryResourceHelpers.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryResourceHelpers.cpp index 88e1afe14..e3e435d59 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryResourceHelpers.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryResourceHelpers.cpp @@ -3,6 +3,7 @@ #include "RdnaDecoder/RdnaImageOpDecoder.hpp" #include #include +#include #include namespace ShaderRecompiler { @@ -109,6 +110,22 @@ IrValue* TranslationContext::makeImageAddress(const RdnaInstruction& inst, const components[index] = &readRawU32(offsetOperand(plainBase, index)).Value(); } } + if ((inst.imageSampleFlags & RdnaImageSampleFlagCd) != 0u) { + const auto mask = ballotMask(IrU1(ir.GetExec())); + IrValue& lane = ir.Emit(IrOpcode::LaneId, IrType::U32, {}); + IrValue& quad = ir.BitwiseAnd(lane, ir.Constant(~3u)); + IrValue& word = program.WaveSize() == 64u ? ir.Select(ir.ULessThan(lane, ir.Constant(32u)), mask[0].Value(), mask[1].Value()) : mask[0].Value(); + IrValue& active = ir.BitwiseAnd(ir.ShiftRightLogical(word, ir.BitwiseAnd(quad, ir.Constant(31u))), ir.Constant(15u)); + IrValue& first = ir.Emit(IrOpcode::FindILsb32, IrType::U32, {&active}); + IrValue& source = ir.Select(ir.INotEqual(active, ir.Constant(0u)), ir.IAdd(quad, first), lane); + const std::uint32_t gradientStart = std::popcount(inst.imageSampleFlags & (RdnaImageSampleFlagOffset | RdnaImageSampleFlagBias | RdnaImageSampleFlagCompare)); + const std::uint32_t gradients = 2u * ((inst.imageSampleFlags & RdnaImageSampleGradientCountMask) >> RdnaImageSampleGradientCountShift); + const std::uint32_t firstDword = GetRdnaImageAddressComponentLayout(inst.imageSampleFlags, gradientStart).bitOffset / 32u; + const std::uint32_t lastDword = GetRdnaImageAddressComponentLayout(inst.imageSampleFlags, gradientStart + gradients - 1u).bitOffset / 32u; + for (std::uint32_t index = firstDword; index <= lastDword; ++index) { + components[index] = &ir.Emit(IrOpcode::ReadLane, IrType::U32, {components[index], &source}); + } + } return &ir.Emit(IrOpcode::MakeImageAddress, IrOpcodeType(IrOpcode::MakeImageAddress), {components[0], components[1], components[2], components[3], components[4], components[5], components[6], components[7], components[8], components[9], components[10], components[11], components[12]}); From 559aeac8ec5f4383799798d7740ad8ae5842f4b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 08:37:00 +0200 Subject: [PATCH 059/164] fix(shader): read _cd quad gradients through a lane-varying permute ReadLane counts as a wave-uniform source, so the two-lane wave64 path shared the first half's gradients with lanes 32 to 63. The test also runs every case as wave64 with different quads in the upper half. Co-Authored-By: Claude --- .../execution/ImageSampleDerivatives.cpp | 50 +++++++++++++------ .../MemoryResourceHelpers.cpp | 4 +- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp index 5f2d50d3a..5c29b7e6c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp @@ -19,6 +19,7 @@ using AgcDriver::Graphics::Require; using ShaderRecompiler::ShaderStage; constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Wave64Threads = 64; constexpr std::uint32_t Inputs = 12; constexpr std::uint32_t Results = 16; constexpr std::uint32_t Width = 64; @@ -76,7 +77,7 @@ alignas(256) constexpr std::array Input{ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3f030000, 0x3f600000, 0x3f400000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, }; -alignas(256) std::array Buffer{}; +alignas(256) std::array Buffer{}; alignas(256) std::array Texels{}; alignas(256) constexpr std::array Code2D{ @@ -258,11 +259,28 @@ std::string Hex(std::uint32_t value) { return text; } +std::uint32_t SourceRow(std::uint32_t tid) { + return tid < Threads ? tid : (tid - Threads + Threads / 2u) % Threads; +} + +template +std::array MovedStores(const std::array& code, std::uint32_t threads) { + auto moved = code; + for (auto& word : moved) { + if ((word & 0xfffff000u) == 0xe0701000u && (word & 0xfffu) >= Threads * Inputs * 4u) word += (threads - Threads) * Inputs * 4u; + } + return moved; +} + template -void Run(AgcDriver::VulkanDevice& device, const std::array& code, std::uint32_t type, std::uint32_t height, std::uint32_t depth, +void Run(AgcDriver::VulkanDevice& device, const std::array& source, std::uint32_t threads, std::uint32_t type, std::uint32_t height, std::uint32_t depth, const std::array& names, const std::array& masked, const std::array& expected) { - std::copy(Input.begin(), Input.end(), Buffer.begin()); - std::fill(Buffer.begin() + Threads * Inputs, Buffer.end(), 0xdeadbeefu); + alignas(256) static std::array code; + code = MovedStores(source, threads); + for (std::uint32_t tid = 0; tid < threads; ++tid) { + std::copy_n(Input.begin() + SourceRow(tid) * Inputs, Inputs, Buffer.begin() + tid * Inputs); + } + std::fill(Buffer.begin() + threads * Inputs, Buffer.end(), 0xdeadbeefu); for (std::uint32_t z = 0; z < depth; ++z) { for (std::uint32_t y = 0; y < height; ++y) { for (std::uint32_t x = 0; x < Width; ++x) { @@ -280,10 +298,10 @@ void Run(AgcDriver::VulkanDevice& device, const std::array span(code); const std::array memory{{{reinterpret_cast(span.data()), std::as_bytes(span)}}}; - const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{threads, 1, 1}, 0u, {false, false, false}, false, 1}; ShaderRecompiler::RecompileRequest request{ {ShaderStage::Compute, reinterpret_cast(span.data()), span, 0, {}}, - {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + {threads, 0, userData, compute, std::nullopt, std::nullopt, memory}, device.Target(), {0, 0, 0, 128} }; @@ -291,13 +309,13 @@ void Run(AgcDriver::VulkanDevice& device, const std::array(span.data())); device.WaitIdle(); - for (std::uint32_t tid = 0; tid < Threads; ++tid) { + for (std::uint32_t tid = 0; tid < threads; ++tid) { for (std::uint32_t k = 0; k < Cases; ++k) { - if (masked[k] && device.Target().subgroupSize < Threads) continue; - const auto actual = Buffer[Threads * Inputs + tid * Results + k]; - const auto wanted = expected[tid * Cases + k]; + if (masked[k] && (device.Target().subgroupSize < Threads || tid >= Threads)) continue; + const auto actual = Buffer[threads * Inputs + tid * Results + k]; + const auto wanted = expected[SourceRow(tid) * Cases + k]; const bool close = std::fabs(std::bit_cast(actual) - std::bit_cast(wanted)) <= 1.0f / 64.0f; - Require(close, std::string(names[k]) + ": lane " + std::to_string(tid) + " is " + Hex(actual) + ", expected " + Hex(wanted)); + Require(close, std::string(names[k]) + ": wave" + std::to_string(threads) + " lane " + std::to_string(tid) + " is " + Hex(actual) + ", expected " + Hex(wanted)); } } } @@ -311,9 +329,13 @@ int main() { if (device->Target().subgroupSize < Threads) { std::printf("EXEC-masked cases skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); } - Run(*device, Code2D, 9u, Height, 1u, Names2D, Masked2D, Expected2D); - Run(*device, Code3D, 10u, Height, Depth, Names3D, Masked3D, Expected3D); - Run(*device, Code1D, 8u, 1u, 1u, Names1D, Masked1D, Expected1D); + std::vector waves{Threads}; + if (device->Target().subgroupSize >= Threads) waves.push_back(Wave64Threads); + for (const auto threads : waves) { + Run(*device, Code2D, threads, 9u, Height, 1u, Names2D, Masked2D, Expected2D); + Run(*device, Code3D, threads, 10u, Height, Depth, Names3D, Masked3D, Expected3D); + Run(*device, Code1D, threads, 8u, 1u, 1u, Names1D, Masked1D, Expected1D); + } std::puts("image sample derivatives tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryResourceHelpers.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryResourceHelpers.cpp index e3e435d59..369b78f6c 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryResourceHelpers.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryResourceHelpers.cpp @@ -118,12 +118,14 @@ IrValue* TranslationContext::makeImageAddress(const RdnaInstruction& inst, const IrValue& active = ir.BitwiseAnd(ir.ShiftRightLogical(word, ir.BitwiseAnd(quad, ir.Constant(31u))), ir.Constant(15u)); IrValue& first = ir.Emit(IrOpcode::FindILsb32, IrType::U32, {&active}); IrValue& source = ir.Select(ir.INotEqual(active, ir.Constant(0u)), ir.IAdd(quad, first), lane); + IrValue& address = ir.ShiftLeftLogical(source, ir.Constant(2u)); + IrValue& exec = ir.GetExec(); const std::uint32_t gradientStart = std::popcount(inst.imageSampleFlags & (RdnaImageSampleFlagOffset | RdnaImageSampleFlagBias | RdnaImageSampleFlagCompare)); const std::uint32_t gradients = 2u * ((inst.imageSampleFlags & RdnaImageSampleGradientCountMask) >> RdnaImageSampleGradientCountShift); const std::uint32_t firstDword = GetRdnaImageAddressComponentLayout(inst.imageSampleFlags, gradientStart).bitOffset / 32u; const std::uint32_t lastDword = GetRdnaImageAddressComponentLayout(inst.imageSampleFlags, gradientStart + gradients - 1u).bitOffset / 32u; for (std::uint32_t index = firstDword; index <= lastDword; ++index) { - components[index] = &ir.Emit(IrOpcode::ReadLane, IrType::U32, {components[index], &source}); + components[index] = &ir.Emit(IrOpcode::BpermuteU32, IrOpcodeType(IrOpcode::BpermuteU32), {components[index], &address, &exec}); } } return &ir.Emit(IrOpcode::MakeImageAddress, IrOpcodeType(IrOpcode::MakeImageAddress), From a4482e0b71be3031cdd02bf7a067827206d328fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 03:27:08 +0200 Subject: [PATCH 060/164] fix(shader): compute v_cvt_pknorm_*_f32, v_ldexp_f32 and v_pack_b32_f16 on the bits v_pack_b32_f16 applied neg/abs to bit 31 of the source dword instead of the selected half. v_cvt_pknorm_{i16,u16}_f32 and v_ldexp_f32 used GLSL PackSnorm2x16/PackUnorm2x16/Ldexp, which leave NaN, rounding ties and denormals to the driver. Compute them on the bits as measured on RDNA2. Co-Authored-By: Claude --- .../tests/execution/Vop3PackLdexp.cpp | 169 ++++++++++++++++++ .../IntermediateRepresentation/IrOpcode.hpp | 3 - .../src/IrOpcode.cpp | 3 - .../SpirvBackend/SpirvEmitterInstructions.hpp | 3 - .../src/SpirvAlu/SpirvAluEmitterMath.cpp | 13 -- .../SpirvBackend/src/SpirvFlowEmitter.cpp | 3 - .../Translation/TranslationContext.hpp | 4 +- .../Translation/src/ConvertInstructions.cpp | 13 +- .../Translation/src/FloatInstructions.cpp | 42 +++++ .../Translation/src/VectorInstructions.cpp | 6 +- 10 files changed, 223 insertions(+), 36 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Vop3PackLdexp.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3PackLdexp.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3PackLdexp.cpp new file mode 100644 index 000000000..baa0da0a1 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3PackLdexp.cpp @@ -0,0 +1,169 @@ +#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 +#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, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd768000a, 0x02020b04, 0xd768020b, + 0x22020b04, 0xd769000c, 0x02020b04, 0xd769010d, 0x62020b04, 0xd762000e, 0x02020d04, 0xd762010f, + 0x22020d04, 0xd7620010, 0x02020f05, 0xd54d0011, 0x041a0b04, 0xd7110012, 0x02020b04, 0xd7110213, + 0x22020b04, 0xd7111814, 0x02020b04, 0xd7111115, 0x22020b04, 0xd56b0016, 0x02020b04, 0xd56b0017, + 0x02020f06, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, + 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x3fc00000u, 0x00800000u, 0x00000080u, 0xffffffffu}, + {0xbf800001u, 0x80ffffffu, 0xffffff81u, 0x00000000u}, + {0x3f800000u, 0x7f000000u, 0x0000007fu, 0x00000000u}, + {0x40000000u, 0x00800001u, 0xffffff82u, 0xfffffffeu}, + {0x3f7fff80u, 0x3effff00u, 0x00000000u, 0x00000001u}, + {0xbf000000u, 0x3f000000u, 0x00000115u, 0x348abf59u}, + {0x382000a0u, 0xffc00000u, 0xffffff00u, 0x0000007fu}, + {0x7fa12345u, 0xb8400180u, 0x367c8d22u, 0xffff0000u}, + {0x37800101u, 0x3840017fu, 0x80000001u, 0x000000fdu}, + {0x00000000u, 0x00000001u, 0xffffff69u, 0xffffff00u}, + {0xd62c214cu, 0x7f800001u, 0x000000feu, 0xffffff6bu}, + {0xffc00000u, 0x3ac900c9u, 0xffffffffu, 0x00000116u}, + {0x38a00141u, 0xff800000u, 0x00000000u, 0x00000098u}, + {0x628aa774u, 0x807fffffu, 0xffffff69u, 0xffffff81u}, + {0x7f800000u, 0x3b490191u, 0x0fdc7365u, 0x00000080u}, + {0xff800000u, 0x7f800000u, 0x00000000u, 0x00000116u}, + {0x807fffffu, 0x80000000u, 0x00000096u, 0x00000080u}, + {0x7f800001u, 0x3a800000u, 0x00001000u, 0x00000082u}, + {0x37c000c0u, 0x7fa12345u, 0xffffffffu, 0x7f703ee0u}, + {0x00000001u, 0xab16df9cu, 0xa053bb90u, 0x55e635d7u}, + {0x8dc822deu, 0x00000000u, 0x7fffffffu, 0x002baa81u}, + {0xffc00000u, 0xffbfffffu, 0x00000097u, 0x00000082u}, + {0x80000000u, 0x2d6e2b74u, 0xffffff6bu, 0x9fb7084au}, + {0xf2d7d40fu, 0x7f800001u, 0x00000100u, 0x7ffffffeu}, + {0x3c000000u, 0x80000000u, 0xfffffe00u, 0x1b1c3f27u}, + {0x3efffe01u, 0x3f000001u, 0xffffff6bu, 0xffffffffu}, + {0x3f000000u, 0xb8a0013fu, 0x00000096u, 0x5cdff86du}, + {0x3b490192u, 0x4801920eu, 0x00000115u, 0x7fffffffu}, + {0x3ac900cau, 0x37c000c1u, 0x00000081u, 0x00000082u}, + {0xbf7ffcffu, 0x32135b34u, 0x00000017u, 0x00000002u}, + {0x38a00141u, 0x3b490193u, 0xffffff82u, 0x8acac024u}, + {0x3820009fu, 0x3f7fffc0u, 0x0000007eu, 0x80000000u} +}; +constexpr std::uint32_t Expected[32][14] = { + {0x00007fffu, 0x00008001u, 0x0000ffffu, 0x00000000u, 0x7f800000u, 0xff800000u, 0x00000000u, 0x1fa00000u, 0x00000000u, 0x00008000u, 0x00803fc0u, 0x00808000u, 0x00000000u, 0xffffff80u}, + {0x00008001u, 0x00007fffu, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x80ffffffu, 0xa0c08080u, 0xffff0001u, 0x7fff8001u, 0x80ffbf80u, 0x80ff8001u, 0xc17fffffu, 0x00000000u}, + {0x7fff7fffu, 0x7fff8001u, 0xffffffffu, 0x00000000u, 0x7f000000u, 0xff000000u, 0x7f000000u, 0x5f400000u, 0x00000000u, 0x00008000u, 0x7f003f80u, 0x7f008000u, 0x00000000u, 0x00000000u}, + {0x00007fffu, 0x00008001u, 0x0000ffffu, 0x00000000u, 0x01000000u, 0x81000000u, 0x00000000u, 0x20400000u, 0x00010000u, 0x00018000u, 0x00804000u, 0x00808000u, 0x40000000u, 0x000000fcu}, + {0x3fff7fffu, 0x3fff8001u, 0x7fffffffu, 0x00000000u, 0x3f7fff80u, 0xbf7fff80u, 0x3f7fff00u, 0x3ebfff40u, 0xff00ff80u, 0x7f007f80u, 0x3eff3f7fu, 0x3effff80u, 0x00008000u, 0x00000000u}, + {0x4000c000u, 0x40004000u, 0x80000000u, 0x00000000u, 0xff800000u, 0xff800000u, 0x7f800000u, 0x7f000000u, 0x00000000u, 0x00008000u, 0x3f00bf00u, 0x3f008000u, 0x00000000u, 0xda210b4du}, + {0x00000001u, 0x0000ffffu, 0x00000002u, 0x00000000u, 0x00000000u, 0x80000000u, 0xffc00000u, 0x9c700050u, 0x000000a0u, 0x000080a0u, 0xffc03820u, 0xffc080a0u, 0xd8000000u, 0xffff8100u}, + {0xffff0000u, 0x00010000u, 0x00000000u, 0x00030000u, 0x7fa12345u, 0xffa12345u, 0x80000000u, 0x9b701262u, 0x01802345u, 0x0180a345u, 0xb8407fa1u, 0xb840a345u, 0xdaf4e780u, 0x72de0000u}, + {0x00010001u, 0x0001ffffu, 0x00030001u, 0x00000000u, 0x00000000u, 0x80000000u, 0x7f800000u, 0x37600140u, 0x017f0101u, 0x017f8101u, 0x38403780u, 0x38408101u, 0x80c1807fu, 0x800000fdu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000001u, 0x00010000u, 0x00018000u, 0x00000000u, 0x00008000u, 0x00000000u, 0x00009700u}, + {0x00008001u, 0x00007fffu, 0x00000000u, 0x00000000u, 0xff800000u, 0xff800000u, 0x7f800001u, 0xaa561026u, 0x0001214cu, 0x0001a14cu, 0x7f80d62cu, 0x7f80a14cu, 0x302c214cu, 0xffff6c2au}, + {0x00320000u, 0x00320000u, 0x00640000u, 0x00000000u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0x9dc50065u, 0x00c90000u, 0x00c98000u, 0x3ac9ffc0u, 0x3ac98000u, 0xcdc00000u, 0xfffffeeau}, + {0x80010003u, 0x7ffffffdu, 0x00000005u, 0xffff0000u, 0x38a00141u, 0xb8a00141u, 0xff800000u, 0x9b900020u, 0x00000141u, 0x00008141u, 0xff8038a0u, 0xff808141u, 0x5f800000u, 0x00000000u}, + {0x00007fffu, 0x00008001u, 0x0000ffffu, 0x00000000u, 0x170aa774u, 0x970aa774u, 0x80000000u, 0x7185d3bau, 0xffffa774u, 0x7fff2774u, 0x807f628au, 0x807fa774u, 0x5775588cu, 0x00004ae9u}, + {0x00647fffu, 0x00648001u, 0x00c9ffffu, 0x00000000u, 0x7f800000u, 0xff800000u, 0x7b490191u, 0x5d640149u, 0x01910000u, 0x01918000u, 0x3b497f80u, 0x3b498000u, 0xb7800000u, 0xee39b280u}, + {0x7fff8001u, 0x7fff7fffu, 0xffff0000u, 0x00000000u, 0xff800000u, 0xff800000u, 0x7f800000u, 0xbf800000u, 0x00000000u, 0x00008000u, 0x7f80ff80u, 0x7f808000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x803f7f7fu, 0x0000ffffu, 0x00007fffu, 0x8000807fu, 0x8000ffffu, 0x80000000u, 0x00004b00u}, + {0x00200000u, 0x00200000u, 0x00400000u, 0x00000000u, 0x7f800001u, 0xff800001u, 0x7b800000u, 0x5c800000u, 0x00000001u, 0x00008001u, 0x3a807f80u, 0x3a808001u, 0x3a800000u, 0x00082000u}, + {0x00000001u, 0x0000ffffu, 0x00000001u, 0x00000000u, 0x374000c0u, 0xb74000c0u, 0x7fa12345u, 0x5bb11283u, 0x234500c0u, 0x234580c0u, 0x7fa137c0u, 0x7fa180c0u, 0xff9a73c0u, 0x808fc120u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0xff800000u, 0x550b704eu, 0xdf9c0001u, 0x5f9c8001u, 0xab160000u, 0xab168001u, 0xab16df9cu, 0x4f8755f0u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff800000u, 0xff800000u, 0x00000000u, 0x4764116fu, 0x000022deu, 0x0000a2deu, 0x00008dc8u, 0x0000a2deu, 0x00000000u, 0x7fd4557fu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffc00000u, 0xffc00000u, 0xffbfffffu, 0xffbf7f80u, 0xffff0000u, 0x7fff8000u, 0xffbfffc0u, 0xffbf8000u, 0x00400000u, 0x00004caeu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x5737163au, 0x2b740000u, 0x2b748000u, 0x2d6e8000u, 0x2d6e8000u, 0x00000000u, 0x0a782ceeu}, + {0x00008001u, 0x00007fffu, 0x00000000u, 0x00000000u, 0xff800000u, 0xff800000u, 0x7f800001u, 0xb8ab6a08u, 0x0001d40fu, 0x0001540fu, 0x7f80f2d7u, 0x7f80d40fu, 0x6b57d40fu, 0xfffffe00u}, + {0x00000100u, 0x0000ff00u, 0x00000200u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x5e000000u, 0x00000000u, 0x00008000u, 0x80003c00u, 0x80008000u, 0x00000000u, 0xc781b200u}, + {0x40003fffu, 0x4000c001u, 0x80007fffu, 0x00000000u, 0x00000000u, 0x80000000u, 0x3e800001u, 0x3f807f01u, 0x0001fe01u, 0x00017e01u, 0x3f003effu, 0x3f00fe01u, 0x7dfffe01u, 0x00000095u}, + {0xfffe4000u, 0x0002c000u, 0x00008000u, 0x00050000u, 0x7f800000u, 0xff800000u, 0xff800000u, 0x7b50001fu, 0x013f0000u, 0x013f8000u, 0xb8a03f00u, 0xb8a08000u, 0x81000000u, 0x6b3b8fdeu}, + {0x7fff0064u, 0x7fffff9cu, 0xffff00c9u, 0x00000000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x41254a50u, 0x920e0192u, 0x120e8192u, 0x48013b49u, 0x48018192u, 0xf27559fcu, 0x7ffffeebu}, + {0x00010032u, 0x0001ffceu, 0x00020065u, 0x00000000u, 0x7b4900cau, 0xfb4900cau, 0x78c000c1u, 0x38c400c6u, 0x00c100cau, 0x00c180cau, 0x37c03ac9u, 0x37c080cau, 0x4f09984au, 0x00004182u}, + {0x00008003u, 0x00007ffdu, 0x00000000u, 0x00000000u, 0xcafffcffu, 0xcafffcffu, 0x33135b34u, 0x7849ab9au, 0x5b34fcffu, 0x5b347cffu, 0x3213bf7fu, 0x3213fcffu, 0xf9db08ccu, 0x0000002eu}, + {0x00650003u, 0x0065fffdu, 0x00c90005u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x3a75016au, 0x01930141u, 0x01938141u, 0x3b4938a0u, 0x3b498141u, 0x7a6af953u, 0xb0356e48u}, + {0x7fff0001u, 0x7fffffffu, 0xffff0002u, 0x00000000u, 0x7720009fu, 0xf720009fu, 0x00000000u, 0x3b4f7fafu, 0xffc0009fu, 0x7fc0809fu, 0x3f7f3820u, 0x3f7f809fu, 0x687fd840u, 0x00000000u} +}; +constexpr const char* Names[14] = {"cvt_pknorm_i16_f320", "cvt_pknorm_i16_f321", "cvt_pknorm_u16_f322", "cvt_pknorm_u16_f323", "ldexp_f324", "ldexp_f325", "ldexp_f326", "lerp_u87", "pack_b32_f168", "pack_b32_f169", "pack_b32_f1610", "pack_b32_f1611", "mul_lo_i32_0", "mul_lo_i32_1"}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop3 pack ldexp: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 14; ++i) Expect(tid, out[i], Expected[tid][i], Names[i]); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop3 pack ldexp tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp index 3ba9c509f..f6254b149 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp @@ -139,8 +139,6 @@ enum class IrOpcode : std::uint16_t { CompositeExtractU32x3, CompositeExtractU32x4, PackHalf2x16, - PackSnorm2x16, - PackUnorm2x16, PackFloat2x16Rtz, FPAbs32, FPNeg32, @@ -239,7 +237,6 @@ enum class IrOpcode : std::uint16_t { FPCos, FPExp2, FPLog2, - FPLdexp, FPRoundEven32, FPFloor32, FPCeil32, diff --git a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp index b0b7461d7..e7a06bef3 100644 --- a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp +++ b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp @@ -180,8 +180,6 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("CompositeExtractU32x3", U32, U32x3, U32), makeMeta("CompositeExtractU32x4", U32, U32x4, U32), makeMeta("PackHalf2x16", U32, F32x2), - makeMeta("PackSnorm2x16", U32, F32x2), - makeMeta("PackUnorm2x16", U32, F32x2), makeMeta("PackFloat2x16Rtz", U32, F32, F32), makeMeta("FPAbs32", F32, F32), makeMeta("FPNeg32", F32, F32), @@ -280,7 +278,6 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("FPCos", F32, F32), makeMeta("FPExp2", F32, F32), makeMeta("FPLog2", F32, F32), - makeMeta("FPLdexp", F32, F32, U32), makeMeta("FPRoundEven32", F32, F32), makeMeta("FPFloor32", F32, F32), makeMeta("FPCeil32", F32, F32), diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp index beb5b9808..7c204434c 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp @@ -55,7 +55,6 @@ std::uint32_t EmitFPRecipSqrt32(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFPSqrt(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFPExp2(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFPLog2(SpirvEmitterState& state, std::uint32_t arg0); -std::uint32_t EmitFPLdexp(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1); std::uint32_t EmitFPSin(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFPCos(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitIdentity(SpirvValueEmitContext& ctx, std::uint32_t value); @@ -170,8 +169,6 @@ std::uint32_t EmitFNegateValue(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFAbsValue(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitPackHalf2x16(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitF32ToF16BitsRte(SpirvEmitterState& state, std::uint32_t value); -std::uint32_t EmitPackSnorm2x16(SpirvEmitterState& state, std::uint32_t arg0); -std::uint32_t EmitPackUnorm2x16(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFPFma32(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2); std::uint32_t EmitFPMad32(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2); std::uint32_t EmitFPRoundEven32(SpirvEmitterState& state, std::uint32_t arg0); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp index 13b9b7244..faffe529c 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp @@ -460,11 +460,6 @@ std::uint32_t EmitFPLog2(SpirvEmitterState& state, std::uint32_t arg0) { return EmitExt(state, TypeF32(state), GLSLstd450Log2, {EmitFlushF32DenormToSignedZero(state, arg0)}); } -std::uint32_t EmitFPLdexp(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1) { - const auto exponent = Unary(state, spv::OpBitcast, TypeI32(state), arg1); - return EmitExt(state, TypeF32(state), GLSLstd450Ldexp, {arg0, exponent}); -} - std::uint32_t EmitFPSin(SpirvEmitterState& state, std::uint32_t arg0) { const auto cycle = EmitTrigCycleF32(state, arg0, true); const auto source = Binary(state, spv::OpFMul, TypeF32(state), cycle, ConstantF32(state, 0x40c90fdbu)); @@ -952,14 +947,6 @@ std::uint32_t EmitPackHalf2x16(SpirvEmitterState& state, std::uint32_t arg0) { return Binary(state, spv::OpBitwiseOr, TypeU32(state), component(0u), high); } -std::uint32_t EmitPackSnorm2x16(SpirvEmitterState& state, std::uint32_t arg0) { - return EmitGlsl(state, arg0); -} - -std::uint32_t EmitPackUnorm2x16(SpirvEmitterState& state, std::uint32_t arg0) { - return EmitGlsl(state, arg0); -} - std::uint32_t EmitFPFma32(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2) { return Exact(state, EmitGlsl(state, arg0, arg1, arg2)); } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp index 873ea24a1..d4048cec8 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp @@ -313,8 +313,6 @@ void EmitDirectInstruction(SpirvValueEmitContext& ctx, const IrValue& inst) { case IrOpcode::CompositeExtractU32x3: return Invoke(EmitCompositeExtractU32x3, ctx, inst); case IrOpcode::CompositeExtractU32x4: return Invoke(EmitCompositeExtractU32x4, ctx, inst); case IrOpcode::PackHalf2x16: return Invoke(EmitPackHalf2x16, ctx, inst); - case IrOpcode::PackSnorm2x16: return Invoke(EmitPackSnorm2x16, ctx, inst); - case IrOpcode::PackUnorm2x16: return Invoke(EmitPackUnorm2x16, ctx, inst); case IrOpcode::PackFloat2x16Rtz: return Invoke(EmitPackFloat2x16Rtz, ctx, inst); case IrOpcode::FPAbs32: return Invoke(EmitFPAbs32, ctx, inst); case IrOpcode::FPNeg32: return Invoke(EmitFPNeg32, ctx, inst); @@ -413,7 +411,6 @@ void EmitDirectInstruction(SpirvValueEmitContext& ctx, const IrValue& inst) { case IrOpcode::FPCos: return Invoke(EmitFPCos, ctx, inst); case IrOpcode::FPExp2: return Invoke(EmitFPExp2, ctx, inst); case IrOpcode::FPLog2: return Invoke(EmitFPLog2, ctx, inst); - case IrOpcode::FPLdexp: return Invoke(EmitFPLdexp, ctx, inst); case IrOpcode::FPRoundEven32: return Invoke(EmitFPRoundEven32, ctx, inst); case IrOpcode::FPFloor32: return Invoke(EmitFPFloor32, ctx, inst); case IrOpcode::FPCeil32: return Invoke(EmitFPCeil32, ctx, inst); diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index b5319134f..2394b1f12 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -145,7 +145,7 @@ private: void vFrexpExpI32F32(const RdnaInstruction& inst); void vCvtOffF32I4(const RdnaInstruction& inst); void vCvtPkrtzF16F32(const RdnaInstruction& inst); - void vCvtPknormF32(const RdnaInstruction& inst, IrOpcode opcode); + void vCvtPknormF32(const RdnaInstruction& inst, bool signedValue); void vCvtPkU8F32(const RdnaInstruction& inst); void vPackB32F16(const RdnaInstruction& inst); bool packedFloat16(const RdnaInstruction& inst, IrOpcode opcode, bool accumulator, bool quietSnan); @@ -162,8 +162,10 @@ private: bool vDivFixupF32(const RdnaInstruction& inst); bool vFrexpMantF32(const RdnaInstruction& inst); bool vFmaLegacyF32(const RdnaInstruction& inst); + bool vLdexpF32(const RdnaInstruction& inst); IrU32 readF16Bits(const RdnaOperand& operand); IrU32 normF16(IrU32 bits, bool signedValue); + IrU32 normF32(IrU32 bits, bool signedValue); bool vLdexpF16(const RdnaInstruction& inst); bool vFrexpF16(const RdnaInstruction& inst, bool exponent); bool vCvtNormF16(const RdnaInstruction& inst, bool signedValue); diff --git a/core/shader/recompiler/Translation/src/ConvertInstructions.cpp b/core/shader/recompiler/Translation/src/ConvertInstructions.cpp index 0a03104de..411039837 100644 --- a/core/shader/recompiler/Translation/src/ConvertInstructions.cpp +++ b/core/shader/recompiler/Translation/src/ConvertInstructions.cpp @@ -151,11 +151,10 @@ void TranslationContext::vCvtPkrtzF16F32(const RdnaInstruction& inst) { writeOperand(inst.destination, &result.Value()); } -void TranslationContext::vCvtPknormF32(const RdnaInstruction& inst, IrOpcode opcode) { - IrValue* lhs = readOperand(sourceAt(inst, 0u), IrType::F32); - IrValue* rhs = readOperand(sourceAt(inst, 1u), IrType::F32); - IrValue& pair = ir.Emit(IrOpcode::CompositeConstructF32x2, IrType::F32x2, {lhs, rhs}); - const IrU32 result(ir.Emit(opcode, IrType::U32, {&pair})); +void TranslationContext::vCvtPknormF32(const RdnaInstruction& inst, bool signedValue) { + const IrU32 low = normF32(readU32(sourceAt(inst, 0u)), signedValue); + const IrU32 high = normF32(readU32(sourceAt(inst, 1u)), signedValue); + const IrU32 result(ir.BitwiseOr(low.Value(), ir.ShiftLeftLogical(high.Value(), ir.Constant(16u)))); writeOperand(inst.destination, &result.Value()); } @@ -171,8 +170,8 @@ void TranslationContext::vCvtPkU8F32(const RdnaInstruction& inst) { } void TranslationContext::vPackB32F16(const RdnaInstruction& inst) { - const IrU32 low = readF16LaneBits(sourceAt(inst, 0u), false); - const IrU32 high(ir.ShiftLeftLogical(readF16LaneBits(sourceAt(inst, 1u), false).Value(), ir.Constant(16u))); + const IrU32 low = readF16Bits(sourceAt(inst, 0u)); + const IrU32 high(ir.ShiftLeftLogical(readF16Bits(sourceAt(inst, 1u)).Value(), ir.Constant(16u))); const IrU32 result(ir.BitwiseOr(low.Value(), high.Value())); writeOperand(inst.destination, &result.Value()); } diff --git a/core/shader/recompiler/Translation/src/FloatInstructions.cpp b/core/shader/recompiler/Translation/src/FloatInstructions.cpp index 079497ea8..8bc1a52d4 100644 --- a/core/shader/recompiler/Translation/src/FloatInstructions.cpp +++ b/core/shader/recompiler/Translation/src/FloatInstructions.cpp @@ -172,6 +172,31 @@ IrU32 TranslationContext::normF16(IrU32 bits, bool signedValue) { return IrU32(ir.BitwiseAnd(ir.Select(nan.Value(), ir.Constant(0u), value.Value()), ir.Constant(0xffffu))); } +IrU32 TranslationContext::normF32(IrU32 bits, bool signedValue) { + const IrU32 magnitude(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffffffu))); + const IrU1 negative(ir.INotEqual(ir.BitwiseAnd(bits.Value(), ir.Constant(0x80000000u)), ir.Constant(0u))); + const IrU1 nan(ir.UGreaterThan(magnitude.Value(), ir.Constant(0x7f800000u))); + const IrU1 saturated(ir.Emit(IrOpcode::UGreaterThanEqual32, IrType::U1, {&magnitude.Value(), &ir.Constant(0x3f800000u)})); + const std::uint32_t scale = signedValue ? 32767u : 65535u; + const IrU32 exponent(ir.ShiftRightLogical(magnitude.Value(), ir.Constant(23u))); + const IrU32 mantissa(ir.BitwiseOr(ir.BitwiseAnd(magnitude.Value(), ir.Constant(0x7fffffu)), ir.Constant(0x800000u))); + const IrU32 shift(ir.Emit(IrOpcode::UMin32, IrType::U32, {&ir.ISub(ir.Constant(149u), exponent.Value()), &ir.Constant(63u)})); + const IrU64 product(ir.Emit(IrOpcode::IMul64, IrType::U64, {&ir.ConstructU64(mantissa.Value(), ir.Constant(0u)), &ir.ConstantU64(scale)})); + const IrU64 halves(ir.Emit(IrOpcode::ShiftRightLogical64, IrType::U64, {&product.Value(), &shift.Value()})); + const IrU1 sticky(ir.Emit(IrOpcode::INotEqual64, IrType::U1, {&ir.Emit(IrOpcode::ShiftLeftLogical64, IrType::U64, {&halves.Value(), &shift.Value()}), &product.Value()})); + const IrU32 doubled = extractU64(halves)[0]; + const IrU32 truncated(ir.ShiftRightLogical(doubled.Value(), ir.Constant(1u))); + const IrU1 roundUp(ir.LogicalAnd(ir.INotEqual(ir.BitwiseAnd(doubled.Value(), ir.Constant(1u)), ir.Constant(0u)), + ir.LogicalOr(sticky.Value(), ir.INotEqual(ir.BitwiseAnd(truncated.Value(), ir.Constant(1u)), ir.Constant(0u))))); + IrU32 value(ir.Select(saturated.Value(), ir.Constant(scale), ir.IAdd(truncated.Value(), ir.Select(roundUp.Value(), ir.Constant(1u), ir.Constant(0u))))); + if (signedValue) { + value = IrU32(ir.Select(negative.Value(), ir.ISub(ir.Constant(0u), value.Value()), value.Value())); + } else { + value = IrU32(ir.Select(negative.Value(), ir.Constant(0u), value.Value())); + } + return IrU32(ir.BitwiseAnd(ir.Select(nan.Value(), ir.Constant(0u), value.Value()), ir.Constant(0xffffu))); +} + bool TranslationContext::vLdexpF16(const RdnaInstruction& inst) { const IrF32 value = readF16AsF32(sourceAt(inst, 0u)); const IrU32 exponent(ir.Emit(IrOpcode::BitFieldSExtract, IrType::U32, {&readU32(sourceAt(inst, 1u)).Value(), &ir.Constant(0u), &ir.Constant(16u)})); @@ -426,6 +451,23 @@ bool TranslationContext::vFrexpMantF32(const RdnaInstruction& inst) { return true; } +bool TranslationContext::vLdexpF32(const RdnaInstruction& inst) { + const IrU32 bits = readU32(sourceAt(inst, 0u)); + const IrU32 offset = readU32(sourceAt(inst, 1u)); + const IrU32 sign(ir.BitwiseAnd(bits.Value(), ir.Constant(0x80000000u))); + const IrU32 exponent(ir.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&bits.Value(), &ir.Constant(23u), &ir.Constant(8u)})); + const IrU32 clamped(ir.Emit(IrOpcode::SMax32, IrType::U32, {&ir.Emit(IrOpcode::SMin32, IrType::U32, {&offset.Value(), &ir.Constant(512u)}), &ir.Constant(static_cast(-512))})); + const IrU32 scaled(ir.IAdd(exponent.Value(), clamped.Value())); + const IrU32 normal(ir.BitwiseOr(ir.BitwiseAnd(bits.Value(), ir.Constant(0x807fffffu)), ir.ShiftLeftLogical(scaled.Value(), ir.Constant(23u)))); + const IrU32 overflow(ir.BitwiseOr(sign.Value(), ir.Constant(0x7f800000u))); + IrU32 result(ir.Select(ir.Emit(IrOpcode::SGreaterThanEqual32, IrType::U1, {&scaled.Value(), &ir.Constant(255u)}), overflow.Value(), normal.Value())); + result = IrU32(ir.Select(ir.Emit(IrOpcode::SLessThanEqual32, IrType::U1, {&scaled.Value(), &ir.Constant(0u)}), sign.Value(), result.Value())); + result = IrU32(ir.Select(ir.IEqual(exponent.Value(), ir.Constant(0xffu)), bits.Value(), result.Value())); + result = IrU32(ir.Select(ir.IEqual(exponent.Value(), ir.Constant(0u)), sign.Value(), result.Value())); + writeOperand(inst.destination, &ir.BitCastF32(result.Value())); + return true; +} + bool TranslationContext::vDot2cF32F16(const RdnaInstruction& inst) { RdnaOperand a = sourceAt(inst, 0u); a.opSel = false; diff --git a/core/shader/recompiler/Translation/src/VectorInstructions.cpp b/core/shader/recompiler/Translation/src/VectorInstructions.cpp index df5505de1..7d10b7354 100644 --- a/core/shader/recompiler/Translation/src/VectorInstructions.cpp +++ b/core/shader/recompiler/Translation/src/VectorInstructions.cpp @@ -708,10 +708,10 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { vCvtPkrtzF16F32(inst); return true; case RdnaOpcode::VCvtPknormI16F32: - vCvtPknormF32(inst, IrOpcode::PackSnorm2x16); + vCvtPknormF32(inst, true); return true; case RdnaOpcode::VCvtPknormU16F32: - vCvtPknormF32(inst, IrOpcode::PackUnorm2x16); + vCvtPknormF32(inst, false); return true; case RdnaOpcode::VCvtPkU8F32: vCvtPkU8F32(inst); @@ -933,7 +933,7 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { case RdnaOpcode::VDivFixupF32: return vDivFixupF32(inst); case RdnaOpcode::VLdexpF32: - return floatBinary(inst, IrOpcode::FPLdexp, false); + return vLdexpF32(inst); case RdnaOpcode::VMacF32: return floatTernary(inst, roundsProductSeparately(inst) ? IrOpcode::FPMad32 : IrOpcode::FPFma32, true, true); case RdnaOpcode::VMadmkF32: From b4151114a17282ca4fdf44e053885e0632881bb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 03:50:30 +0200 Subject: [PATCH 061/164] test(shader): execute the flat D16 loads against hardware results Co-Authored-By: Claude --- .../tests/execution/FlatD16Loads.cpp | 570 ++++++++++++++++++ 1 file changed, 570 insertions(+) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/FlatD16Loads.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatD16Loads.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatD16Loads.cpp new file mode 100644 index 000000000..3dfd1167e --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatD16Loads.cpp @@ -0,0 +1,570 @@ +#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::uint32_t Threads = 32; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array FlatD16LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0101, 0x7e1802ff, 0x5a5a0202, 0x7e1a02ff, 0x5a5a0303, 0x7e1c02ff, 0x5a5a0404, 0x7e1e02ff, + 0x5a5a0505, 0x7e2002ff, 0x5a5a0606, 0x7e2202ff, 0x5a5a0707, 0x7e2402ff, 0x5a5a0808, 0x7e2602ff, + 0x5a5a0909, 0x7e2802ff, 0x5a5a0a0a, 0x7e2a02ff, 0x5a5a0b0b, 0x7e2c02ff, 0x5a5a0c0c, 0x7e2e02ff, + 0x5a5a0d0d, 0x7e3002ff, 0x5a5a0e0e, 0x7e3202ff, 0x5a5a0f0f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc800000, 0x0a7d0024, 0xdc8407ff, + 0x0b7d0024, 0xdc880001, 0x0c7d0024, 0xdc8c0003, 0x0d7d0024, 0xdc900001, 0x0e7d0024, 0xdc9407fe, + 0x0f7d0024, 0xdc900005, 0x107d0024, 0xdc8c0407, 0x107d0024, 0xdc840400, 0x117d0024, 0xdc880009, + 0x117d0024, 0xdc940007, 0x127d0024, 0xdc8007f8, 0x127d0024, 0xdc8807ff, 0x137d0024, 0xdc90000c, + 0x137d0024, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, + 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 +}; + +constexpr std::array Region{ + 0xd2, 0xf2, 0x26, 0x65, 0xa6, 0x0c, 0x12, 0xd2, 0x89, 0x18, 0x5d, 0x95, 0x0e, 0xe8, 0x81, 0x36, + 0x09, 0x16, 0x6f, 0x6b, 0x11, 0x3d, 0x17, 0x8d, 0x6c, 0x0f, 0xd3, 0x90, 0x1f, 0xf2, 0x39, 0xa1, + 0xa0, 0x95, 0xf2, 0x0f, 0x93, 0x95, 0x65, 0x0c, 0xf9, 0x38, 0x0b, 0x8e, 0xdb, 0x22, 0x4a, 0x6b, + 0x24, 0x8a, 0x1e, 0x92, 0x4e, 0x8f, 0xd0, 0xae, 0x2e, 0x1a, 0x94, 0x92, 0xa3, 0x30, 0x5f, 0x18, + 0x8c, 0xb6, 0x10, 0x90, 0x0f, 0x9e, 0x34, 0x7f, 0xae, 0x88, 0x6d, 0xc6, 0x50, 0x77, 0x95, 0xec, + 0x74, 0x5c, 0x4c, 0x3f, 0xcb, 0x2e, 0xb2, 0xc7, 0x3e, 0x14, 0x93, 0x4c, 0x86, 0x7e, 0xe0, 0x57, + 0xba, 0xf2, 0x49, 0x9b, 0xfa, 0x12, 0x1e, 0x83, 0x6b, 0x2a, 0xc1, 0x57, 0x26, 0xee, 0x7d, 0x6b, + 0x0a, 0xf6, 0xab, 0x13, 0xc3, 0x8e, 0x92, 0xca, 0xe0, 0xd1, 0x50, 0x57, 0xb1, 0x59, 0x98, 0x7f, + 0x94, 0xcc, 0x74, 0x11, 0xd7, 0x17, 0xf1, 0x45, 0x79, 0xb2, 0xaa, 0x10, 0x0f, 0xbb, 0xb3, 0x4f, + 0xa5, 0x93, 0xfe, 0xae, 0xd2, 0x72, 0x48, 0xb7, 0x62, 0xe3, 0xab, 0x58, 0x05, 0xf0, 0x76, 0x5a, + 0x2b, 0x9c, 0x1d, 0x7e, 0x0f, 0x37, 0xc4, 0x49, 0x21, 0xbd, 0x3f, 0x65, 0x64, 0xea, 0xdf, 0x7f, + 0x14, 0x2a, 0x72, 0x66, 0x8c, 0x47, 0xe2, 0x23, 0xd1, 0x6e, 0xdd, 0x8c, 0x47, 0xb4, 0x6a, 0xfc, + 0x5b, 0xae, 0xe2, 0x61, 0xf5, 0x3b, 0x26, 0x15, 0x2d, 0x26, 0x3b, 0xa8, 0x3b, 0x03, 0x7c, 0xd4, + 0x96, 0x2e, 0x43, 0x48, 0x01, 0x25, 0x6b, 0x88, 0x5e, 0x9c, 0x90, 0x51, 0xf3, 0x20, 0xb0, 0xdb, + 0x83, 0xf3, 0x9e, 0xa7, 0xad, 0xbd, 0x0d, 0x74, 0xe6, 0xde, 0xc7, 0xf3, 0xdf, 0xae, 0xcc, 0x8f, + 0x64, 0x65, 0x66, 0x64, 0x1a, 0x7b, 0xa2, 0x66, 0x0f, 0x30, 0x11, 0xfc, 0x35, 0x70, 0x29, 0x1c, + 0x57, 0x99, 0x0d, 0x1a, 0x00, 0x91, 0x26, 0x89, 0x19, 0xf2, 0x5d, 0x9d, 0x06, 0x12, 0xdf, 0x35, + 0x9d, 0x60, 0x26, 0xa2, 0x40, 0xf4, 0x58, 0x9a, 0x5d, 0x79, 0x1f, 0x1d, 0xd9, 0x7c, 0xfe, 0xfa, + 0x77, 0x7a, 0x7b, 0xcf, 0x15, 0x24, 0x1a, 0xbf, 0x57, 0xbd, 0x43, 0x7a, 0xd4, 0xb1, 0x29, 0x84, + 0x05, 0x34, 0xf3, 0xf3, 0x87, 0x5c, 0x25, 0xb0, 0x8b, 0xea, 0x06, 0xc2, 0x87, 0x4c, 0xfa, 0xa4, + 0xdd, 0x17, 0xb2, 0xd8, 0x42, 0x84, 0x5d, 0xe8, 0x2a, 0x5b, 0xc5, 0x39, 0x88, 0x8a, 0xc7, 0x80, + 0x54, 0xa2, 0x39, 0x9c, 0xcf, 0xc9, 0xfc, 0xc2, 0xda, 0x31, 0xce, 0x3d, 0xd1, 0x66, 0xbd, 0xcd, + 0x3a, 0x33, 0x84, 0x7e, 0x5b, 0xbb, 0x07, 0xfd, 0x07, 0xca, 0x47, 0x78, 0x42, 0x31, 0xb1, 0x9a, + 0xf4, 0x58, 0x72, 0xce, 0xef, 0xb9, 0xfc, 0x59, 0xf4, 0xf9, 0x5d, 0x14, 0x38, 0x1a, 0x3a, 0x78, + 0x32, 0x56, 0x34, 0x7b, 0x9f, 0xfc, 0xe6, 0x9c, 0xd7, 0x00, 0x7a, 0xe8, 0xa7, 0x58, 0xcc, 0xa4, + 0x15, 0xd5, 0xa9, 0x1e, 0xe8, 0x63, 0xc8, 0xb6, 0xc0, 0x33, 0x7a, 0xe3, 0x2d, 0x6f, 0xca, 0xa2, + 0x55, 0x16, 0xcd, 0xf2, 0xf8, 0xb8, 0x65, 0x76, 0x66, 0xbe, 0xf2, 0x15, 0xb9, 0x28, 0x2b, 0xfe, + 0x20, 0x07, 0x26, 0x97, 0xe7, 0x77, 0xce, 0xa7, 0x25, 0x9c, 0xd3, 0x98, 0xfa, 0x79, 0xa8, 0xef, + 0x59, 0x27, 0x8c, 0x8c, 0x21, 0x05, 0x03, 0xcc, 0xf8, 0xb9, 0xa6, 0x1a, 0x86, 0xbf, 0xef, 0x23, + 0x6f, 0xfc, 0xdf, 0x31, 0xd3, 0xdf, 0x36, 0x07, 0x40, 0x36, 0x4a, 0x80, 0x3d, 0xc3, 0x96, 0x53, + 0x42, 0x8b, 0x6b, 0xd5, 0x21, 0x8f, 0xe8, 0xbd, 0x5a, 0xe5, 0x75, 0xa9, 0x95, 0xd0, 0xe7, 0x84, + 0x6b, 0xd3, 0xea, 0xe0, 0x80, 0x21, 0x88, 0x26, 0x86, 0x82, 0x04, 0xdf, 0x70, 0xc6, 0x2e, 0x9b, + 0x01, 0xc6, 0xcc, 0x26, 0x2c, 0x24, 0x79, 0x9e, 0xb9, 0x1e, 0x8e, 0x0f, 0x53, 0xae, 0x84, 0x87, + 0x8e, 0x7b, 0xc8, 0xc6, 0x1b, 0xe2, 0x8f, 0x0e, 0x3f, 0x30, 0x46, 0x0a, 0xc5, 0x19, 0x81, 0x73, + 0x8f, 0x07, 0xc2, 0xe4, 0xe9, 0x10, 0x71, 0x53, 0x9c, 0xf9, 0x81, 0x9b, 0x83, 0x33, 0xb1, 0x46, + 0x73, 0x82, 0x88, 0xce, 0x7a, 0x81, 0xf1, 0x3f, 0xb2, 0x85, 0xe0, 0xe0, 0xf1, 0xed, 0x42, 0xec, + 0x8f, 0xe4, 0xf1, 0x33, 0xd7, 0x72, 0xa3, 0x6a, 0x1f, 0x64, 0x71, 0x50, 0x12, 0xab, 0x3d, 0x6d, + 0x12, 0x36, 0xab, 0x4d, 0xc8, 0x1f, 0xe5, 0xc6, 0x27, 0xf0, 0xb7, 0xa4, 0xa9, 0x5d, 0x24, 0x40, + 0xe2, 0x23, 0xf7, 0x77, 0x38, 0xbf, 0xf3, 0x18, 0x65, 0xe2, 0x7c, 0x29, 0xfd, 0xaa, 0xd5, 0x39, + 0x29, 0xb4, 0x6e, 0xfe, 0x83, 0x67, 0x56, 0x6b, 0x32, 0x5b, 0x51, 0x17, 0xb8, 0x5d, 0x04, 0x56, + 0x8d, 0x75, 0x70, 0xb4, 0x04, 0x62, 0x54, 0x84, 0x9f, 0x4b, 0x83, 0xf5, 0x10, 0x1c, 0xfc, 0xeb, + 0xc9, 0x3a, 0xf8, 0xe0, 0x1a, 0x15, 0x43, 0x45, 0x0a, 0xe7, 0xc7, 0x2e, 0x45, 0xc1, 0x21, 0xd1, + 0x6c, 0xd9, 0xe9, 0xad, 0xd1, 0xf2, 0x42, 0xe7, 0x26, 0x89, 0xeb, 0x83, 0x92, 0x7e, 0xb3, 0x53, + 0x16, 0x47, 0x0e, 0xcc, 0xb0, 0x2e, 0x6c, 0xe5, 0x12, 0x44, 0xf0, 0x04, 0xa2, 0x16, 0xcd, 0x42, + 0x15, 0x9b, 0xdb, 0x38, 0x11, 0x43, 0xdc, 0x1f, 0x74, 0x02, 0x56, 0xfe, 0x8d, 0x6a, 0xed, 0xea, + 0x44, 0x9f, 0x21, 0x0b, 0x86, 0xb5, 0x3d, 0xf0, 0x1c, 0xf8, 0x29, 0x43, 0x0c, 0x2e, 0x33, 0xee, + 0x4f, 0xa0, 0x4e, 0x87, 0xc2, 0x34, 0x4a, 0x72, 0x80, 0xac, 0x2d, 0x45, 0x58, 0xcd, 0x04, 0xfe, + 0x40, 0x09, 0x03, 0x04, 0xbb, 0x81, 0x8d, 0xfa, 0x30, 0x83, 0x79, 0x3e, 0xef, 0x72, 0x1b, 0xa8, + 0xd1, 0xa6, 0x6e, 0xa8, 0x7e, 0x8b, 0xd5, 0xe3, 0xe4, 0xf8, 0x81, 0x4e, 0xb0, 0x37, 0xfb, 0x3a, + 0x57, 0x32, 0xd5, 0xe1, 0xb4, 0xba, 0xa2, 0x23, 0x67, 0xfd, 0x58, 0xfb, 0x0d, 0xd6, 0x21, 0x03, + 0x12, 0xa0, 0xbd, 0xe1, 0x41, 0x6e, 0x29, 0x0e, 0x15, 0xaa, 0xd7, 0x61, 0xde, 0x81, 0xab, 0xf8, + 0x48, 0x99, 0x3e, 0xb1, 0x4b, 0x0b, 0x75, 0x2f, 0x28, 0x44, 0x72, 0x00, 0x43, 0x5d, 0xf6, 0x54, + 0xf8, 0xfc, 0x8c, 0x52, 0x3e, 0x08, 0xf7, 0xe1, 0x4f, 0x37, 0x5b, 0x2e, 0x00, 0x55, 0x61, 0x15, + 0x79, 0x47, 0x80, 0xa7, 0x33, 0x3f, 0x81, 0xc6, 0x01, 0x17, 0x43, 0xd1, 0x16, 0x24, 0x66, 0x96, + 0x0a, 0x64, 0x05, 0x4c, 0x4d, 0xa1, 0x3b, 0x15, 0x95, 0xf5, 0x87, 0xda, 0xc0, 0x27, 0xa8, 0xe4, + 0xb7, 0xc8, 0xe1, 0x98, 0x63, 0xc3, 0x53, 0xb8, 0xfc, 0x7e, 0x26, 0x48, 0xb9, 0x9e, 0xa4, 0x25, + 0x0b, 0xd3, 0xd5, 0xb7, 0xe4, 0x83, 0xa0, 0x6d, 0xbb, 0xb3, 0xcf, 0x81, 0x23, 0xe8, 0x86, 0xc0, + 0x81, 0x91, 0xd5, 0xd0, 0xcd, 0x04, 0xd3, 0xaf, 0x95, 0xcc, 0xe4, 0xb6, 0xae, 0xf4, 0xb1, 0xa4, + 0x3a, 0x15, 0x07, 0x0a, 0x22, 0xa3, 0x5c, 0xf5, 0x1a, 0x60, 0xd5, 0x73, 0x8e, 0x0c, 0xa0, 0x04, + 0xa0, 0x88, 0xae, 0x3e, 0x7d, 0x43, 0x00, 0x74, 0xcc, 0x11, 0xbf, 0xee, 0x80, 0xe5, 0x89, 0x17, + 0xa8, 0x86, 0x10, 0xbe, 0xbc, 0x79, 0x40, 0xcf, 0x13, 0xd8, 0xc3, 0x3c, 0xba, 0xc1, 0x34, 0x3b, + 0xbd, 0xa6, 0xf9, 0x75, 0x7e, 0xd8, 0x61, 0x13, 0x7a, 0xe9, 0xaf, 0x49, 0xc4, 0x0b, 0x9d, 0xa1, + 0xa4, 0x32, 0x13, 0x99, 0x25, 0x54, 0x41, 0xa6, 0xbe, 0xb1, 0x4d, 0x9f, 0x91, 0x22, 0x03, 0x7b, + 0x0f, 0x7c, 0x44, 0xf8, 0xac, 0x19, 0xb1, 0x37, 0xac, 0x7d, 0x4a, 0xb5, 0x84, 0x49, 0x76, 0x77, + 0x77, 0xc4, 0x1e, 0xfe, 0xe4, 0x8c, 0x33, 0x4f, 0xfa, 0x15, 0xef, 0x79, 0x04, 0x4a, 0x75, 0x13, + 0xd1, 0x81, 0xf7, 0xfe, 0x73, 0xfe, 0x44, 0x63, 0x35, 0xea, 0xf2, 0xee, 0x35, 0x13, 0x94, 0x17, + 0x24, 0xbf, 0x86, 0x43, 0xf3, 0x5c, 0x21, 0x9a, 0xd1, 0xa1, 0x82, 0xc7, 0xe3, 0x1c, 0xb4, 0x5d, + 0x3b, 0x7f, 0xe5, 0xe0, 0x7c, 0x64, 0x06, 0x28, 0x00, 0xf3, 0x7d, 0xae, 0x73, 0x67, 0x4d, 0xba, + 0x24, 0x6a, 0x58, 0x60, 0x50, 0x1e, 0xd7, 0x54, 0x00, 0x53, 0xc0, 0x56, 0xd6, 0x65, 0x1e, 0xf0, + 0xed, 0x32, 0xb6, 0x03, 0xe6, 0xbd, 0x4a, 0x40, 0x5f, 0x10, 0x64, 0x63, 0xff, 0xde, 0x96, 0x13, + 0x5c, 0xec, 0x6d, 0xc1, 0x46, 0xda, 0x0c, 0x47, 0x1a, 0x0d, 0xd5, 0xa9, 0x49, 0xa2, 0xef, 0x26, + 0x3f, 0xf8, 0x44, 0x6f, 0x82, 0x50, 0x30, 0xc5, 0x5f, 0xc8, 0xf4, 0x6d, 0xe2, 0x07, 0xcf, 0xc2, + 0xa1, 0x66, 0xe9, 0xe0, 0xf0, 0x8d, 0x8c, 0x34, 0xb8, 0x14, 0x0c, 0xee, 0xbb, 0x69, 0x73, 0x9d, + 0xc0, 0x23, 0xa4, 0xde, 0x49, 0x7c, 0x0c, 0xe9, 0xed, 0x8c, 0x20, 0x2b, 0x78, 0x6a, 0x57, 0x48, + 0x4c, 0x41, 0xbd, 0xbd, 0xf9, 0xa7, 0x42, 0x67, 0xa7, 0x3d, 0x4d, 0x7b, 0x8e, 0xab, 0x64, 0x1e, + 0x2a, 0xa4, 0x29, 0x13, 0x35, 0x80, 0xe7, 0xcf, 0x7f, 0x8c, 0x38, 0x73, 0xe8, 0x55, 0xff, 0xc2, + 0x73, 0x6d, 0x23, 0x8c, 0x31, 0x3e, 0x17, 0x2c, 0x57, 0x8e, 0x17, 0x51, 0x3d, 0x5e, 0x42, 0xcf, + 0x91, 0x33, 0xe3, 0x05, 0xbf, 0xde, 0x69, 0x62, 0x69, 0xbe, 0x86, 0x35, 0x60, 0x45, 0x56, 0xc0, + 0x0f, 0x7f, 0x47, 0x93, 0xf7, 0x5c, 0x20, 0xaf, 0x80, 0x87, 0xa1, 0xca, 0xdc, 0xd9, 0x37, 0x17, + 0x45, 0xe5, 0x3f, 0x62, 0x66, 0xa5, 0x72, 0x6e, 0xf4, 0x4f, 0xd9, 0xd0, 0xdf, 0xf7, 0x05, 0x20, + 0x08, 0x6c, 0xb5, 0xc3, 0xe5, 0xcd, 0x79, 0xf7, 0x96, 0x7d, 0x00, 0x12, 0x64, 0xee, 0xed, 0xed, + 0xd3, 0x87, 0xda, 0x77, 0xf8, 0x72, 0x3f, 0xc8, 0x1b, 0x39, 0x27, 0x26, 0x85, 0xf8, 0xae, 0x1b, + 0xf1, 0xd3, 0xb8, 0xb3, 0xa5, 0xd8, 0xc3, 0xe5, 0x75, 0x15, 0x8d, 0xc6, 0x0a, 0x00, 0xc8, 0x20, + 0x3b, 0x91, 0xeb, 0x09, 0xa5, 0xb7, 0x4d, 0xf6, 0x20, 0xa0, 0x40, 0x87, 0xa2, 0x6f, 0xb2, 0xc3, + 0x1c, 0x19, 0x12, 0x4c, 0x86, 0xf1, 0x95, 0x31, 0x63, 0x42, 0x39, 0xca, 0x99, 0x00, 0x82, 0x89, + 0x4d, 0xff, 0x75, 0x47, 0xf5, 0x50, 0xa5, 0xd6, 0xe2, 0x3e, 0x79, 0x86, 0x3c, 0x8c, 0x3f, 0x07, + 0xf5, 0x69, 0xb4, 0xa6, 0x4e, 0x0e, 0x05, 0x31, 0x7f, 0xe2, 0xac, 0xa5, 0x6b, 0x14, 0x41, 0x3a, + 0xaa, 0x6c, 0xec, 0x5e, 0x3a, 0x7e, 0x08, 0xb2, 0x56, 0xb7, 0x6b, 0x5c, 0xae, 0x65, 0x32, 0x01, + 0xcc, 0x4a, 0xbd, 0xd8, 0x81, 0x11, 0x34, 0x7e, 0xf8, 0x33, 0x4f, 0xc4, 0xd1, 0x31, 0x3b, 0x77, + 0x38, 0x43, 0xc2, 0xe3, 0x4b, 0x1b, 0xf3, 0x9f, 0x7e, 0x9c, 0x2f, 0xe5, 0x39, 0x7c, 0x6a, 0xe9, + 0xaa, 0x0e, 0xf2, 0x98, 0x25, 0xec, 0x64, 0x0d, 0x36, 0x06, 0xf9, 0x98, 0x24, 0x6a, 0x0d, 0xb5, + 0x0f, 0x2f, 0x64, 0x73, 0xe5, 0xb6, 0xe2, 0x50, 0xbb, 0x1c, 0xff, 0x14, 0xee, 0x2a, 0x54, 0x30, + 0x2f, 0xa7, 0xef, 0x86, 0xbf, 0x77, 0x08, 0x4f, 0xaa, 0xb9, 0x60, 0xd6, 0x5f, 0xfc, 0x54, 0x71, + 0x2b, 0x1b, 0x00, 0x14, 0x47, 0x14, 0x59, 0x6b, 0xf4, 0xe2, 0x1f, 0x8f, 0xf6, 0xc2, 0x35, 0x61, + 0x5b, 0xc4, 0xd2, 0x4f, 0xd2, 0xcd, 0x6e, 0x16, 0x0c, 0xb4, 0x79, 0x32, 0x5f, 0x8a, 0xeb, 0x72, + 0x31, 0x52, 0x5d, 0xbc, 0xe5, 0x79, 0x07, 0xa1, 0x69, 0x3f, 0xcf, 0xa0, 0xc4, 0x67, 0x0a, 0x60, + 0x08, 0x76, 0x10, 0xcd, 0xeb, 0x0f, 0x41, 0x31, 0xbf, 0x10, 0xe6, 0x9b, 0x56, 0x5c, 0x45, 0x55, + 0xf5, 0xf4, 0x9d, 0x0b, 0x43, 0xbf, 0xb7, 0xb0, 0x51, 0xec, 0x46, 0x4c, 0x00, 0xb8, 0xc1, 0x98, + 0xea, 0xce, 0xa2, 0xf2, 0xf1, 0x10, 0x06, 0xd3, 0x3b, 0x1b, 0x79, 0xb7, 0xf4, 0x77, 0xf4, 0xc6, + 0x62, 0xca, 0x40, 0xe9, 0x6e, 0xd0, 0x7e, 0x21, 0xed, 0x7f, 0x2e, 0x02, 0xcd, 0xee, 0xbd, 0x4d, + 0xd2, 0xb1, 0xc5, 0x26, 0x9b, 0x3c, 0x53, 0xdc, 0x51, 0x75, 0x5c, 0xc8, 0xc8, 0x98, 0x14, 0x83, + 0x32, 0x64, 0xc0, 0x28, 0x3f, 0x68, 0x10, 0xa6, 0x08, 0x7b, 0x8d, 0x8b, 0x53, 0x29, 0xfa, 0x6d, + 0xe2, 0x1a, 0xfc, 0x12, 0x43, 0x9f, 0x15, 0x35, 0x18, 0x6b, 0x7f, 0xfd, 0xb5, 0xf8, 0x72, 0x2c, + 0x3b, 0xa2, 0x6a, 0x75, 0x9e, 0xe4, 0xac, 0x3c, 0xbf, 0x89, 0xd8, 0xc6, 0xaa, 0xc2, 0x1f, 0xc7, + 0xd7, 0x4b, 0x4b, 0x47, 0x91, 0x44, 0x5f, 0x41, 0xbc, 0x42, 0x32, 0x70, 0x3f, 0x2f, 0x3e, 0x3c, + 0x27, 0x48, 0xe2, 0xe8, 0x94, 0x30, 0x53, 0x10, 0x65, 0x40, 0xfe, 0x3e, 0x81, 0x86, 0x3b, 0xa6, + 0xce, 0x19, 0xa7, 0x76, 0xfd, 0x09, 0x1a, 0x01, 0x79, 0xe2, 0xd1, 0x3b, 0xd7, 0x72, 0xea, 0x5f, + 0x0a, 0xe0, 0x4b, 0x3b, 0x1e, 0x0c, 0x30, 0x99, 0xf9, 0xd3, 0x95, 0x31, 0xee, 0x13, 0x5f, 0x83, + 0xdd, 0x2d, 0x72, 0x9a, 0x42, 0xc6, 0xc7, 0xaa, 0xf2, 0x01, 0x1b, 0xa3, 0x98, 0xb5, 0x9e, 0x59, + 0x37, 0x09, 0xde, 0x57, 0x24, 0x0b, 0x34, 0xff, 0x41, 0x09, 0x99, 0xbb, 0xa6, 0xe9, 0x34, 0xd0, + 0x02, 0xd1, 0x53, 0x68, 0xad, 0x5f, 0x2f, 0x9e, 0x4f, 0x13, 0x34, 0x08, 0xcb, 0x7e, 0x8c, 0x7b, + 0x10, 0x68, 0x19, 0xcb, 0x65, 0xa9, 0x8c, 0x27, 0xa3, 0x88, 0x17, 0xa7, 0x29, 0x65, 0xb2, 0x45, + 0x68, 0xfc, 0x48, 0xaa, 0x4e, 0x6a, 0xf4, 0x0d, 0x4f, 0xbe, 0x91, 0xe2, 0x5b, 0x6a, 0x6a, 0x04, + 0xdd, 0xc4, 0xff, 0xcd, 0x5d, 0xa4, 0x32, 0x64, 0xba, 0x67, 0x34, 0xf1, 0x01, 0x6f, 0xe6, 0x28, + 0x6c, 0x1d, 0xd2, 0x17, 0x67, 0x93, 0xe2, 0x5d, 0x75, 0xc5, 0x29, 0x21, 0x03, 0x0d, 0x8d, 0x24, + 0xa4, 0xce, 0xe8, 0xe5, 0x16, 0x92, 0x9f, 0xed, 0x5e, 0xbc, 0x81, 0x2b, 0x25, 0x59, 0x48, 0x29, + 0x85, 0x2b, 0xec, 0x11, 0x1b, 0x62, 0x7d, 0xc0, 0xce, 0xca, 0xf7, 0xce, 0x32, 0x4d, 0x20, 0xd6, + 0xf1, 0x0b, 0xf9, 0xe9, 0x7b, 0x50, 0x0d, 0x9b, 0xed, 0xa2, 0x63, 0x16, 0xe7, 0xb6, 0x9e, 0xb0, + 0xd3, 0xe4, 0x29, 0xa3, 0xc9, 0xdb, 0x38, 0x9e, 0x67, 0x9d, 0xd8, 0x32, 0xd4, 0x79, 0x2e, 0x90, + 0x37, 0x0a, 0x66, 0xf0, 0x84, 0x28, 0x62, 0x5b, 0x1f, 0x26, 0x3f, 0xf8, 0xb9, 0xd0, 0xe5, 0x31, + 0x0a, 0xe2, 0x8f, 0xd7, 0xc1, 0xac, 0x09, 0xaa, 0xd6, 0x52, 0x1e, 0x63, 0x99, 0x74, 0x8c, 0xd9, + 0xa0, 0xc7, 0x4e, 0xa6, 0xeb, 0x4e, 0x95, 0x3f, 0x6c, 0x63, 0xa8, 0x5e, 0x72, 0x80, 0x70, 0x2d, + 0x05, 0x00, 0x9e, 0xfc, 0x7d, 0x77, 0x3c, 0x72, 0xc3, 0x9e, 0xc7, 0xd1, 0x75, 0xd6, 0x2d, 0xcf, + 0x79, 0x66, 0x1b, 0x11, 0x20, 0x5b, 0x6e, 0x5d, 0x17, 0xcd, 0x71, 0x81, 0x82, 0xa8, 0x0a, 0x0a, + 0xa2, 0x21, 0x15, 0xec, 0xbb, 0x50, 0xc7, 0xb8, 0x82, 0x14, 0x0d, 0xc0, 0x81, 0xe5, 0x60, 0xa7, + 0xf3, 0xc8, 0x22, 0x06, 0xdb, 0x10, 0xff, 0x9d, 0xbb, 0xb1, 0xd0, 0x1c, 0x31, 0x21, 0xfb, 0xe2, + 0x7d, 0x49, 0xf4, 0xcf, 0xea, 0xcb, 0x2a, 0xaf, 0xc9, 0xb8, 0xee, 0x38, 0x10, 0xd5, 0x59, 0x9c, + 0xc1, 0x40, 0x28, 0x52, 0xe5, 0x9d, 0x46, 0xe7, 0xd0, 0x74, 0x24, 0x41, 0x80, 0xf6, 0xeb, 0x7a, + 0x35, 0x97, 0x43, 0x9d, 0x81, 0x3c, 0x51, 0x5f, 0x09, 0x32, 0x2e, 0x67, 0x29, 0xa2, 0xef, 0x47, + 0xad, 0x53, 0xe5, 0x60, 0x2b, 0xca, 0xc8, 0x43, 0x1d, 0xc4, 0x87, 0x0c, 0xa2, 0xdb, 0x5c, 0xf7, + 0xdf, 0x73, 0x8e, 0x85, 0x94, 0xb0, 0xe1, 0xe5, 0x1a, 0x40, 0xfe, 0x89, 0xa1, 0xdb, 0x64, 0xbc, + 0xcc, 0x5f, 0x43, 0x60, 0xfd, 0x5e, 0x93, 0x25, 0x5c, 0x54, 0xc3, 0x14, 0x71, 0x3a, 0x2d, 0x9d, + 0xbe, 0xf5, 0x0c, 0x4b, 0xd1, 0x84, 0x40, 0x4f, 0xa3, 0xf7, 0xfb, 0xde, 0x95, 0xed, 0xa9, 0xe5, + 0x50, 0xbb, 0x00, 0xbf, 0x08, 0x38, 0xa6, 0x4a, 0x9d, 0xa0, 0x6e, 0x6a, 0x83, 0x5d, 0xe5, 0x0c, + 0x21, 0x7d, 0x3a, 0x9c, 0xa7, 0x0b, 0x05, 0x0d, 0x00, 0x91, 0x5a, 0x4d, 0x1b, 0x85, 0x5b, 0x88, + 0x39, 0x69, 0x95, 0x4d, 0x96, 0x22, 0x34, 0x5d, 0x9f, 0xd4, 0x79, 0x28, 0x22, 0x03, 0xef, 0xcd, + 0x3e, 0xb5, 0x26, 0x73, 0x18, 0x10, 0xa3, 0x25, 0xdf, 0xaa, 0xc8, 0x45, 0x66, 0xcf, 0x43, 0xf7, + 0x02, 0x0e, 0xa5, 0xd2, 0x8f, 0xe4, 0x59, 0x98, 0xa5, 0x94, 0x71, 0x9a, 0xef, 0x84, 0xbb, 0x7e, + 0x3f, 0x2a, 0xe7, 0x00, 0x0b, 0x0f, 0x88, 0x06, 0x67, 0x2f, 0x3c, 0x28, 0x0e, 0xe9, 0xc7, 0x1a, + 0x03, 0x9c, 0x8d, 0xa8, 0xf0, 0x32, 0x24, 0xe9, 0x33, 0x84, 0x9b, 0xa4, 0x81, 0xa5, 0xa4, 0x6a, + 0xd0, 0x9c, 0x2c, 0x82, 0x4f, 0x10, 0x4c, 0xa0, 0x0c, 0xfe, 0xe3, 0xb9, 0xc8, 0x7a, 0xb7, 0x89, + 0x01, 0x60, 0xd8, 0x6f, 0xbe, 0xe9, 0x77, 0x14, 0xbd, 0xa7, 0x73, 0x2c, 0x39, 0xff, 0x1a, 0x42, + 0x3b, 0xa4, 0x09, 0x1f, 0x55, 0xe4, 0xbf, 0xec, 0xb1, 0xf1, 0xd8, 0x43, 0xb6, 0x0d, 0x44, 0xa2, + 0x8d, 0xad, 0x6f, 0xaf, 0xc9, 0xea, 0x85, 0xf8, 0x43, 0x4b, 0xa4, 0xed, 0xf7, 0xe4, 0x37, 0x15, + 0xe1, 0x81, 0x03, 0x2b, 0x42, 0xe7, 0x3c, 0xd7, 0xbe, 0x33, 0xf1, 0x28, 0xbf, 0xea, 0x53, 0x31, + 0xe1, 0x63, 0x54, 0x99, 0x3d, 0x61, 0xe8, 0xda, 0xa1, 0xeb, 0xb1, 0xfb, 0xaa, 0xd7, 0xfa, 0x89, + 0x78, 0x78, 0xd6, 0x87, 0xb2, 0x01, 0xdb, 0x06, 0x6f, 0xf4, 0xb9, 0x3b, 0x92, 0xe2, 0x4e, 0xca, + 0x36, 0x64, 0x9f, 0x95, 0x13, 0x90, 0xe9, 0x2b, 0x25, 0x08, 0x06, 0x1c, 0x1b, 0x9f, 0xed, 0x29, + 0x58, 0xfa, 0x24, 0xb3, 0x07, 0x07, 0x0a, 0x23, 0xb1, 0xa4, 0xa2, 0x0a, 0xb2, 0x11, 0xbc, 0x0b, + 0x10, 0xdb, 0x97, 0xc3, 0x5d, 0x33, 0xd1, 0xf4, 0xd1, 0x88, 0xe4, 0xaa, 0x10, 0xe1, 0xde, 0xc1, + 0xea, 0xb6, 0xf1, 0x62, 0x1b, 0x3f, 0x34, 0x34, 0x1c, 0x08, 0x08, 0xf3, 0xd9, 0xe9, 0xcf, 0xc0, + 0xa2, 0x16, 0xd3, 0xc0, 0xa1, 0xa1, 0x49, 0x7a, 0x19, 0xa1, 0x19, 0xca, 0xc1, 0xa5, 0x34, 0x4b, + 0x51, 0x56, 0x6c, 0x42, 0x05, 0x59, 0x41, 0xee, 0x48, 0x0c, 0xb7, 0xc2, 0x5e, 0xe9, 0x52, 0xc4, + 0xf6, 0x9a, 0x80, 0x79, 0xd9, 0x49, 0x9e, 0xbe, 0x07, 0xc9, 0x69, 0x07, 0x6f, 0x84, 0xc5, 0x19, + 0x58, 0x78, 0xb4, 0x0c, 0x89, 0x90, 0x37, 0xb6, 0xdc, 0xd3, 0x17, 0x93, 0xd1, 0x49, 0x2b, 0x6f, + 0x00, 0x86, 0x33, 0x49, 0xc3, 0xc0, 0xfa, 0x0d, 0x01, 0x59, 0x7d, 0x18, 0x7d, 0xb1, 0xcb, 0xd3, + 0x2f, 0xf7, 0x7e, 0x97, 0x58, 0xf5, 0xd4, 0x83, 0x42, 0x93, 0xf1, 0x28, 0x48, 0xd0, 0x36, 0xf0, + 0xb3, 0x3b, 0x7f, 0x2a, 0x1c, 0xf0, 0xa2, 0xc4, 0x14, 0x7d, 0xc9, 0xfd, 0xb2, 0x8f, 0xc9, 0x1a, + 0xa0, 0x53, 0x5b, 0x18, 0x66, 0xed, 0x65, 0xe4, 0xe3, 0xbe, 0x16, 0x6c, 0xe3, 0xa5, 0x06, 0x5f, + 0x34, 0x4d, 0x43, 0x6d, 0xe6, 0x8b, 0x80, 0x2b, 0x61, 0xfb, 0xe2, 0xa1, 0x3b, 0xf1, 0x75, 0x20, + 0x88, 0x98, 0xc1, 0xb0, 0xc0, 0x9a, 0xa5, 0x08, 0x59, 0x94, 0x53, 0x85, 0x27, 0xde, 0xd7, 0x73, + 0xa9, 0x8d, 0xbd, 0x52, 0x2b, 0x76, 0x70, 0xb0, 0xc5, 0x41, 0x94, 0x3b, 0x20, 0x55, 0x76, 0xa4, + 0xe2, 0xb2, 0x3c, 0x81, 0x31, 0x44, 0x4d, 0xc1, 0xb4, 0xd3, 0xd7, 0x9e, 0x27, 0xb9, 0x27, 0xf9, + 0x3f, 0xb9, 0x53, 0x9a, 0x85, 0x59, 0x29, 0x3c, 0x53, 0xf4, 0x30, 0xc2, 0xf9, 0xf4, 0xba, 0xfe, + 0x1a, 0x2a, 0xf6, 0xa8, 0x1a, 0x32, 0x62, 0x26, 0xfb, 0x25, 0xcb, 0x4d, 0xbb, 0x4c, 0x6f, 0x46, + 0x32, 0x1b, 0xa3, 0xe9, 0x1b, 0x47, 0x34, 0xe2, 0x63, 0x76, 0x08, 0x03, 0x66, 0xda, 0xca, 0x6f, + 0xb1, 0x38, 0x80, 0xfb, 0xa1, 0x4b, 0x76, 0x05, 0x24, 0x41, 0x9a, 0xbc, 0x67, 0x01, 0xbd, 0x3e, + 0xe8, 0xda, 0x6e, 0xb3, 0x92, 0x96, 0xbf, 0xa5, 0x6b, 0xd8, 0x3a, 0xaa, 0xb8, 0xa7, 0xe1, 0xe0, + 0xc6, 0xa4, 0xb3, 0x95, 0xda, 0x3a, 0xad, 0x2e, 0xa4, 0x1f, 0x74, 0x6e, 0x50, 0x42, 0xa0, 0xb3, + 0x19, 0xe5, 0x6b, 0x3e, 0xc8, 0x66, 0xb6, 0xb6, 0xa1, 0x28, 0x40, 0xd9, 0xec, 0x7b, 0x74, 0x05, + 0x9f, 0xdb, 0x68, 0x84, 0xac, 0xa9, 0xee, 0xdf, 0x2e, 0xe4, 0xa7, 0x53, 0xc7, 0x02, 0x63, 0xd4, + 0x7d, 0xe8, 0xf9, 0x1b, 0x09, 0x40, 0x8b, 0x37, 0x29, 0xb7, 0xc8, 0xf3, 0xf0, 0x33, 0x84, 0x59, + 0x19, 0xd8, 0x93, 0x74, 0x8a, 0x34, 0xb7, 0x79, 0x83, 0x04, 0xa3, 0xca, 0xd4, 0x5e, 0x85, 0x57, + 0x69, 0xbd, 0xf2, 0x74, 0x35, 0xfd, 0xaf, 0x2f, 0x64, 0x83, 0xc3, 0xee, 0x1f, 0xba, 0xfc, 0x9d, + 0x5b, 0xa3, 0x0e, 0x40, 0x46, 0x61, 0x66, 0x0f, 0x03, 0x13, 0x6b, 0xea, 0x6b, 0xa0, 0xb2, 0xac, + 0x5a, 0x94, 0x43, 0x1b, 0x39, 0x4d, 0xbd, 0x66, 0xf0, 0xf4, 0x86, 0xf8, 0x38, 0xfe, 0xcd, 0xf5, + 0x64, 0x76, 0x36, 0x2a, 0x21, 0xed, 0xc6, 0x11, 0xcf, 0xcc, 0xa2, 0x31, 0x78, 0xa4, 0x8f, 0xb8, + 0x39, 0xd0, 0xf6, 0x25, 0x5a, 0xaa, 0xa3, 0xd4, 0xd1, 0xcb, 0xd0, 0x69, 0x77, 0xff, 0x4b, 0xc2, + 0x8c, 0xa6, 0x20, 0xc7, 0xd5, 0x78, 0x5a, 0xc8, 0xd9, 0x3a, 0x44, 0xb4, 0x60, 0xaf, 0x40, 0xfb, + 0x6d, 0xad, 0x2f, 0x7b, 0x00, 0xce, 0xb8, 0xcc, 0x47, 0x5b, 0x3e, 0xa7, 0x4d, 0x52, 0x7a, 0x7c, + 0x6d, 0x9f, 0xa3, 0x15, 0xa8, 0xe5, 0x5c, 0x27, 0xed, 0x4d, 0xda, 0x62, 0x0e, 0x15, 0xd3, 0x90, + 0xe7, 0x53, 0xc8, 0xf1, 0x23, 0x87, 0xd4, 0x58, 0xa2, 0x95, 0x03, 0xa8, 0x02, 0x35, 0xf3, 0x12, + 0xa7, 0x4b, 0x40, 0x9b, 0x19, 0x94, 0x24, 0xda, 0x3b, 0x2f, 0xc6, 0x73, 0x58, 0xc8, 0x27, 0x35, + 0xe7, 0x67, 0xca, 0x88, 0x2a, 0x9c, 0xe4, 0xb0, 0x9b, 0xfa, 0xc8, 0x17, 0xab, 0xe6, 0xe4, 0x8c, + 0xc9, 0xa2, 0xd6, 0x4c, 0x32, 0x7e, 0xb1, 0x36, 0x87, 0x14, 0xbd, 0xd6, 0x70, 0xab, 0xe1, 0x1d, + 0x8e, 0x1e, 0x43, 0x6b, 0x3b, 0xd3, 0x23, 0x79, 0x7e, 0x8e, 0x0e, 0x7b, 0x77, 0xe7, 0x24, 0xb3, + 0x7d, 0x3f, 0x7f, 0x2a, 0x8a, 0x99, 0xdc, 0xbc, 0x01, 0x29, 0xd7, 0x52, 0x77, 0xb2, 0x90, 0x7f, + 0xaa, 0x4b, 0xd7, 0x77, 0x5f, 0x6d, 0x6b, 0xff, 0xf5, 0xad, 0x13, 0x2e, 0xa3, 0x5c, 0xa2, 0xa5, + 0x07, 0x05, 0x9c, 0x0b, 0xae, 0xbc, 0xee, 0xff, 0x54, 0xcf, 0xfb, 0x18, 0x82, 0x7b, 0x7c, 0xc1, + 0xe5, 0x24, 0x08, 0x36, 0xb7, 0x6a, 0xa0, 0x20, 0x56, 0x18, 0xdc, 0xa8, 0x5d, 0x57, 0x79, 0xc7, + 0x86, 0x8d, 0xc5, 0xe9, 0x35, 0x48, 0x6f, 0x57, 0x6c, 0x40, 0x8d, 0x0d, 0xd3, 0x4a, 0x4a, 0x5a, + 0xd3, 0x7e, 0x67, 0x55, 0x80, 0xfb, 0x45, 0xdf, 0x81, 0x58, 0xf9, 0x34, 0xa7, 0x7e, 0xca, 0x1e, + 0x54, 0x31, 0x51, 0xb6, 0x4c, 0x20, 0x96, 0xf9, 0xa2, 0x16, 0xc8, 0xff, 0x0a, 0x66, 0xb9, 0x8d, + 0xe2, 0x67, 0x8b, 0x92, 0x0c, 0x66, 0x4c, 0x1b, 0x01, 0x0b, 0x30, 0xd2, 0xeb, 0x79, 0x9b, 0xc4, + 0xa8, 0x0f, 0xc9, 0x80, 0xe8, 0x8b, 0x9c, 0x60, 0x9d, 0x25, 0xa0, 0xac, 0xb2, 0xb0, 0x98, 0xe0, + 0xae, 0x15, 0x36, 0x0a, 0xaa, 0xa2, 0x75, 0xa0, 0xc3, 0x2c, 0x19, 0xa9, 0x2e, 0xde, 0x09, 0x6b, + 0xc6, 0x19, 0xea, 0xee, 0xa7, 0x03, 0x5e, 0xdf, 0xd2, 0x23, 0xc9, 0x4f, 0x8f, 0xb5, 0x42, 0xdc, + 0x4d, 0x2f, 0x6b, 0x08, 0x51, 0x05, 0x6e, 0x90, 0xa4, 0x94, 0xef, 0xe9, 0x0d, 0x7f, 0x91, 0x85, + 0x0a, 0xd3, 0x1e, 0xc6, 0xcf, 0x6b, 0x93, 0xb2, 0xeb, 0x67, 0x72, 0x11, 0x03, 0xae, 0x63, 0x98, + 0x97, 0xfe, 0xf0, 0xa8, 0xfb, 0x27, 0x79, 0xc5, 0x69, 0x8c, 0x1a, 0x15, 0xa4, 0x78, 0x36, 0xe5, + 0x26, 0xa0, 0x03, 0x6d, 0x01, 0x02, 0xaf, 0xab, 0x1f, 0xfc, 0xf7, 0xdb, 0x16, 0x37, 0xde, 0x1f, + 0x21, 0x78, 0x04, 0x46, 0xb8, 0x91, 0x3e, 0x73, 0xbb, 0xbe, 0x2f, 0xec, 0x0c, 0x5d, 0xc6, 0xbf, + 0xb6, 0xb1, 0xdb, 0x25, 0xba, 0xc2, 0x15, 0x4b, 0xa0, 0x8e, 0xb5, 0x7f, 0x75, 0xab, 0xee, 0xe3, + 0x41, 0xe9, 0xf6, 0x0d, 0xb7, 0x08, 0x02, 0x0f, 0x03, 0xe2, 0xa6, 0xaf, 0xd1, 0x9e, 0x14, 0x63, + 0x4f, 0x4f, 0xba, 0x99, 0x2a, 0xf5, 0xdc, 0xd5, 0x7c, 0x9b, 0x0f, 0x50, 0x5e, 0xf2, 0x93, 0xba, + 0x70, 0x78, 0xad, 0x2a, 0x25, 0xf7, 0xcc, 0x1d, 0x5c, 0xf4, 0xa5, 0x29, 0xa1, 0xcd, 0x6a, 0x7a, + 0x62, 0xc7, 0xc9, 0x73, 0xf1, 0x45, 0xc8, 0xc1, 0x91, 0x55, 0x4a, 0x47, 0x0f, 0x9f, 0xf9, 0xa6, + 0xb4, 0xcd, 0xd3, 0x99, 0x55, 0xde, 0x9b, 0xb9, 0xfa, 0x03, 0xd4, 0x26, 0x99, 0xd5, 0x4f, 0x95, + 0x6d, 0xf9, 0xe3, 0x3f, 0x60, 0x63, 0xaf, 0x60, 0x9a, 0xc5, 0xe5, 0x3b, 0xce, 0x73, 0x48, 0xb0, + 0x00, 0x52, 0x43, 0x44, 0x6c, 0x28, 0x96, 0xeb, 0xd0, 0xc3, 0xe3, 0xc8, 0x0a, 0x49, 0xd5, 0x24, + 0xcf, 0xe3, 0xde, 0xfe, 0x92, 0x25, 0x46, 0xf9, 0xd9, 0xcc, 0xce, 0x8c, 0xaf, 0xc6, 0xe9, 0x7f, + 0x58, 0x88, 0x15, 0x8a, 0x8d, 0x7c, 0xcc, 0x61, 0x33, 0xc9, 0xc0, 0xb8, 0xee, 0xfb, 0x3b, 0x4f, + 0x9b, 0x0e, 0xad, 0xe5, 0x77, 0xb5, 0x34, 0xed, 0x41, 0x96, 0xc0, 0x02, 0xca, 0x62, 0x75, 0x8a, + 0x16, 0x89, 0xce, 0x5a, 0xc5, 0x10, 0x3b, 0x65, 0x94, 0x85, 0xe5, 0x42, 0xe2, 0xd5, 0x85, 0x52, + 0x7a, 0x81, 0x96, 0x33, 0x30, 0x36, 0x31, 0x17, 0x2e, 0xce, 0xb3, 0x4a, 0x5c, 0x93, 0x90, 0x5b, + 0x67, 0xc7, 0x84, 0xdb, 0x26, 0x3f, 0x0b, 0xec, 0xff, 0x7e, 0x5f, 0xdd, 0x1b, 0x5f, 0xa1, 0x76, + 0xc9, 0x14, 0x27, 0x50, 0x98, 0x07, 0x58, 0x47, 0x84, 0x9b, 0x05, 0x18, 0x08, 0x34, 0xfd, 0xde, + 0xdd, 0x90, 0x7c, 0x96, 0x91, 0x36, 0x42, 0xec, 0xc7, 0x47, 0x6d, 0x18, 0xf2, 0x72, 0xc4, 0x97, + 0xd1, 0x9b, 0xf6, 0x21, 0xc1, 0xd7, 0x09, 0x56, 0x33, 0xfe, 0x2e, 0x60, 0x15, 0x07, 0x0d, 0x08, + 0x8e, 0x5e, 0xde, 0xb4, 0x75, 0x7c, 0xf2, 0xd8, 0xe8, 0xe5, 0x10, 0xdc, 0x99, 0xa3, 0x65, 0xec, + 0x1e, 0xb4, 0xf5, 0x17, 0x41, 0x51, 0x90, 0x3b, 0xa4, 0x16, 0xf4, 0xeb, 0xab, 0x81, 0x64, 0x2e, + 0x72, 0xd9, 0x28, 0x5e, 0xf7, 0x3c, 0xfd, 0xb8, 0x38, 0x2c, 0x09, 0xf1, 0x41, 0xf0, 0x5a, 0x0f, + 0xe7, 0x8d, 0xe7, 0x07, 0xd6, 0xeb, 0x0c, 0x42, 0xc9, 0x83, 0xb5, 0xbd, 0xa5, 0xc2, 0xfc, 0x7b, + 0x0e, 0x19, 0x25, 0x51, 0xc1, 0x01, 0xf0, 0x32, 0xad, 0xbf, 0x4c, 0x96, 0x97, 0x70, 0xc2, 0xa7, + 0x1a, 0x78, 0x52, 0x5f, 0x41, 0xe3, 0x1f, 0x5f, 0x7b, 0x61, 0x2b, 0x70, 0x3d, 0xce, 0x24, 0xea, + 0xad, 0xe4, 0x03, 0x77, 0xb7, 0xe9, 0x31, 0xcc, 0x09, 0x28, 0xed, 0xd5, 0x38, 0x13, 0xef, 0x9e, + 0xdd, 0x5f, 0xe3, 0xbf, 0x23, 0xc7, 0x72, 0xf5, 0x18, 0xed, 0xed, 0x62, 0xd7, 0x05, 0xa0, 0x13, + 0x73, 0xf8, 0x56, 0x52, 0xd2, 0x3b, 0x7a, 0x1d, 0xa0, 0x5d, 0x24, 0x54, 0x38, 0xbc, 0x0e, 0x2e, + 0xb6, 0x73, 0x8d, 0xe3, 0x25, 0x70, 0xde, 0x26, 0x44, 0x6b, 0x69, 0x3f, 0x27, 0x06, 0x45, 0x92, + 0xd6, 0x4b, 0x55, 0xcd, 0x2a, 0x42, 0x7d, 0x1b, 0x51, 0x74, 0xe7, 0x7b, 0x1d, 0x27, 0xfa, 0x83, + 0x0e, 0xa1, 0xe5, 0xc9, 0xab, 0xec, 0xb6, 0x8f, 0x7a, 0xd5, 0x49, 0x1e, 0x41, 0xc1, 0x33, 0xf8, + 0x5d, 0x6e, 0xfd, 0x42, 0xff, 0x3d, 0xec, 0x3c, 0x18, 0x63, 0x4a, 0x6a, 0xe5, 0x29, 0x0e, 0xd5, + 0xb9, 0xfa, 0x4b, 0x24, 0xfa, 0xa3, 0x04, 0x71, 0xce, 0x81, 0x57, 0x82, 0x23, 0x71, 0x00, 0xca, + 0xd5, 0xf1, 0x86, 0x49, 0x2f, 0x5c, 0x6f, 0x0a, 0xe9, 0x68, 0x37, 0x46, 0x92, 0x2e, 0x23, 0xd7, + 0x2e, 0x85, 0xc5, 0x3a, 0xb6, 0x2c, 0x32, 0x99, 0x14, 0xd4, 0x16, 0xe3, 0x9b, 0xbb, 0x7e, 0xc2, + 0x46, 0x2c, 0x34, 0x23, 0x9c, 0xab, 0xb5, 0xa0, 0xcf, 0x31, 0x95, 0x4e, 0x33, 0x02, 0x10, 0xb1, + 0xbb, 0x85, 0x68, 0xd7, 0xb8, 0xea, 0x0e, 0x84, 0xcf, 0x58, 0x55, 0x48, 0xd7, 0xa3, 0xdd, 0xf2, + 0x7e, 0x17, 0x03, 0x68, 0xe9, 0xc3, 0x7a, 0x22, 0xdf, 0xaa, 0x44, 0x3f, 0x2f, 0x90, 0xd4, 0xfc, + 0x5d, 0x09, 0x29, 0xb3, 0x5f, 0x93, 0x98, 0xdb, 0x01, 0x5b, 0x85, 0xee, 0x72, 0xf7, 0x84, 0x12, + 0x1e, 0x5b, 0xb6, 0x3e, 0xd1, 0xd4, 0xdd, 0xe9, 0x52, 0xc7, 0xb6, 0xde, 0x61, 0x93, 0xc0, 0xe5, + 0x0f, 0x4a, 0xdf, 0x1b, 0xf4, 0xbb, 0x7e, 0x72, 0x83, 0x06, 0x87, 0xcd, 0x89, 0x22, 0x05, 0x3e, + 0xf7, 0x16, 0x39, 0x9e, 0x2e, 0x2a, 0x1a, 0x4f, 0x40, 0x8e, 0xd1, 0xf4, 0x07, 0x04, 0x18, 0xed, + 0xb2, 0xbd, 0x31, 0x42, 0x04, 0xd6, 0x99, 0xa3, 0x93, 0x76, 0x85, 0x3d, 0xb3, 0x71, 0x1a, 0x59, + 0xde, 0x18, 0xb7, 0x2d, 0x0b, 0x45, 0x1f, 0x77, 0x7e, 0x95, 0x80, 0xc2, 0x47, 0x1c, 0x1f, 0x1f, + 0x67, 0xe2, 0x23, 0x8a, 0x97, 0x3a, 0xdc, 0x3a, 0x25, 0xab, 0x92, 0x76, 0xbf, 0x65, 0x2a, 0xf2, + 0xd3, 0x04, 0xf0, 0xa2, 0x63, 0xb1, 0x6b, 0x98, 0xd6, 0x9a, 0x86, 0x09, 0x65, 0xf8, 0xf0, 0x0d, + 0xc6, 0x5c, 0x56, 0x66, 0x3d, 0xd6, 0x55, 0xb7, 0x6f, 0xd7, 0xfb, 0x90, 0xcd, 0xfc, 0xe9, 0x52, + 0xd0, 0x66, 0xd8, 0x8f, 0x0d, 0x53, 0x84, 0x25, 0xf5, 0xae, 0xef, 0x5a, 0x3f, 0xde, 0x6c, 0xa9, + 0xa1, 0x02, 0x5d, 0x1b, 0x87, 0x2f, 0x11, 0x53, 0x6e, 0xb3, 0x81, 0xab, 0x05, 0x39, 0x23, 0x6b, + 0xf8, 0x65, 0xc6, 0xff, 0xef, 0x74, 0xa2, 0x0b, 0xcf, 0xfa, 0xe2, 0xf9, 0xe2, 0x0a, 0x08, 0xdd, + 0xa4, 0x9e, 0x44, 0xea, 0xad, 0x9f, 0x45, 0xa0, 0x8a, 0xce, 0xec, 0x09, 0x9f, 0x19, 0x40, 0x1f, + 0x85, 0x03, 0x6f, 0x3c, 0xf3, 0x0a, 0x49, 0x1c, 0x4e, 0x58, 0xa5, 0x2a, 0x1e, 0x0f, 0x98, 0xf5, + 0xf4, 0xeb, 0x83, 0xe6, 0x44, 0x15, 0x77, 0x97, 0x88, 0xee, 0x25, 0x70, 0x1f, 0x82, 0x21, 0xe2, + 0x4b, 0xea, 0x68, 0x93, 0x49, 0x46, 0x3e, 0xbc, 0x16, 0xbd, 0x8b, 0x49, 0xd6, 0x74, 0x9c, 0xb1, + 0x91, 0x38, 0xa6, 0x62, 0x33, 0x8c, 0xb5, 0x5d, 0x75, 0xe4, 0x8c, 0x4d, 0x9c, 0x7a, 0x78, 0xd1, + 0x4f, 0x07, 0x3e, 0x55, 0x38, 0x30, 0x83, 0x8b, 0x62, 0xf8, 0x95, 0x65, 0x03, 0xec, 0x5a, 0x29, + 0xdc, 0xf3, 0x3d, 0x52, 0x8e, 0x53, 0x7d, 0x45, 0x48, 0xe0, 0xfc, 0x37, 0x4b, 0x0e, 0xc5, 0x05, + 0x28, 0x8d, 0x11, 0x9b, 0xdf, 0x59, 0x70, 0xa8, 0x0f, 0x84, 0x63, 0xd5, 0x70, 0x5a, 0xbc, 0xc3, + 0x1b, 0x85, 0x39, 0xfd, 0xf5, 0xad, 0xbd, 0xef, 0x27, 0x6a, 0x56, 0xab, 0x5a, 0x23, 0xac, 0x33, + 0x9d, 0x9c, 0xd9, 0x46, 0xd2, 0xd6, 0x84, 0x18, 0xbd, 0xdb, 0xbe, 0xec, 0xc2, 0xfe, 0x79, 0x44, + 0xc8, 0xa1, 0xb5, 0xa1, 0xea, 0xb4, 0x20, 0x69, 0xde, 0x1a, 0x01, 0xe9, 0xc4, 0x8c, 0x95, 0x1e, + 0x7f, 0x65, 0xf6, 0xfe, 0x92, 0x26, 0x6a, 0xd9, 0xc8, 0x47, 0xdf, 0x9f, 0x9b, 0x1c, 0x61, 0xda, + 0x73, 0xb1, 0x75, 0x49, 0xb9, 0x5a, 0x4a, 0x5a, 0x64, 0x86, 0x8e, 0x98, 0x62, 0xa5, 0x52, 0x01, + 0xc9, 0xbe, 0xd9, 0xfd, 0x7f, 0x61, 0x71, 0x4c, 0x2f, 0x89, 0x4d, 0xcd, 0x25, 0x6f, 0x93, 0x60, + 0x94, 0x3b, 0x16, 0xd2, 0xeb, 0x54, 0x52, 0xf8, 0xd7, 0x9b, 0xd6, 0x3e, 0xf5, 0x53, 0x34, 0xf8, + 0x6d, 0xe4, 0xe9, 0xf4, 0x02, 0x06, 0x0c, 0x41, 0x90, 0xe5, 0x7f, 0x4c, 0xeb, 0x89, 0xc6, 0x4f, + 0x89, 0x9e, 0xff, 0x6f, 0x84, 0xd3, 0x84, 0xba, 0xaf, 0x6e, 0x63, 0x76, 0xdb, 0x0a, 0x98, 0xad, + 0x59, 0x73, 0xf2, 0x02, 0xad, 0x11, 0x86, 0x3a, 0x19, 0x68, 0x5f, 0x80, 0x66, 0xa6, 0x8f, 0xed, + 0x92, 0x27, 0xe1, 0x30, 0xf6, 0x6b, 0x7c, 0x66, 0x70, 0xc4, 0x9f, 0xe6, 0xff, 0x96, 0x57, 0xb1, + 0x87, 0xbf, 0xd0, 0x17, 0x2b, 0x5c, 0x51, 0x5d, 0xfa, 0x13, 0xd3, 0x4f, 0x83, 0x2c, 0x1c, 0xa7, + 0xe4, 0x4b, 0xb0, 0x57, 0xd2, 0xef, 0xfd, 0x82, 0xe3, 0xf8, 0x6b, 0xa1, 0x28, 0x86, 0x4a, 0xd0, + 0x82, 0x35, 0x81, 0xe4, 0x30, 0x69, 0x2e, 0x0f, 0xa1, 0x90, 0x9a, 0x1b, 0x5a, 0x91, 0xfe, 0xa1, + 0xa2, 0xb9, 0x0a, 0xb1, 0x69, 0x02, 0xc9, 0x00, 0x4e, 0xb5, 0xb0, 0x8d, 0x01, 0xea, 0x4d, 0x65, + 0xd7, 0x19, 0x96, 0x03, 0xab, 0x07, 0x32, 0x2c, 0x7f, 0xc4, 0x8d, 0x91, 0x44, 0xdf, 0xa5, 0xe5, + 0x88, 0x83, 0xff, 0x24, 0x93, 0x32, 0x69, 0x9a, 0x1f, 0x25, 0x28, 0x84, 0xc2, 0x82, 0x1b, 0x07, + 0x19, 0x13, 0x2b, 0xf2, 0x85, 0x7d, 0xd2, 0x77, 0x9c, 0x6e, 0xce, 0xcc, 0x0f, 0xa6, 0x03, 0xaf, + 0xc5, 0x94, 0x52, 0x24, 0xb7, 0x3c, 0x5a, 0x46, 0x2b, 0x08, 0x44, 0xa0, 0x19, 0xdb, 0xe7, 0xf2, + 0x95, 0x10, 0x59, 0x31, 0x73, 0x9f, 0x62, 0x05, 0x0d, 0x38, 0xe3, 0x65, 0x95, 0xc3, 0xf5, 0x0b, + 0x70, 0x0d, 0x9e, 0x3d, 0x3f, 0x39, 0x0b, 0x28, 0xee, 0x96, 0xda, 0x2c, 0x50, 0x01, 0xe6, 0xdd, + 0xd0, 0x74, 0x4d, 0x6b, 0x9a, 0x40, 0xf5, 0xe3, 0x7e, 0xfa, 0xf3, 0x11, 0x3e, 0xad, 0x63, 0xac, + 0xb7, 0x95, 0x38, 0x69, 0x4f, 0x66, 0xe0, 0xb6, 0x7c, 0x05, 0xca, 0xde, 0x3e, 0x16, 0x2c, 0x2b, + 0x5b, 0x61, 0x2f, 0x01, 0xf8, 0xe1, 0x4a, 0x65, 0x8f, 0x5c, 0x1d, 0x55, 0x88, 0xdf, 0x62, 0x55, + 0x67, 0xa6, 0x10, 0xf6, 0x1f, 0x6c, 0xd3, 0xe9, 0x59, 0x8d, 0x3e, 0x63, 0x30, 0x77, 0x48, 0x58, + 0x3c, 0x6f, 0x08, 0x47, 0xaa, 0x06, 0x57, 0xce, 0x27, 0x3d, 0xb4, 0x21, 0x17, 0x32, 0x45, 0x8b, + 0xd5, 0xc9, 0x20, 0x8e, 0x71, 0x77, 0xd6, 0xcb, 0xce, 0x3d, 0x28, 0x5e, 0x5a, 0x37, 0xb8, 0xe7, + 0x60, 0xa1, 0xf5, 0x94, 0x35, 0x4c, 0xf3, 0x79, 0x81, 0x34, 0x3a, 0xdb, 0x73, 0xac, 0x21, 0xf1, + 0xb4, 0xff, 0x42, 0x98, 0xe6, 0x70, 0x96, 0xfd, 0x5e, 0x88, 0x3f, 0x67, 0x9b, 0x82, 0x36, 0x20, + 0xdf, 0xc0, 0x1f, 0xad, 0x83, 0x17, 0x8a, 0xda, 0x45, 0xbc, 0xc5, 0xc3, 0x62, 0x07, 0xa8, 0xb7, + 0x91, 0x25, 0x4f, 0x03, 0x63, 0xb5, 0x16, 0xb1, 0x2d, 0xc6, 0xd9, 0x3b, 0x52, 0x30, 0xa9, 0xe4, + 0x1b, 0x11, 0x8f, 0xe9, 0x5c, 0xce, 0x80, 0xc2, 0x4c, 0x31, 0x10, 0xb7, 0x4f, 0x16, 0x39, 0x49, + 0x20, 0xd1, 0xb7, 0x66, 0x48, 0x5b, 0x67, 0xd8, 0xe8, 0x76, 0xc6, 0xa0, 0xe1, 0xa0, 0xdc, 0xdc, + 0xa1, 0xef, 0x46, 0x2d, 0x07, 0x5d, 0xad, 0xcc, 0xa9, 0xb0, 0x59, 0xe5, 0x69, 0x06, 0xa8, 0xb4, + 0xb3, 0x76, 0x3f, 0xff, 0xd8, 0x66, 0x5a, 0xe7, 0xa0, 0x19, 0x2e, 0x4a, 0x1d, 0x45, 0xe9, 0x9b, + 0xbb, 0x38, 0xb6, 0xad, 0x0a, 0x67, 0x0a, 0x9b, 0x29, 0x6e, 0x32, 0xc1, 0x4d, 0x27, 0x61, 0xbd, + 0x0a, 0x8d, 0x4f, 0xa1, 0xa3, 0xf1, 0x2d, 0x90, 0xd6, 0x3a, 0x91, 0x7f, 0xb7, 0x85, 0x41, 0xec, + 0x6f, 0xab, 0xaf, 0x93, 0x59, 0xef, 0x00, 0x1c, 0xd5, 0xc3, 0xc6, 0xa7, 0x49, 0xe6, 0x0a, 0xe0, + 0xda, 0x95, 0x9b, 0xb2, 0x0c, 0xf9, 0x3e, 0xae, 0x1c, 0x09, 0xca, 0x51, 0x35, 0xc6, 0xea, 0x58, + 0xbf, 0xe9, 0x16, 0x6a, 0xb1, 0xbe, 0x64, 0xff, 0xbf, 0x9d, 0xd4, 0x38, 0x47, 0x86, 0x17, 0x59, + 0xf2, 0xf3, 0x6c, 0x71, 0xee, 0x57, 0xb1, 0x80, 0xbd, 0xb0, 0xd4, 0xd6, 0xa0, 0xa0, 0x73, 0x82, + 0x0d, 0xad, 0xb2, 0x34, 0x6d, 0xac, 0x83, 0xd8, 0xed, 0xc7, 0x20, 0x7d, 0xc3, 0x30, 0x0b, 0xf3, + 0xb3, 0xd3, 0xce, 0x8f, 0x42, 0x2c, 0x8b, 0x29, 0xf8, 0xc7, 0xa3, 0x3c, 0x8b, 0x42, 0x3f, 0xf6, + 0x0f, 0x2b, 0x5b, 0x58, 0x69, 0x17, 0x33, 0xa2, 0x4f, 0x23, 0x22, 0xaf, 0xb4, 0x7c, 0xab, 0x7b, + 0x3c, 0xb4, 0x3d, 0x01, 0x83, 0xb1, 0x71, 0x22, 0xef, 0xa4, 0x59, 0xb2, 0x4c, 0x22, 0xe2, 0xb5, + 0x24, 0x96, 0x90, 0x3d, 0x55, 0xa1, 0xd0, 0x1e, 0x8c, 0x6c, 0xc2, 0xf0, 0x2b, 0xad, 0xaa, 0x27, + 0x99, 0xfa, 0x76, 0xd6, 0xc4, 0x67, 0xd4, 0x34, 0x1d, 0xb0, 0x4a, 0x03, 0x5c, 0x7c, 0x34, 0x0b, + 0x0f, 0xe5, 0x47, 0x4d, 0x32, 0x1c, 0xb3, 0x4f, 0x72, 0xf6, 0x1c, 0x29, 0x53, 0x71, 0x77, 0x91, + 0x5c, 0x4a, 0x2b, 0x8e, 0x12, 0x0b, 0x02, 0x77, 0xfd, 0xfa, 0xc0, 0x7c, 0x15, 0xbf, 0xb7, 0x54, + 0xfa, 0xbd, 0x90, 0x43, 0x1b, 0xa5, 0x7d, 0xf4, 0x6f, 0x7d, 0x30, 0xc8, 0x8b, 0x52, 0x02, 0x5b, + 0xeb, 0x17, 0xa4, 0x49, 0xa0, 0x9d, 0xef, 0xbb, 0xa7, 0xb3, 0x40, 0xa7, 0x3e, 0x14, 0x23, 0xbf, + 0x07, 0x06, 0xc6, 0xe5, 0xd6, 0x25, 0x4b, 0x5e, 0x2f, 0xf6, 0xa3, 0x86, 0xd8, 0xe5, 0xed, 0xae, + 0x2b, 0x1a, 0xc8, 0xb8, 0xd4, 0x4f, 0xbe, 0x9d, 0x53, 0x61, 0x2f, 0xa5, 0xd3, 0x5b, 0x51, 0x3a, + 0x5e, 0x22, 0x8d, 0xeb, 0x5e, 0xd6, 0xd4, 0x40, 0x3d, 0x0e, 0x0a, 0x1b, 0x91, 0xcd, 0xa0, 0xeb, + 0xd1, 0xff, 0xb4, 0x67, 0xe7, 0x0c, 0xf1, 0x37, 0x7e, 0x6c, 0x7f, 0xbb, 0x28, 0xfe, 0x4c, 0x9a, + 0x94, 0xa0, 0x14, 0x24, 0xb0, 0x3a, 0x29, 0x23, 0x71, 0xa3, 0xf8, 0x66, 0x16, 0xfa, 0x0a, 0xd9, + 0x70, 0x7a, 0x30, 0x37, 0xb9, 0x5f, 0x00, 0x08, 0xd7, 0x9c, 0xda, 0xd5, 0xc9, 0x82, 0x6c, 0x24, + 0x48, 0x12, 0xa9, 0x0e, 0x83, 0xb5, 0x6b, 0xe3, 0x56, 0x10, 0x70, 0x02, 0xaa, 0xf4, 0xd3, 0x2d, + 0xe7, 0xb9, 0x2a, 0x60, 0x4b, 0x01, 0x71, 0xcd, 0x90, 0xac, 0x59, 0x91, 0x32, 0x78, 0x15, 0x8a, + 0x52, 0x84, 0x75, 0x6d, 0xf8, 0x88, 0xe8, 0xa0, 0xdd, 0x27, 0xf9, 0x66, 0xf6, 0x9b, 0x9e, 0x14, + 0xcf, 0xcf, 0x0f, 0xb9, 0xad, 0x54, 0x9b, 0xa8, 0x4c, 0x90, 0x92, 0x6b, 0xf3, 0x5e, 0x7b, 0xa8, + 0xa5, 0x23, 0x4c, 0xdd, 0x57, 0x87, 0xe2, 0xa2, 0x07, 0xd9, 0x30, 0x38, 0xad, 0xbd, 0x72, 0xb0, + 0x15, 0x25, 0xa9, 0x94, 0x5f, 0x8e, 0x94, 0xf1, 0x6a, 0x5c, 0x87, 0x3d, 0x90, 0x70, 0x65, 0x42, + 0x1d, 0x3a, 0x2e, 0xf7, 0xe3, 0xb3, 0x8c, 0xbf, 0x1c, 0x38, 0xdc, 0xd6, 0x40, 0xa6, 0x18, 0x30, + 0x87, 0xab, 0x40, 0xb5, 0x7d, 0x3a, 0x8d, 0x75, 0x39, 0x8a, 0x92, 0xb2, 0x1c, 0xbc, 0x83, 0xe8, + 0x96, 0x91, 0x14, 0xd9, 0x68, 0xad, 0x12, 0xcc, 0x70, 0x22, 0xdd, 0x80, 0x8c, 0x81, 0xb6, 0xd6, + 0xc1, 0xf2, 0x1d, 0xa0, 0xfd, 0xf5, 0xb8, 0x83, 0x1a, 0x75, 0xd4, 0xaf, 0x64, 0x8b, 0x2b, 0xf7, + 0xf5, 0x31, 0x90, 0x79, 0xc6, 0x17, 0x23, 0x5f, 0xc6, 0x9e, 0x0e, 0x67, 0x3c, 0x0c, 0x5f, 0x0a, + 0x03, 0xb3, 0x98, 0xf4, 0x36, 0x75, 0x4c, 0x1e, 0xb5, 0x22, 0x6d, 0xe8, 0xe3, 0x16, 0x9f, 0xfd, + 0xdf, 0x33, 0x90, 0x1d, 0xea, 0xba, 0xde, 0x5a, 0x2b, 0x5d, 0xbe, 0xd7, 0x57, 0xcd, 0xc3, 0xbc, + 0xae, 0x02, 0xd3, 0x41, 0x1f, 0x3d, 0x5f, 0x83, 0xbc, 0x86, 0xf2, 0x5b, 0xb8, 0x7d, 0x0b, 0xd1, + 0x9a, 0x5a, 0x19, 0x5b, 0x8c, 0x53, 0xcd, 0x9a, 0x1c, 0x08, 0xec, 0xe9, 0xac, 0x3e, 0x41, 0x5a, + 0x31, 0xb1, 0x72, 0x05, 0xd6, 0xfd, 0x94, 0x70, 0x1d, 0xca, 0x05, 0x7c, 0x1c, 0x12, 0xcc, 0x42, + 0x2f, 0x26, 0x8d, 0xee, 0x4a, 0xdf, 0xaf, 0xab, 0x61, 0xd6, 0x24, 0x96, 0xe0, 0x40, 0x89, 0xff, + 0xb0, 0xc2, 0xce, 0x44, 0xf2, 0x71, 0x03, 0x06, 0x57, 0xfe, 0x26, 0x7c, 0x80, 0x7b, 0xdf, 0x08, + 0xcc, 0xd6, 0x09, 0x13, 0x2e, 0x9e, 0xd1, 0xa5, 0xad, 0x99, 0x64, 0xd7, 0x79, 0xf7, 0x28, 0xb1, + 0xd8, 0x72, 0x64, 0x3a, 0xdf, 0xf5, 0x9c, 0x84, 0x13, 0x5c, 0x54, 0x87, 0x37, 0x4f, 0xe4, 0x21, + 0x96, 0x9f, 0x0b, 0x36, 0x2b, 0xd1, 0x5c, 0xba, 0x77, 0x54, 0x93, 0x77, 0x63, 0xef, 0x5a, 0x50, + 0x01, 0x55, 0x94, 0x7b, 0x55, 0x3a, 0x05, 0x3f, 0x75, 0xe0, 0xfc, 0x9b, 0x0b, 0xa1, 0x25, 0xba, + 0xab, 0x24, 0x45, 0x62, 0x45, 0x10, 0x80, 0xfd, 0x43, 0x5b, 0x91, 0x92, 0x87, 0x95, 0xf4, 0x23, + 0xfd, 0xb2, 0x08, 0xea, 0x8f, 0xe7, 0xc5, 0x18, 0xdf, 0x33, 0xc6, 0x6d, 0xa2, 0x92, 0xa2, 0x19, + 0x5c, 0xca, 0x48, 0xcb, 0xcb, 0x3c, 0xdf, 0xcb, 0xf0, 0x24, 0xae, 0x12, 0x4d, 0xf6, 0xc3, 0x57, + 0xbd, 0x5c, 0x82, 0xda, 0xa2, 0x3e, 0x59, 0xdf, 0x8c, 0xb7, 0x67, 0x55, 0x0f, 0xb4, 0x56, 0xab, + 0x52, 0xe2, 0xfd, 0xc8, 0x7b, 0x80, 0x5e, 0xe4, 0x3e, 0xcf, 0x3c, 0xff, 0x59, 0x26, 0x22, 0x34, + 0x01, 0xe3, 0xde, 0xab, 0x74, 0x67, 0x72, 0x65, 0x91, 0xc5, 0x4d, 0xed, 0x2b, 0x96, 0x10, 0x24, + 0x4d, 0xb8, 0x4e, 0x40, 0xba, 0x92, 0x8d, 0xa8, 0xef, 0xf7, 0x57, 0x12, 0xeb, 0x30, 0x95, 0xec, + 0x14, 0x95, 0x2d, 0x4d, 0x94, 0x5a, 0xfc, 0x77, 0x5b, 0xf8, 0xc6, 0xb0, 0x6d, 0xb8, 0xde, 0xec, + 0x11, 0xd6, 0x7c, 0x51, 0xe6, 0x2c, 0x46, 0xe5, 0x41, 0x8b, 0x05, 0xc2, 0x2a, 0xa0, 0x44, 0x3c, + 0xb4, 0x05, 0x37, 0x0c, 0x66, 0x72, 0x33, 0xe4, 0x9a, 0x48, 0xdd, 0x80, 0xa5, 0x19, 0x32, 0x3d, + 0xbb, 0x0e, 0xf6, 0x21, 0x99, 0x0c, 0x14, 0x12, 0xcf, 0xd0, 0xe0, 0x93, 0x57, 0xb8, 0x22, 0x01, + 0x30, 0x45, 0x89, 0xa4, 0xe0, 0x03, 0xa3, 0x52, 0xec, 0x07, 0x36, 0x52, 0x53, 0xde, 0xbf, 0x06, + 0xa6, 0x7c, 0x67, 0x9c, 0xad, 0xcc, 0x56, 0x2c, 0x0e, 0xdd, 0x6a, 0xcb, 0x0b, 0x16, 0xa0, 0x9c, + 0x55, 0xc6, 0x7e, 0xfc, 0x99, 0x66, 0x41, 0xf0, 0x76, 0xdf, 0x03, 0x06, 0xec, 0x51, 0x90, 0xa7, + 0xfc, 0x50, 0x0e, 0x6a, 0x9d, 0xb5, 0xb9, 0xd5, 0x54, 0x28, 0x97, 0x04, 0x27, 0x35, 0x24, 0x87, + 0xc4, 0xd7, 0x17, 0x5b, 0xd0, 0x5c, 0x6c, 0x58, 0x89, 0xae, 0x96, 0xdd, 0x8e, 0x27, 0xa8, 0xfb, + 0x9a, 0x93, 0x54, 0x3a, 0xbd, 0x9e, 0x42, 0xd0, 0xb6, 0x7a, 0xc3, 0x08, 0xc6, 0xa5, 0x4f, 0xa6, + 0xc5, 0x8c, 0xfa, 0xb4, 0x74, 0x8f, 0x47, 0x5c, 0x85, 0x87, 0xf0, 0x46, 0x21, 0x40, 0x02, 0x8e, + 0x79, 0x19, 0xa7, 0xcf, 0xc6, 0xfa, 0x5c, 0x26, 0xfd, 0xa0, 0x3a, 0x66, 0xc1, 0xfa, 0x17, 0xef, + 0x07, 0x9f, 0x22, 0x1f, 0x0f, 0x8b, 0x80, 0x34, 0x8e, 0xc7, 0x2e, 0x42, 0xf0, 0x9b, 0x5d, 0xbc, + 0x26, 0xe7, 0x2d, 0xde, 0xbc, 0xdb, 0xeb, 0xc7, 0x29, 0x87, 0x07, 0xd9, 0xc7, 0xb5, 0x3e, 0x71, + 0xfb, 0xdc, 0x7f, 0x36, 0xa2, 0xe9, 0x58, 0xe6, 0xcc, 0x63, 0x75, 0x36, 0x52, 0xca, 0xe7, 0x06, + 0x1b, 0xa8, 0xbb, 0x03, 0x10, 0xce, 0xa5, 0xe9, 0x66, 0xac, 0xdd, 0x59, 0x0f, 0x3a, 0x90, 0x60, + 0x68, 0xe8, 0xeb, 0x60, 0xf1, 0xa8, 0xa0, 0xdc, 0x39, 0x07, 0x40, 0x05, 0x43, 0xb5, 0x6f, 0x3d, + 0x3b, 0x5a, 0x34, 0x53, 0xc2, 0x6c, 0xa4, 0x47, 0x4c, 0xe1, 0xfe, 0x7f, 0x37, 0xfb, 0x91, 0xca, + 0x28, 0x7a, 0xdc, 0xef, 0xde, 0xc4, 0x44, 0xf4, 0xc0, 0x22, 0xd2, 0x4c, 0x48, 0x16, 0x54, 0x01, + 0x7c, 0xdf, 0xe4, 0x3f, 0x29, 0x51, 0xae, 0x9c, 0x98, 0xf4, 0x73, 0x36, 0x94, 0x0d, 0xe2, 0xc8, + 0x35, 0xd9, 0xe2, 0xbc, 0x5c, 0x0b, 0xc7, 0xc6, 0xdd, 0x70, 0x2e, 0x6f, 0xdd, 0x23, 0xfe, 0xef, + 0x4c, 0xaf, 0x06, 0xce, 0x1c, 0x26, 0xf9, 0xe9, 0x02, 0x22, 0xe9, 0x4d, 0x26, 0x80, 0xbc, 0x5a, + 0x18, 0xc0, 0x2b, 0x76, 0xae, 0x65, 0x17, 0x6a, 0x56, 0xa4, 0xeb, 0xaa, 0xb7, 0x65, 0xe1, 0x55, + 0xfa, 0xe5, 0x08, 0x95, 0x3c, 0x33, 0xca, 0xa0, 0xb0, 0x03, 0x09, 0x22, 0x81, 0x98, 0x3b, 0x93, + 0x6e, 0xb2, 0x1a, 0xba, 0x05, 0x0c, 0xfd, 0xe4, 0x51, 0x10, 0xe0, 0x1c, 0x1e, 0xf5, 0x7c, 0xf8, + 0x22, 0x86, 0x6d, 0x00, 0x2d, 0x39, 0xaf, 0x8a, 0x25, 0xa2, 0xbc, 0x8b, 0x80, 0xfe, 0x1c, 0x87, + 0x5a, 0xd6, 0x7f, 0xf5, 0xeb, 0x13, 0x59, 0xf8, 0x37, 0xda, 0xf7, 0xf8, 0xe2, 0x39, 0xbb, 0x12, + 0x45, 0xb4, 0x2d, 0x03, 0x43, 0x44, 0x11, 0xf7, 0x0b, 0x32, 0x82, 0x0c, 0x68, 0xca, 0x8e, 0xf3, + 0x5c, 0x44, 0x02, 0x53, 0xb0, 0x0a, 0xa7, 0x74, 0x8b, 0x48, 0x8c, 0x54, 0xb0, 0x69, 0xfb, 0xfe, + 0xdf, 0xbe, 0xb7, 0x44, 0x66, 0x6c, 0x51, 0x8a, 0x6b, 0x62, 0xf9, 0x26, 0x63, 0xc2, 0x62, 0xe1, + 0x68, 0xcd, 0x24, 0xe5, 0xff, 0xa2, 0x01, 0x3d, 0x9b, 0x80, 0xed, 0xfd, 0x41, 0xb1, 0x9c, 0xba, + 0x60, 0xfd, 0x3d, 0xd3, 0x32, 0xa9, 0x1d, 0x16, 0xd7, 0x9e, 0xc8, 0x08, 0xe8, 0xb7, 0x8c, 0x67, + 0xb1, 0x8e, 0x53, 0xaf, 0xa5, 0x71, 0x8c, 0xab, 0x50, 0x74, 0xf8, 0x93, 0x00, 0x79, 0xbf, 0xa5, + 0xda, 0x78, 0x82, 0x57, 0x97, 0x8b, 0xfe, 0x61, 0x3c, 0xd3, 0xa1, 0xca, 0xbe, 0xde, 0x60, 0x5a, + 0xb6, 0x10, 0x64, 0xf9, 0x86, 0x44, 0x9c, 0xa8, 0xad, 0xd3, 0x52, 0x12, 0xa0, 0xcc, 0x8b, 0xaa, + 0x39, 0xec, 0x9c, 0xc3, 0x43, 0x43, 0xe8, 0xd7, 0x79, 0xdb, 0xb8, 0x59, 0x85, 0x96, 0x7a, 0x92, + 0x38, 0xff, 0x24, 0x10, 0xed, 0xc1, 0x87, 0x5d, 0x86, 0x34, 0x87, 0x2b, 0xd0, 0x5d, 0x3d, 0xac, + 0x2c, 0x27, 0xd2, 0xa9, 0x75, 0x2d, 0xa3, 0xf2, 0xd3, 0xdb, 0xe4, 0xa6, 0xde, 0xe9, 0x0b, 0xd2, + 0x61, 0x5c, 0xd5, 0xdd, 0xd1, 0x6d, 0x1f, 0x68, 0x27, 0xb3, 0x40, 0x60, 0x1a, 0x5d, 0x5b, 0xa9, + 0xcd, 0x85, 0x85, 0x4d, 0x73, 0xa9, 0x16, 0x46, 0x65, 0x4a, 0xff, 0x72, 0xb1, 0x1c, 0x73, 0xa2 +}; + +struct Row { + std::array input; + std::array expected; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x5a5a00d2, 0x007a0101, 0x5a5afff2, 0x00650303, 0x5a5a26f2, 0x7aeb0505, 0x004f120c, 0x00770018, 0x89d200d0, 0x5a5ae80e, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x5a5a00f2, 0x00350101, 0x5a5a0026, 0xffa60303, 0x5a5a6526, 0x357a0505, 0xfffad212, 0x00c4005d, 0x18890074, 0x5a5a81e8, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x5a5a00a4, 0x005a0101, 0x5a5aff9e, 0xffea0303, 0x5a5a449e, 0x5a600505, 0x0008459f, 0x0070ffce, 0x8aa0003c, 0x5a5a199f, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x5a5a00dd, 0x00600101, 0x5a5affa4, 0x00440303, 0x5a5a9ea4, 0x60de0505, 0x00009fad, 0x00d9ff8a, 0xa0450061, 0x5a5a9f09, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x5a5a0026, 0x00970101, 0x5a5a0065, 0x000c0303, 0x5a5aa665, 0x97350505, 0x001589d2, 0x001eff95, 0x5d180024, 0x5a5a3681, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0101, 0x5a5a0202, 0x5a5a0303, 0x5a5a0404, 0x5a5a0505, 0x5a5a0606, 0x5a5a0707, 0x5a5a0808, 0x5a5a0909, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x5a5a007a, 0x005a0101, 0x5a5a0035, 0x00430303, 0x5a5a9735, 0x5aec0505, 0xff963c81, 0x001e0009, 0x5f51008b, 0x5a5a2967, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x5a5a0097, 0x00dc0101, 0x5a5a0043, 0xff810303, 0x5a5a9d43, 0xdc290505, 0xffa25f51, 0x0031002e, 0x320900f8, 0x5a5aefa2, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x5a5a0000, 0x00580101, 0x5a5aff86, 0x00490303, 0x5a5a3386, 0x58480505, 0xffd8fac0, 0x008e0059, 0x010d0059, 0x5a5ab17d, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x5a5a0021, 0x008b0101, 0x5a5aff9a, 0xffa10303, 0x5a5ad19a, 0x8be80505, 0xffdbe3c7, 0x00e1005d, 0xb41c009b, 0x5a5ae0e5, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x5a5a00fa, 0x003d0101, 0x5a5affc8, 0xffab0303, 0x5a5a17c8, 0x3d400505, 0xffa18ce4, 0x00aeffd6, 0xa2c90022, 0x5a5ab17e, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x5a5a008d, 0x00740101, 0x5a5aff8b, 0x00290303, 0x5a5a538b, 0x74510505, 0x0038e26d, 0x00080012, 0xfc1a0055, 0x5a5a3515, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0101, 0x5a5a0202, 0x5a5a0303, 0x5a5a0404, 0x5a5a0505, 0x5a5a0606, 0x5a5a0707, 0x5a5a0808, 0x5a5a0909, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x5a5a005d, 0x00660101, 0x5a5a0057, 0xffc70303, 0x5a5a7957, 0x66f90505, 0x0062c58d, 0x00d60048, 0x35e900f8, 0x5a5a406c, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x5a5a0010, 0x006e0101, 0x5a5a001c, 0xffeb0303, 0x5a5afc1c, 0x6e740505, 0xffe8f83a, 0x003f0015, 0x1ae000da, 0x5a5ae70a, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x5a5a0015, 0x00be0101, 0x5a5affa4, 0x00360303, 0x5a5a78a4, 0xbe5d0505, 0x0016a026, 0x00cd0001, 0x6d03001d, 0x5a5a1fab, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x5a5a00a0, 0x006b0101, 0x5a5affc7, 0xffa60303, 0x5a5a4ec7, 0x6b230505, 0x0079954e, 0x008e0063, 0x6c3f006e, 0x5a5a8072, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x5a5a00c8, 0x00d30101, 0x5a5a0027, 0xffe70303, 0x5a5a3527, 0xd3a50505, 0x000d88ca, 0x00fcffe4, 0x9c2a004f, 0x5a5ac8fa, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x5a5a00cd, 0x00fd0101, 0x5a5afffc, 0x00520303, 0x5a5ae9fc, 0xfded0505, 0xffebd866, 0x00d30053, 0x0d8f00ff, 0x5a5aaef5, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0101, 0x5a5a0202, 0x5a5a0303, 0x5a5a0404, 0x5a5a0505, 0x5a5a0606, 0x5a5a0707, 0x5a5a0808, 0x5a5a0909, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x5a5a00fc, 0x00b00101, 0x5a5a007e, 0x00480303, 0x5a5a267e, 0xb0e40505, 0x0031a49e, 0x001fffd3, 0x0b2500e7, 0x5a5a83e4, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x5a5a0079, 0x00ba0101, 0x5a5affc5, 0xff8c0303, 0x5a5a69c5, 0xbaea0505, 0x006fa415, 0x0071ffe5, 0x3678009f, 0x5a5a6d03, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x5a5a0013, 0x00330101, 0x5a5a005f, 0xffdd0303, 0x5a5a835f, 0x334e0505, 0xff8a9a72, 0x0033ffc7, 0xc64200ab, 0x5a5a1b01, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x5a5a00ca, 0x001b0101, 0x5a5a0036, 0xff9f0303, 0x5a5a6436, 0x1b820505, 0xffcc9013, 0x007f0025, 0x2be9009a, 0x5a5a1b1c, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x5a5a0088, 0x000d0101, 0x5a5affe4, 0x00100303, 0x5a5aaae4, 0x0d050505, 0x007ac1de, 0x0085fff1, 0xb6ea0010, 0x5a5a343f, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x5a5a0013, 0x00ef0101, 0x5a5a006b, 0x006b0303, 0x5a5aea6b, 0xef220505, 0x001eacb2, 0x005b0043, 0x945a00b4, 0x5a5abd4d, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0101, 0x5a5a0202, 0x5a5a0303, 0x5a5a0404, 0x5a5a0505, 0x5a5a0606, 0x5a5a0707, 0x5a5a0808, 0x5a5a0909, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x5a5a00af, 0x00150101, 0x5a5a0040, 0x006d0303, 0x5a5afb40, 0x157c0505, 0x000b7b2f, 0x0071ffb8, 0xce00000b, 0x5a5a3e5b, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x5a5a0073, 0x002f0101, 0x5a5a0058, 0x00270303, 0x5a5ac858, 0x2f610505, 0xffd867e7, 0x0090002a, 0x88ca00b8, 0x5a5a9bb0, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x5a5a009d, 0x00de0101, 0x5a5affc0, 0xffa40303, 0x5a5a23c0, 0xde370505, 0x00597c49, 0x00f7ffed, 0xe90c00ab, 0x5a5a782b, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x5a5a00fe, 0x009a0101, 0x5a5a002e, 0x00150303, 0x5a5a602e, 0x9ae40505, 0x003c080d, 0x008dffde, 0x5e8e0005, 0x5a5af27c, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0101, 0x5a5a0202, 0x5a5a0303, 0x5a5a0404, 0x5a5a0505, 0x5a5a0606, 0x5a5a0707, 0x5a5a0808, 0x5a5a0909, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}} +}}; + +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, "flat d16 loads: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~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::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%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest) { + Output.fill(Sentinel); + Dispatch(device, FlatD16LoadsCode, guest.Data()); + const std::string wave = "flat d16 loads: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].expected; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, Region.data(), Region.size()); + Run(*device, guest); + std::puts("flat d16 loads tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} From 9f7d1bfa9850f1b297fda93a09afc4555207600d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 03:54:49 +0200 Subject: [PATCH 062/164] test(shader): execute buffer_atomic_or_x2 and buffer_atomic_swap_x2 against hardware results Co-Authored-By: Claude --- .../tests/execution/BufferAtomicsOrSwap64.cpp | 296 ++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicsOrSwap64.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicsOrSwap64.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicsOrSwap64.cpp new file mode 100644 index 000000000..be03cdc3b --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicsOrSwap64.cpp @@ -0,0 +1,296 @@ +#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 MaxThreads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 128; +constexpr std::uint32_t Checked = 28; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Wave32Code{ + 0x34020084, 0x34060089, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xbe880304, 0xbe890305, 0xbe8a03ff, + 0x00002134, 0xbe8b03ff, 0x31016fac, 0xe0741100, 0x80010403, 0xe0741108, 0x80010403, 0xe0741110, + 0x80010403, 0xe0741118, 0x80010403, 0xe0741120, 0x80010403, 0xe0741128, 0x80010403, 0xe0741130, + 0x80010403, 0xe0741138, 0x80010403, 0xbf8c3f70, 0x7e140306, 0x7e160307, 0x7e180306, 0x7e1a0307, + 0x7e1c0306, 0x7e1e0307, 0x7e200306, 0x7e220307, 0x7e240306, 0x7e260307, 0x7e280306, 0x7e2a0307, + 0x7e500306, 0x7e520307, 0xe1685100, 0x80010a03, 0xe1681108, 0x80012803, 0xe1405110, 0x80010c03, + 0xe1401118, 0x80012803, 0x36a00e83, 0x7d8aa080, 0xbea83c6a, 0xe1685120, 0x80010e03, 0xe1405128, + 0x80011003, 0xbefe03c1, 0xe1685130, 0x80021203, 0xe1405138, 0x80021403, 0xbf8c3f70, 0xe034d100, + 0x80011603, 0xe034d108, 0x80011803, 0xe034d110, 0x80011a03, 0xe034d118, 0x80011c03, 0xe034d120, + 0x80011e03, 0xe034d128, 0x80012003, 0xe034d130, 0x80012203, 0xe034d138, 0x80012403, 0xbf8c3f70, + 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, + 0xe0781040, 0x80011a03, 0xe0781050, 0x80011e03, 0xe0781060, 0x80012203, 0xbf810000, +}; + +alignas(256) constexpr std::array Wave64Code{ + 0x34020084, 0x34060089, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xbe880304, 0xbe890305, 0xbe8a03ff, + 0x00004134, 0xbe8b03ff, 0x31016fac, 0xe0741100, 0x80010403, 0xe0741108, 0x80010403, 0xe0741110, + 0x80010403, 0xe0741118, 0x80010403, 0xe0741120, 0x80010403, 0xe0741128, 0x80010403, 0xe0741130, + 0x80010403, 0xe0741138, 0x80010403, 0xbf8c3f70, 0x7e140306, 0x7e160307, 0x7e180306, 0x7e1a0307, + 0x7e1c0306, 0x7e1e0307, 0x7e200306, 0x7e220307, 0x7e240306, 0x7e260307, 0x7e280306, 0x7e2a0307, + 0x7e500306, 0x7e520307, 0xe1685100, 0x80010a03, 0xe1681108, 0x80012803, 0xe1405110, 0x80010c03, + 0xe1401118, 0x80012803, 0x36a00e83, 0x7d8aa080, 0xbea8246a, 0xe1685120, 0x80010e03, 0xe1405128, + 0x80011003, 0xbefe04c1, 0xe1685130, 0x80021203, 0xe1405138, 0x80021403, 0xbf8c3f70, 0xe034d100, + 0x80011603, 0xe034d108, 0x80011803, 0xe034d110, 0x80011a03, 0xe034d118, 0x80011c03, 0xe034d120, + 0x80011e03, 0xe034d128, 0x80012003, 0xe034d130, 0x80012203, 0xe034d138, 0x80012403, 0xbf8c3f70, + 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, + 0xe0781040, 0x80011a03, 0xe0781050, 0x80011e03, 0xe0781060, 0x80012203, 0xbf810000, +}; + +constexpr std::uint32_t Rows[64][4] = { + {0x6a6ae768u, 0x89abcdefu, 0x466de486u, 0x00ff00ffu}, + {0x00ff00ffu, 0x00ff00ffu, 0x80000000u, 0x2e09e4b8u}, + {0x2be38eccu, 0xdd1775a9u, 0xab195b47u, 0xe88f40a2u}, + {0xe9a462ceu, 0x00000000u, 0x2b1141d6u, 0xac0e36d5u}, + {0x64ffc604u, 0xbcfdc56au, 0x2af2003cu, 0x8f4c6fc3u}, + {0x70ce7993u, 0x00ff00ffu, 0x80000000u, 0x7b05bc1au}, + {0x00000001u, 0xba30a818u, 0x6e325d76u, 0x7767929du}, + {0x7e974317u, 0x00ff00ffu, 0xddd59451u, 0xdf30a0eau}, + {0xffffffffu, 0x159840eau, 0xe41c70dbu, 0x88d1f52cu}, + {0x89abcdefu, 0x00000000u, 0x6dd9de7bu, 0x93cd7e6fu}, + {0x7fffffffu, 0x6d924e06u, 0x379cf45fu, 0x5a176daau}, + {0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xdd343334u}, + {0xf4c65508u, 0x774afa8fu, 0x4bf01f77u, 0x80000000u}, + {0x0680d87fu, 0xe2aeba83u, 0x0cae8376u, 0x21027ce8u}, + {0x12345678u, 0x24483dacu, 0x343089c1u, 0xfe3a9921u}, + {0x9dd441ebu, 0x7fffffffu, 0x3f250fabu, 0x5a25508fu}, + {0x80000000u, 0x940c48d1u, 0x00000001u, 0x12345678u}, + {0x06a06e39u, 0xdca4271cu, 0x89abcdefu, 0x80000000u}, + {0x00ff00ffu, 0xdadef6e0u, 0xdfd0b7a2u, 0xffffffffu}, + {0x436d4bd7u, 0xb4373875u, 0xc71d5b33u, 0x63b433c8u}, + {0xe1a9611du, 0xdd70b68bu, 0xc3e41dfbu, 0x80000000u}, + {0x00ff00ffu, 0x00ff00ffu, 0x2a2b5f67u, 0xf2d9cc0du}, + {0x89abcdefu, 0x38573260u, 0x5b120ed1u, 0x49e07752u}, + {0xf60106b7u, 0xd61d1405u, 0xc58ccccdu, 0xbce22306u}, + {0x7fffffffu, 0x80000000u, 0x15452b7bu, 0xb8054218u}, + {0x12345678u, 0x00000001u, 0xa7387a4cu, 0xffffffffu}, + {0x00000000u, 0x90322d5bu, 0x7ee058dau, 0x89abcdefu}, + {0x7fffffffu, 0x93b8f2e0u, 0x8245e09du, 0x168b95f5u}, + {0x80000000u, 0x4ec04618u, 0x44b6a55du, 0x89abcdefu}, + {0x80a3acc4u, 0x7fffffffu, 0x1f016769u, 0xac7c83b9u}, + {0x725f6ff6u, 0x00000000u, 0x45040843u, 0x00ff00ffu}, + {0x50d14398u, 0x7f767333u, 0x2369eadcu, 0x00000001u}, + {0xac8c599bu, 0x7fffffffu, 0xd8f36844u, 0x4761f31au}, + {0xd078019fu, 0x60ac3d38u, 0x7fffffffu, 0x20b5d1d4u}, + {0x2925feb3u, 0x00000001u, 0x7fffffffu, 0xb16614f4u}, + {0x86d5360fu, 0xfaf40417u, 0xd4424852u, 0x89631325u}, + {0xffffffffu, 0xde274210u, 0xe96e12c9u, 0x65305d1cu}, + {0x1043c4f3u, 0x8a860eeau, 0xe9579523u, 0x7563acb0u}, + {0x310b55c7u, 0x12345678u, 0x12345678u, 0x96188707u}, + {0x80000000u, 0xbcabb1bau, 0x12345678u, 0xffffffffu}, + {0x72fdad03u, 0x00000001u, 0x028f3b02u, 0x6c60da42u}, + {0xe473cdc9u, 0x75eb16c7u, 0x12345678u, 0xe0d59f3cu}, + {0x89abcdefu, 0x74102553u, 0x1cb3e931u, 0xe536aae9u}, + {0x4d126f38u, 0x7d42a12au, 0x50692ce5u, 0xa68132ddu}, + {0x744e3f0eu, 0x7fffffffu, 0xde298785u, 0x80000000u}, + {0xad554ba1u, 0x7c5153d0u, 0xeaf8edcfu, 0xc8aab92fu}, + {0xf8f008c5u, 0x0f119987u, 0x1bd39c7cu, 0x5dc52692u}, + {0xd03b304du, 0x00ff00ffu, 0x89abcdefu, 0x991132c0u}, + {0xa92c8f72u, 0x07b9077du, 0x66546794u, 0xf6a5f7c1u}, + {0xb5691c18u, 0x5d6c710au, 0x7ae6b997u, 0xbfe4abb0u}, + {0xc9570a1cu, 0xefa96b6eu, 0x00000000u, 0xcd12bfb3u}, + {0x00000000u, 0x37eadcd2u, 0x3a6d60aau, 0x89abcdefu}, + {0x114a2d32u, 0x00000000u, 0x89abcdefu, 0x12345678u}, + {0x14090b01u, 0xffffffffu, 0x0eb083edu, 0x1a0caeefu}, + {0x12345678u, 0x4521d7efu, 0x00000001u, 0x80000000u}, + {0xefa94a8eu, 0x561bfb20u, 0x19b0c2f0u, 0x69b1111du}, + {0xbcf7629fu, 0xaf1de605u, 0x41622456u, 0x00000000u}, + {0xd47a46a9u, 0x12da9645u, 0x7ded0e8du, 0x00000000u}, + {0x697d50dbu, 0x89abcdefu, 0xdeacacaau, 0xcf93dd30u}, + {0x00ff00ffu, 0xb6a48819u, 0x1f752ee6u, 0x80000000u}, + {0x12345678u, 0xbb36bb87u, 0x0888385bu, 0x5cb6c6d7u}, + {0xd24df64du, 0x9595f0c7u, 0x00000000u, 0x7fffffffu}, + {0xffffffffu, 0x00ff00ffu, 0x00000001u, 0xfe7ffe21u}, + {0xdd6537d2u, 0x6c2ab542u, 0xffffffffu, 0xde5ffb8au} +}; + +constexpr std::uint32_t Expected32[32][28] = { + {0x6a6ae768u, 0x89abcdefu, 0x6a6ae768u, 0x89abcdefu, 0x6a6ae768u, 0x89abcdefu, 0x6a6ae768u, 0x89abcdefu, 0x6a6ae768u, 0x89abcdefu, 0x6a6ae768u, 0x89abcdefu, 0x6e6fe7eeu, 0x89ffcdffu, 0x6e6fe7eeu, 0x89ffcdffu, 0x466de486u, 0x00ff00ffu, 0x466de486u, 0x00ff00ffu, 0x6e6fe7eeu, 0x89ffcdffu, 0x466de486u, 0x00ff00ffu, 0x6e6fe7eeu, 0x89ffcdffu, 0x466de486u, 0x00ff00ffu}, + {0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x80000000u, 0x2e09e4b8u, 0x80000000u, 0x2e09e4b8u, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x80ff00ffu, 0x2effe4ffu, 0x80ff00ffu, 0x2effe4ffu, 0x80000000u, 0x2e09e4b8u, 0x80000000u, 0x2e09e4b8u, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x80ff00ffu, 0x2effe4ffu, 0x80000000u, 0x2e09e4b8u}, + {0x2be38eccu, 0xdd1775a9u, 0x2be38eccu, 0xdd1775a9u, 0x2be38eccu, 0xdd1775a9u, 0x2be38eccu, 0xdd1775a9u, 0x2be38eccu, 0xdd1775a9u, 0x2be38eccu, 0xdd1775a9u, 0xabfbdfcfu, 0xfd9f75abu, 0xabfbdfcfu, 0xfd9f75abu, 0xab195b47u, 0xe88f40a2u, 0xab195b47u, 0xe88f40a2u, 0xabfbdfcfu, 0xfd9f75abu, 0xab195b47u, 0xe88f40a2u, 0xabfbdfcfu, 0xfd9f75abu, 0xab195b47u, 0xe88f40a2u}, + {0xe9a462ceu, 0x00000000u, 0xe9a462ceu, 0x00000000u, 0xe9a462ceu, 0x00000000u, 0xe9a462ceu, 0x00000000u, 0xe9a462ceu, 0x00000000u, 0xe9a462ceu, 0x00000000u, 0xebb563deu, 0xac0e36d5u, 0xebb563deu, 0xac0e36d5u, 0x2b1141d6u, 0xac0e36d5u, 0x2b1141d6u, 0xac0e36d5u, 0xebb563deu, 0xac0e36d5u, 0x2b1141d6u, 0xac0e36d5u, 0xebb563deu, 0xac0e36d5u, 0x2b1141d6u, 0xac0e36d5u}, + {0x64ffc604u, 0xbcfdc56au, 0x64ffc604u, 0xbcfdc56au, 0x64ffc604u, 0xbcfdc56au, 0x64ffc604u, 0xbcfdc56au, 0x64ffc604u, 0xbcfdc56au, 0x64ffc604u, 0xbcfdc56au, 0x6effc63cu, 0xbffdefebu, 0x6effc63cu, 0xbffdefebu, 0x2af2003cu, 0x8f4c6fc3u, 0x2af2003cu, 0x8f4c6fc3u, 0x6effc63cu, 0xbffdefebu, 0x2af2003cu, 0x8f4c6fc3u, 0x6effc63cu, 0xbffdefebu, 0x2af2003cu, 0x8f4c6fc3u}, + {0x70ce7993u, 0x00ff00ffu, 0x70ce7993u, 0x00ff00ffu, 0x70ce7993u, 0x00ff00ffu, 0x70ce7993u, 0x00ff00ffu, 0x70ce7993u, 0x00ff00ffu, 0x70ce7993u, 0x00ff00ffu, 0xf0ce7993u, 0x7bffbcffu, 0xf0ce7993u, 0x7bffbcffu, 0x80000000u, 0x7b05bc1au, 0x80000000u, 0x7b05bc1au, 0xf0ce7993u, 0x7bffbcffu, 0x80000000u, 0x7b05bc1au, 0xf0ce7993u, 0x7bffbcffu, 0x80000000u, 0x7b05bc1au}, + {0x00000001u, 0xba30a818u, 0x00000001u, 0xba30a818u, 0x00000001u, 0xba30a818u, 0x00000001u, 0xba30a818u, 0x00000001u, 0xba30a818u, 0x00000001u, 0xba30a818u, 0x6e325d77u, 0xff77ba9du, 0x6e325d77u, 0xff77ba9du, 0x6e325d76u, 0x7767929du, 0x6e325d76u, 0x7767929du, 0x6e325d77u, 0xff77ba9du, 0x6e325d76u, 0x7767929du, 0x6e325d77u, 0xff77ba9du, 0x6e325d76u, 0x7767929du}, + {0x7e974317u, 0x00ff00ffu, 0x7e974317u, 0x00ff00ffu, 0x7e974317u, 0x00ff00ffu, 0x7e974317u, 0x00ff00ffu, 0x7e974317u, 0x00ff00ffu, 0x7e974317u, 0x00ff00ffu, 0xffd7d757u, 0xdfffa0ffu, 0xffd7d757u, 0xdfffa0ffu, 0xddd59451u, 0xdf30a0eau, 0xddd59451u, 0xdf30a0eau, 0xffd7d757u, 0xdfffa0ffu, 0xddd59451u, 0xdf30a0eau, 0xffd7d757u, 0xdfffa0ffu, 0xddd59451u, 0xdf30a0eau}, + {0xffffffffu, 0x159840eau, 0xffffffffu, 0x159840eau, 0xe41c70dbu, 0x88d1f52cu, 0xe41c70dbu, 0x88d1f52cu, 0xffffffffu, 0x159840eau, 0xffffffffu, 0x159840eau, 0xffffffffu, 0x9dd9f5eeu, 0xffffffffu, 0x9dd9f5eeu, 0xe41c70dbu, 0x88d1f52cu, 0xe41c70dbu, 0x88d1f52cu, 0xffffffffu, 0x159840eau, 0xffffffffu, 0x159840eau, 0xffffffffu, 0x9dd9f5eeu, 0xe41c70dbu, 0x88d1f52cu}, + {0x89abcdefu, 0x00000000u, 0x89abcdefu, 0x00000000u, 0x89abcdefu, 0x00000000u, 0x89abcdefu, 0x00000000u, 0x89abcdefu, 0x00000000u, 0x89abcdefu, 0x00000000u, 0xedfbdfffu, 0x93cd7e6fu, 0xedfbdfffu, 0x93cd7e6fu, 0x6dd9de7bu, 0x93cd7e6fu, 0x6dd9de7bu, 0x93cd7e6fu, 0xedfbdfffu, 0x93cd7e6fu, 0x6dd9de7bu, 0x93cd7e6fu, 0xedfbdfffu, 0x93cd7e6fu, 0x6dd9de7bu, 0x93cd7e6fu}, + {0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x7f976faeu, 0x7fffffffu, 0x7f976faeu, 0x379cf45fu, 0x5a176daau, 0x379cf45fu, 0x5a176daau, 0x7fffffffu, 0x7f976faeu, 0x379cf45fu, 0x5a176daau, 0x7fffffffu, 0x7f976faeu, 0x379cf45fu, 0x5a176daau}, + {0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xdd343334u, 0x89abcdefu, 0xdd343334u, 0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xfd3eb3bdu, 0x89abcdefu, 0xfd3eb3bdu, 0x89abcdefu, 0xdd343334u, 0x89abcdefu, 0xdd343334u, 0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xfd3eb3bdu, 0x89abcdefu, 0xdd343334u}, + {0xf4c65508u, 0x774afa8fu, 0xf4c65508u, 0x774afa8fu, 0x4bf01f77u, 0x80000000u, 0x4bf01f77u, 0x80000000u, 0xf4c65508u, 0x774afa8fu, 0xf4c65508u, 0x774afa8fu, 0xfff65f7fu, 0xf74afa8fu, 0xfff65f7fu, 0xf74afa8fu, 0x4bf01f77u, 0x80000000u, 0x4bf01f77u, 0x80000000u, 0xf4c65508u, 0x774afa8fu, 0xf4c65508u, 0x774afa8fu, 0xfff65f7fu, 0xf74afa8fu, 0x4bf01f77u, 0x80000000u}, + {0x0680d87fu, 0xe2aeba83u, 0x0680d87fu, 0xe2aeba83u, 0x0cae8376u, 0x21027ce8u, 0x0cae8376u, 0x21027ce8u, 0x0680d87fu, 0xe2aeba83u, 0x0680d87fu, 0xe2aeba83u, 0x0eaedb7fu, 0xe3aefeebu, 0x0eaedb7fu, 0xe3aefeebu, 0x0cae8376u, 0x21027ce8u, 0x0cae8376u, 0x21027ce8u, 0x0680d87fu, 0xe2aeba83u, 0x0680d87fu, 0xe2aeba83u, 0x0eaedb7fu, 0xe3aefeebu, 0x0cae8376u, 0x21027ce8u}, + {0x12345678u, 0x24483dacu, 0x12345678u, 0x24483dacu, 0x12345678u, 0x24483dacu, 0x12345678u, 0x24483dacu, 0x12345678u, 0x24483dacu, 0x12345678u, 0x24483dacu, 0x3634dff9u, 0xfe7abdadu, 0x3634dff9u, 0xfe7abdadu, 0x343089c1u, 0xfe3a9921u, 0x343089c1u, 0xfe3a9921u, 0x3634dff9u, 0xfe7abdadu, 0x343089c1u, 0xfe3a9921u, 0x3634dff9u, 0xfe7abdadu, 0x343089c1u, 0xfe3a9921u}, + {0x9dd441ebu, 0x7fffffffu, 0x9dd441ebu, 0x7fffffffu, 0x9dd441ebu, 0x7fffffffu, 0x9dd441ebu, 0x7fffffffu, 0x9dd441ebu, 0x7fffffffu, 0x9dd441ebu, 0x7fffffffu, 0xbff54febu, 0x7fffffffu, 0xbff54febu, 0x7fffffffu, 0x3f250fabu, 0x5a25508fu, 0x3f250fabu, 0x5a25508fu, 0xbff54febu, 0x7fffffffu, 0x3f250fabu, 0x5a25508fu, 0xbff54febu, 0x7fffffffu, 0x3f250fabu, 0x5a25508fu}, + {0x80000000u, 0x940c48d1u, 0x80000000u, 0x940c48d1u, 0x00000001u, 0x12345678u, 0x00000001u, 0x12345678u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000001u, 0x963c5ef9u, 0x80000001u, 0x963c5ef9u, 0x00000001u, 0x12345678u, 0x00000001u, 0x12345678u, 0x80000000u, 0x940c48d1u, 0x80000000u, 0x940c48d1u, 0x80000000u, 0x940c48d1u, 0x80000000u, 0x940c48d1u}, + {0x06a06e39u, 0xdca4271cu, 0x06a06e39u, 0xdca4271cu, 0x89abcdefu, 0x80000000u, 0x89abcdefu, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x8fabefffu, 0xdca4271cu, 0x8fabefffu, 0xdca4271cu, 0x89abcdefu, 0x80000000u, 0x89abcdefu, 0x80000000u, 0x06a06e39u, 0xdca4271cu, 0x06a06e39u, 0xdca4271cu, 0x06a06e39u, 0xdca4271cu, 0x06a06e39u, 0xdca4271cu}, + {0x00ff00ffu, 0xdadef6e0u, 0x00ff00ffu, 0xdadef6e0u, 0x00ff00ffu, 0xdadef6e0u, 0x00ff00ffu, 0xdadef6e0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdfffb7ffu, 0xffffffffu, 0xdfffb7ffu, 0xffffffffu, 0xdfd0b7a2u, 0xffffffffu, 0xdfd0b7a2u, 0xffffffffu, 0xdfffb7ffu, 0xffffffffu, 0xdfd0b7a2u, 0xffffffffu, 0x00ff00ffu, 0xdadef6e0u, 0x00ff00ffu, 0xdadef6e0u}, + {0x436d4bd7u, 0xb4373875u, 0x436d4bd7u, 0xb4373875u, 0xc71d5b33u, 0x63b433c8u, 0xc71d5b33u, 0x63b433c8u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xc77d5bf7u, 0xf7b73bfdu, 0xc77d5bf7u, 0xf7b73bfdu, 0xc71d5b33u, 0x63b433c8u, 0xc71d5b33u, 0x63b433c8u, 0x436d4bd7u, 0xb4373875u, 0x436d4bd7u, 0xb4373875u, 0x436d4bd7u, 0xb4373875u, 0x436d4bd7u, 0xb4373875u}, + {0xe1a9611du, 0xdd70b68bu, 0xe1a9611du, 0xdd70b68bu, 0xc3e41dfbu, 0x80000000u, 0xc3e41dfbu, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xe3ed7dffu, 0xdd70b68bu, 0xe3ed7dffu, 0xdd70b68bu, 0xc3e41dfbu, 0x80000000u, 0xc3e41dfbu, 0x80000000u, 0xe1a9611du, 0xdd70b68bu, 0xe1a9611du, 0xdd70b68bu, 0xe1a9611du, 0xdd70b68bu, 0xe1a9611du, 0xdd70b68bu}, + {0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x2aff5fffu, 0xf2ffccffu, 0x2aff5fffu, 0xf2ffccffu, 0x2a2b5f67u, 0xf2d9cc0du, 0x2a2b5f67u, 0xf2d9cc0du, 0x2aff5fffu, 0xf2ffccffu, 0x2a2b5f67u, 0xf2d9cc0du, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu}, + {0x89abcdefu, 0x38573260u, 0x89abcdefu, 0x38573260u, 0x89abcdefu, 0x38573260u, 0x89abcdefu, 0x38573260u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdbbbcfffu, 0x79f77772u, 0xdbbbcfffu, 0x79f77772u, 0x5b120ed1u, 0x49e07752u, 0x5b120ed1u, 0x49e07752u, 0xdbbbcfffu, 0x79f77772u, 0x5b120ed1u, 0x49e07752u, 0x89abcdefu, 0x38573260u, 0x89abcdefu, 0x38573260u}, + {0xf60106b7u, 0xd61d1405u, 0xf60106b7u, 0xd61d1405u, 0xf60106b7u, 0xd61d1405u, 0xf60106b7u, 0xd61d1405u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xf78dceffu, 0xfeff3707u, 0xf78dceffu, 0xfeff3707u, 0xc58ccccdu, 0xbce22306u, 0xc58ccccdu, 0xbce22306u, 0xf78dceffu, 0xfeff3707u, 0xc58ccccdu, 0xbce22306u, 0xf60106b7u, 0xd61d1405u, 0xf60106b7u, 0xd61d1405u}, + {0x7fffffffu, 0x80000000u, 0x7fffffffu, 0x80000000u, 0x15452b7bu, 0xb8054218u, 0x15452b7bu, 0xb8054218u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fffffffu, 0xb8054218u, 0x7fffffffu, 0xb8054218u, 0x15452b7bu, 0xb8054218u, 0x15452b7bu, 0xb8054218u, 0x7fffffffu, 0x80000000u, 0x7fffffffu, 0x80000000u, 0x7fffffffu, 0x80000000u, 0x7fffffffu, 0x80000000u}, + {0x12345678u, 0x00000001u, 0x12345678u, 0x00000001u, 0x12345678u, 0x00000001u, 0x12345678u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xb73c7e7cu, 0xffffffffu, 0xb73c7e7cu, 0xffffffffu, 0xa7387a4cu, 0xffffffffu, 0xa7387a4cu, 0xffffffffu, 0xb73c7e7cu, 0xffffffffu, 0xa7387a4cu, 0xffffffffu, 0x12345678u, 0x00000001u, 0x12345678u, 0x00000001u}, + {0x00000000u, 0x90322d5bu, 0x00000000u, 0x90322d5bu, 0x00000000u, 0x90322d5bu, 0x00000000u, 0x90322d5bu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ee058dau, 0x99bbedffu, 0x7ee058dau, 0x99bbedffu, 0x7ee058dau, 0x89abcdefu, 0x7ee058dau, 0x89abcdefu, 0x7ee058dau, 0x99bbedffu, 0x7ee058dau, 0x89abcdefu, 0x00000000u, 0x90322d5bu, 0x00000000u, 0x90322d5bu}, + {0x7fffffffu, 0x93b8f2e0u, 0x7fffffffu, 0x93b8f2e0u, 0x7fffffffu, 0x93b8f2e0u, 0x7fffffffu, 0x93b8f2e0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x97bbf7f5u, 0xffffffffu, 0x97bbf7f5u, 0x8245e09du, 0x168b95f5u, 0x8245e09du, 0x168b95f5u, 0xffffffffu, 0x97bbf7f5u, 0x8245e09du, 0x168b95f5u, 0x7fffffffu, 0x93b8f2e0u, 0x7fffffffu, 0x93b8f2e0u}, + {0x80000000u, 0x4ec04618u, 0x80000000u, 0x4ec04618u, 0x80000000u, 0x4ec04618u, 0x80000000u, 0x4ec04618u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xc4b6a55du, 0xcfebcfffu, 0xc4b6a55du, 0xcfebcfffu, 0x44b6a55du, 0x89abcdefu, 0x44b6a55du, 0x89abcdefu, 0xc4b6a55du, 0xcfebcfffu, 0x44b6a55du, 0x89abcdefu, 0x80000000u, 0x4ec04618u, 0x80000000u, 0x4ec04618u}, + {0x80a3acc4u, 0x7fffffffu, 0x80a3acc4u, 0x7fffffffu, 0x80a3acc4u, 0x7fffffffu, 0x80a3acc4u, 0x7fffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x9fa3efedu, 0xffffffffu, 0x9fa3efedu, 0xffffffffu, 0x1f016769u, 0xac7c83b9u, 0x1f016769u, 0xac7c83b9u, 0x9fa3efedu, 0xffffffffu, 0x1f016769u, 0xac7c83b9u, 0x80a3acc4u, 0x7fffffffu, 0x80a3acc4u, 0x7fffffffu}, + {0x725f6ff6u, 0x00000000u, 0x725f6ff6u, 0x00000000u, 0x725f6ff6u, 0x00000000u, 0x725f6ff6u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x775f6ff7u, 0x00ff00ffu, 0x775f6ff7u, 0x00ff00ffu, 0x45040843u, 0x00ff00ffu, 0x45040843u, 0x00ff00ffu, 0x775f6ff7u, 0x00ff00ffu, 0x45040843u, 0x00ff00ffu, 0x725f6ff6u, 0x00000000u, 0x725f6ff6u, 0x00000000u}, + {0x50d14398u, 0x7f767333u, 0x50d14398u, 0x7f767333u, 0x50d14398u, 0x7f767333u, 0x50d14398u, 0x7f767333u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x73f9ebdcu, 0x7f767333u, 0x73f9ebdcu, 0x7f767333u, 0x2369eadcu, 0x00000001u, 0x2369eadcu, 0x00000001u, 0x73f9ebdcu, 0x7f767333u, 0x2369eadcu, 0x00000001u, 0x50d14398u, 0x7f767333u, 0x50d14398u, 0x7f767333u} +}; +constexpr std::uint32_t Expected64[64][28] = { + {0x6a6ae768u, 0x89abcdefu, 0x6a6ae768u, 0x89abcdefu, 0x6a6ae768u, 0x89abcdefu, 0x6a6ae768u, 0x89abcdefu, 0x6a6ae768u, 0x89abcdefu, 0x6a6ae768u, 0x89abcdefu, 0x6e6fe7eeu, 0x89ffcdffu, 0x6e6fe7eeu, 0x89ffcdffu, 0x466de486u, 0x00ff00ffu, 0x466de486u, 0x00ff00ffu, 0x6e6fe7eeu, 0x89ffcdffu, 0x466de486u, 0x00ff00ffu, 0x6e6fe7eeu, 0x89ffcdffu, 0x466de486u, 0x00ff00ffu}, + {0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x80000000u, 0x2e09e4b8u, 0x80000000u, 0x2e09e4b8u, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x80ff00ffu, 0x2effe4ffu, 0x80ff00ffu, 0x2effe4ffu, 0x80000000u, 0x2e09e4b8u, 0x80000000u, 0x2e09e4b8u, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x80ff00ffu, 0x2effe4ffu, 0x80000000u, 0x2e09e4b8u}, + {0x2be38eccu, 0xdd1775a9u, 0x2be38eccu, 0xdd1775a9u, 0x2be38eccu, 0xdd1775a9u, 0x2be38eccu, 0xdd1775a9u, 0x2be38eccu, 0xdd1775a9u, 0x2be38eccu, 0xdd1775a9u, 0xabfbdfcfu, 0xfd9f75abu, 0xabfbdfcfu, 0xfd9f75abu, 0xab195b47u, 0xe88f40a2u, 0xab195b47u, 0xe88f40a2u, 0xabfbdfcfu, 0xfd9f75abu, 0xab195b47u, 0xe88f40a2u, 0xabfbdfcfu, 0xfd9f75abu, 0xab195b47u, 0xe88f40a2u}, + {0xe9a462ceu, 0x00000000u, 0xe9a462ceu, 0x00000000u, 0xe9a462ceu, 0x00000000u, 0xe9a462ceu, 0x00000000u, 0xe9a462ceu, 0x00000000u, 0xe9a462ceu, 0x00000000u, 0xebb563deu, 0xac0e36d5u, 0xebb563deu, 0xac0e36d5u, 0x2b1141d6u, 0xac0e36d5u, 0x2b1141d6u, 0xac0e36d5u, 0xebb563deu, 0xac0e36d5u, 0x2b1141d6u, 0xac0e36d5u, 0xebb563deu, 0xac0e36d5u, 0x2b1141d6u, 0xac0e36d5u}, + {0x64ffc604u, 0xbcfdc56au, 0x64ffc604u, 0xbcfdc56au, 0x64ffc604u, 0xbcfdc56au, 0x64ffc604u, 0xbcfdc56au, 0x64ffc604u, 0xbcfdc56au, 0x64ffc604u, 0xbcfdc56au, 0x6effc63cu, 0xbffdefebu, 0x6effc63cu, 0xbffdefebu, 0x2af2003cu, 0x8f4c6fc3u, 0x2af2003cu, 0x8f4c6fc3u, 0x6effc63cu, 0xbffdefebu, 0x2af2003cu, 0x8f4c6fc3u, 0x6effc63cu, 0xbffdefebu, 0x2af2003cu, 0x8f4c6fc3u}, + {0x70ce7993u, 0x00ff00ffu, 0x70ce7993u, 0x00ff00ffu, 0x70ce7993u, 0x00ff00ffu, 0x70ce7993u, 0x00ff00ffu, 0x70ce7993u, 0x00ff00ffu, 0x70ce7993u, 0x00ff00ffu, 0xf0ce7993u, 0x7bffbcffu, 0xf0ce7993u, 0x7bffbcffu, 0x80000000u, 0x7b05bc1au, 0x80000000u, 0x7b05bc1au, 0xf0ce7993u, 0x7bffbcffu, 0x80000000u, 0x7b05bc1au, 0xf0ce7993u, 0x7bffbcffu, 0x80000000u, 0x7b05bc1au}, + {0x00000001u, 0xba30a818u, 0x00000001u, 0xba30a818u, 0x00000001u, 0xba30a818u, 0x00000001u, 0xba30a818u, 0x00000001u, 0xba30a818u, 0x00000001u, 0xba30a818u, 0x6e325d77u, 0xff77ba9du, 0x6e325d77u, 0xff77ba9du, 0x6e325d76u, 0x7767929du, 0x6e325d76u, 0x7767929du, 0x6e325d77u, 0xff77ba9du, 0x6e325d76u, 0x7767929du, 0x6e325d77u, 0xff77ba9du, 0x6e325d76u, 0x7767929du}, + {0x7e974317u, 0x00ff00ffu, 0x7e974317u, 0x00ff00ffu, 0x7e974317u, 0x00ff00ffu, 0x7e974317u, 0x00ff00ffu, 0x7e974317u, 0x00ff00ffu, 0x7e974317u, 0x00ff00ffu, 0xffd7d757u, 0xdfffa0ffu, 0xffd7d757u, 0xdfffa0ffu, 0xddd59451u, 0xdf30a0eau, 0xddd59451u, 0xdf30a0eau, 0xffd7d757u, 0xdfffa0ffu, 0xddd59451u, 0xdf30a0eau, 0xffd7d757u, 0xdfffa0ffu, 0xddd59451u, 0xdf30a0eau}, + {0xffffffffu, 0x159840eau, 0xffffffffu, 0x159840eau, 0xe41c70dbu, 0x88d1f52cu, 0xe41c70dbu, 0x88d1f52cu, 0xffffffffu, 0x159840eau, 0xffffffffu, 0x159840eau, 0xffffffffu, 0x9dd9f5eeu, 0xffffffffu, 0x9dd9f5eeu, 0xe41c70dbu, 0x88d1f52cu, 0xe41c70dbu, 0x88d1f52cu, 0xffffffffu, 0x159840eau, 0xffffffffu, 0x159840eau, 0xffffffffu, 0x9dd9f5eeu, 0xe41c70dbu, 0x88d1f52cu}, + {0x89abcdefu, 0x00000000u, 0x89abcdefu, 0x00000000u, 0x89abcdefu, 0x00000000u, 0x89abcdefu, 0x00000000u, 0x89abcdefu, 0x00000000u, 0x89abcdefu, 0x00000000u, 0xedfbdfffu, 0x93cd7e6fu, 0xedfbdfffu, 0x93cd7e6fu, 0x6dd9de7bu, 0x93cd7e6fu, 0x6dd9de7bu, 0x93cd7e6fu, 0xedfbdfffu, 0x93cd7e6fu, 0x6dd9de7bu, 0x93cd7e6fu, 0xedfbdfffu, 0x93cd7e6fu, 0x6dd9de7bu, 0x93cd7e6fu}, + {0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x7f976faeu, 0x7fffffffu, 0x7f976faeu, 0x379cf45fu, 0x5a176daau, 0x379cf45fu, 0x5a176daau, 0x7fffffffu, 0x7f976faeu, 0x379cf45fu, 0x5a176daau, 0x7fffffffu, 0x7f976faeu, 0x379cf45fu, 0x5a176daau}, + {0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xdd343334u, 0x89abcdefu, 0xdd343334u, 0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xfd3eb3bdu, 0x89abcdefu, 0xfd3eb3bdu, 0x89abcdefu, 0xdd343334u, 0x89abcdefu, 0xdd343334u, 0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xfd3eb3bdu, 0x89abcdefu, 0xdd343334u}, + {0xf4c65508u, 0x774afa8fu, 0xf4c65508u, 0x774afa8fu, 0x4bf01f77u, 0x80000000u, 0x4bf01f77u, 0x80000000u, 0xf4c65508u, 0x774afa8fu, 0xf4c65508u, 0x774afa8fu, 0xfff65f7fu, 0xf74afa8fu, 0xfff65f7fu, 0xf74afa8fu, 0x4bf01f77u, 0x80000000u, 0x4bf01f77u, 0x80000000u, 0xf4c65508u, 0x774afa8fu, 0xf4c65508u, 0x774afa8fu, 0xfff65f7fu, 0xf74afa8fu, 0x4bf01f77u, 0x80000000u}, + {0x0680d87fu, 0xe2aeba83u, 0x0680d87fu, 0xe2aeba83u, 0x0cae8376u, 0x21027ce8u, 0x0cae8376u, 0x21027ce8u, 0x0680d87fu, 0xe2aeba83u, 0x0680d87fu, 0xe2aeba83u, 0x0eaedb7fu, 0xe3aefeebu, 0x0eaedb7fu, 0xe3aefeebu, 0x0cae8376u, 0x21027ce8u, 0x0cae8376u, 0x21027ce8u, 0x0680d87fu, 0xe2aeba83u, 0x0680d87fu, 0xe2aeba83u, 0x0eaedb7fu, 0xe3aefeebu, 0x0cae8376u, 0x21027ce8u}, + {0x12345678u, 0x24483dacu, 0x12345678u, 0x24483dacu, 0x12345678u, 0x24483dacu, 0x12345678u, 0x24483dacu, 0x12345678u, 0x24483dacu, 0x12345678u, 0x24483dacu, 0x3634dff9u, 0xfe7abdadu, 0x3634dff9u, 0xfe7abdadu, 0x343089c1u, 0xfe3a9921u, 0x343089c1u, 0xfe3a9921u, 0x3634dff9u, 0xfe7abdadu, 0x343089c1u, 0xfe3a9921u, 0x3634dff9u, 0xfe7abdadu, 0x343089c1u, 0xfe3a9921u}, + {0x9dd441ebu, 0x7fffffffu, 0x9dd441ebu, 0x7fffffffu, 0x9dd441ebu, 0x7fffffffu, 0x9dd441ebu, 0x7fffffffu, 0x9dd441ebu, 0x7fffffffu, 0x9dd441ebu, 0x7fffffffu, 0xbff54febu, 0x7fffffffu, 0xbff54febu, 0x7fffffffu, 0x3f250fabu, 0x5a25508fu, 0x3f250fabu, 0x5a25508fu, 0xbff54febu, 0x7fffffffu, 0x3f250fabu, 0x5a25508fu, 0xbff54febu, 0x7fffffffu, 0x3f250fabu, 0x5a25508fu}, + {0x80000000u, 0x940c48d1u, 0x80000000u, 0x940c48d1u, 0x00000001u, 0x12345678u, 0x00000001u, 0x12345678u, 0x80000000u, 0x940c48d1u, 0x80000000u, 0x940c48d1u, 0x80000001u, 0x963c5ef9u, 0x80000001u, 0x963c5ef9u, 0x00000001u, 0x12345678u, 0x00000001u, 0x12345678u, 0x80000000u, 0x940c48d1u, 0x80000000u, 0x940c48d1u, 0x80000001u, 0x963c5ef9u, 0x00000001u, 0x12345678u}, + {0x06a06e39u, 0xdca4271cu, 0x06a06e39u, 0xdca4271cu, 0x89abcdefu, 0x80000000u, 0x89abcdefu, 0x80000000u, 0x06a06e39u, 0xdca4271cu, 0x06a06e39u, 0xdca4271cu, 0x8fabefffu, 0xdca4271cu, 0x8fabefffu, 0xdca4271cu, 0x89abcdefu, 0x80000000u, 0x89abcdefu, 0x80000000u, 0x06a06e39u, 0xdca4271cu, 0x06a06e39u, 0xdca4271cu, 0x8fabefffu, 0xdca4271cu, 0x89abcdefu, 0x80000000u}, + {0x00ff00ffu, 0xdadef6e0u, 0x00ff00ffu, 0xdadef6e0u, 0x00ff00ffu, 0xdadef6e0u, 0x00ff00ffu, 0xdadef6e0u, 0x00ff00ffu, 0xdadef6e0u, 0x00ff00ffu, 0xdadef6e0u, 0xdfffb7ffu, 0xffffffffu, 0xdfffb7ffu, 0xffffffffu, 0xdfd0b7a2u, 0xffffffffu, 0xdfd0b7a2u, 0xffffffffu, 0xdfffb7ffu, 0xffffffffu, 0xdfd0b7a2u, 0xffffffffu, 0xdfffb7ffu, 0xffffffffu, 0xdfd0b7a2u, 0xffffffffu}, + {0x436d4bd7u, 0xb4373875u, 0x436d4bd7u, 0xb4373875u, 0xc71d5b33u, 0x63b433c8u, 0xc71d5b33u, 0x63b433c8u, 0x436d4bd7u, 0xb4373875u, 0x436d4bd7u, 0xb4373875u, 0xc77d5bf7u, 0xf7b73bfdu, 0xc77d5bf7u, 0xf7b73bfdu, 0xc71d5b33u, 0x63b433c8u, 0xc71d5b33u, 0x63b433c8u, 0x436d4bd7u, 0xb4373875u, 0x436d4bd7u, 0xb4373875u, 0xc77d5bf7u, 0xf7b73bfdu, 0xc71d5b33u, 0x63b433c8u}, + {0xe1a9611du, 0xdd70b68bu, 0xe1a9611du, 0xdd70b68bu, 0xc3e41dfbu, 0x80000000u, 0xc3e41dfbu, 0x80000000u, 0xe1a9611du, 0xdd70b68bu, 0xe1a9611du, 0xdd70b68bu, 0xe3ed7dffu, 0xdd70b68bu, 0xe3ed7dffu, 0xdd70b68bu, 0xc3e41dfbu, 0x80000000u, 0xc3e41dfbu, 0x80000000u, 0xe1a9611du, 0xdd70b68bu, 0xe1a9611du, 0xdd70b68bu, 0xe3ed7dffu, 0xdd70b68bu, 0xc3e41dfbu, 0x80000000u}, + {0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x2aff5fffu, 0xf2ffccffu, 0x2aff5fffu, 0xf2ffccffu, 0x2a2b5f67u, 0xf2d9cc0du, 0x2a2b5f67u, 0xf2d9cc0du, 0x2aff5fffu, 0xf2ffccffu, 0x2a2b5f67u, 0xf2d9cc0du, 0x2aff5fffu, 0xf2ffccffu, 0x2a2b5f67u, 0xf2d9cc0du}, + {0x89abcdefu, 0x38573260u, 0x89abcdefu, 0x38573260u, 0x89abcdefu, 0x38573260u, 0x89abcdefu, 0x38573260u, 0x89abcdefu, 0x38573260u, 0x89abcdefu, 0x38573260u, 0xdbbbcfffu, 0x79f77772u, 0xdbbbcfffu, 0x79f77772u, 0x5b120ed1u, 0x49e07752u, 0x5b120ed1u, 0x49e07752u, 0xdbbbcfffu, 0x79f77772u, 0x5b120ed1u, 0x49e07752u, 0xdbbbcfffu, 0x79f77772u, 0x5b120ed1u, 0x49e07752u}, + {0xf60106b7u, 0xd61d1405u, 0xf60106b7u, 0xd61d1405u, 0xf60106b7u, 0xd61d1405u, 0xf60106b7u, 0xd61d1405u, 0xf60106b7u, 0xd61d1405u, 0xf60106b7u, 0xd61d1405u, 0xf78dceffu, 0xfeff3707u, 0xf78dceffu, 0xfeff3707u, 0xc58ccccdu, 0xbce22306u, 0xc58ccccdu, 0xbce22306u, 0xf78dceffu, 0xfeff3707u, 0xc58ccccdu, 0xbce22306u, 0xf78dceffu, 0xfeff3707u, 0xc58ccccdu, 0xbce22306u}, + {0x7fffffffu, 0x80000000u, 0x7fffffffu, 0x80000000u, 0x15452b7bu, 0xb8054218u, 0x15452b7bu, 0xb8054218u, 0x7fffffffu, 0x80000000u, 0x7fffffffu, 0x80000000u, 0x7fffffffu, 0xb8054218u, 0x7fffffffu, 0xb8054218u, 0x15452b7bu, 0xb8054218u, 0x15452b7bu, 0xb8054218u, 0x7fffffffu, 0x80000000u, 0x7fffffffu, 0x80000000u, 0x7fffffffu, 0xb8054218u, 0x15452b7bu, 0xb8054218u}, + {0x12345678u, 0x00000001u, 0x12345678u, 0x00000001u, 0x12345678u, 0x00000001u, 0x12345678u, 0x00000001u, 0x12345678u, 0x00000001u, 0x12345678u, 0x00000001u, 0xb73c7e7cu, 0xffffffffu, 0xb73c7e7cu, 0xffffffffu, 0xa7387a4cu, 0xffffffffu, 0xa7387a4cu, 0xffffffffu, 0xb73c7e7cu, 0xffffffffu, 0xa7387a4cu, 0xffffffffu, 0xb73c7e7cu, 0xffffffffu, 0xa7387a4cu, 0xffffffffu}, + {0x00000000u, 0x90322d5bu, 0x00000000u, 0x90322d5bu, 0x00000000u, 0x90322d5bu, 0x00000000u, 0x90322d5bu, 0x00000000u, 0x90322d5bu, 0x00000000u, 0x90322d5bu, 0x7ee058dau, 0x99bbedffu, 0x7ee058dau, 0x99bbedffu, 0x7ee058dau, 0x89abcdefu, 0x7ee058dau, 0x89abcdefu, 0x7ee058dau, 0x99bbedffu, 0x7ee058dau, 0x89abcdefu, 0x7ee058dau, 0x99bbedffu, 0x7ee058dau, 0x89abcdefu}, + {0x7fffffffu, 0x93b8f2e0u, 0x7fffffffu, 0x93b8f2e0u, 0x7fffffffu, 0x93b8f2e0u, 0x7fffffffu, 0x93b8f2e0u, 0x7fffffffu, 0x93b8f2e0u, 0x7fffffffu, 0x93b8f2e0u, 0xffffffffu, 0x97bbf7f5u, 0xffffffffu, 0x97bbf7f5u, 0x8245e09du, 0x168b95f5u, 0x8245e09du, 0x168b95f5u, 0xffffffffu, 0x97bbf7f5u, 0x8245e09du, 0x168b95f5u, 0xffffffffu, 0x97bbf7f5u, 0x8245e09du, 0x168b95f5u}, + {0x80000000u, 0x4ec04618u, 0x80000000u, 0x4ec04618u, 0x80000000u, 0x4ec04618u, 0x80000000u, 0x4ec04618u, 0x80000000u, 0x4ec04618u, 0x80000000u, 0x4ec04618u, 0xc4b6a55du, 0xcfebcfffu, 0xc4b6a55du, 0xcfebcfffu, 0x44b6a55du, 0x89abcdefu, 0x44b6a55du, 0x89abcdefu, 0xc4b6a55du, 0xcfebcfffu, 0x44b6a55du, 0x89abcdefu, 0xc4b6a55du, 0xcfebcfffu, 0x44b6a55du, 0x89abcdefu}, + {0x80a3acc4u, 0x7fffffffu, 0x80a3acc4u, 0x7fffffffu, 0x80a3acc4u, 0x7fffffffu, 0x80a3acc4u, 0x7fffffffu, 0x80a3acc4u, 0x7fffffffu, 0x80a3acc4u, 0x7fffffffu, 0x9fa3efedu, 0xffffffffu, 0x9fa3efedu, 0xffffffffu, 0x1f016769u, 0xac7c83b9u, 0x1f016769u, 0xac7c83b9u, 0x9fa3efedu, 0xffffffffu, 0x1f016769u, 0xac7c83b9u, 0x9fa3efedu, 0xffffffffu, 0x1f016769u, 0xac7c83b9u}, + {0x725f6ff6u, 0x00000000u, 0x725f6ff6u, 0x00000000u, 0x725f6ff6u, 0x00000000u, 0x725f6ff6u, 0x00000000u, 0x725f6ff6u, 0x00000000u, 0x725f6ff6u, 0x00000000u, 0x775f6ff7u, 0x00ff00ffu, 0x775f6ff7u, 0x00ff00ffu, 0x45040843u, 0x00ff00ffu, 0x45040843u, 0x00ff00ffu, 0x775f6ff7u, 0x00ff00ffu, 0x45040843u, 0x00ff00ffu, 0x775f6ff7u, 0x00ff00ffu, 0x45040843u, 0x00ff00ffu}, + {0x50d14398u, 0x7f767333u, 0x50d14398u, 0x7f767333u, 0x50d14398u, 0x7f767333u, 0x50d14398u, 0x7f767333u, 0x50d14398u, 0x7f767333u, 0x50d14398u, 0x7f767333u, 0x73f9ebdcu, 0x7f767333u, 0x73f9ebdcu, 0x7f767333u, 0x2369eadcu, 0x00000001u, 0x2369eadcu, 0x00000001u, 0x73f9ebdcu, 0x7f767333u, 0x2369eadcu, 0x00000001u, 0x73f9ebdcu, 0x7f767333u, 0x2369eadcu, 0x00000001u}, + {0xac8c599bu, 0x7fffffffu, 0xac8c599bu, 0x7fffffffu, 0xac8c599bu, 0x7fffffffu, 0xac8c599bu, 0x7fffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfcff79dfu, 0x7fffffffu, 0xfcff79dfu, 0x7fffffffu, 0xd8f36844u, 0x4761f31au, 0xd8f36844u, 0x4761f31au, 0xfcff79dfu, 0x7fffffffu, 0xd8f36844u, 0x4761f31au, 0xac8c599bu, 0x7fffffffu, 0xac8c599bu, 0x7fffffffu}, + {0xd078019fu, 0x60ac3d38u, 0xd078019fu, 0x60ac3d38u, 0x7fffffffu, 0x20b5d1d4u, 0x7fffffffu, 0x20b5d1d4u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x60bdfdfcu, 0xffffffffu, 0x60bdfdfcu, 0x7fffffffu, 0x20b5d1d4u, 0x7fffffffu, 0x20b5d1d4u, 0xd078019fu, 0x60ac3d38u, 0xd078019fu, 0x60ac3d38u, 0xd078019fu, 0x60ac3d38u, 0xd078019fu, 0x60ac3d38u}, + {0x2925feb3u, 0x00000001u, 0x2925feb3u, 0x00000001u, 0x7fffffffu, 0xb16614f4u, 0x7fffffffu, 0xb16614f4u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fffffffu, 0xb16614f5u, 0x7fffffffu, 0xb16614f5u, 0x7fffffffu, 0xb16614f4u, 0x7fffffffu, 0xb16614f4u, 0x2925feb3u, 0x00000001u, 0x2925feb3u, 0x00000001u, 0x2925feb3u, 0x00000001u, 0x2925feb3u, 0x00000001u}, + {0x86d5360fu, 0xfaf40417u, 0x86d5360fu, 0xfaf40417u, 0x86d5360fu, 0xfaf40417u, 0x86d5360fu, 0xfaf40417u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xd6d77e5fu, 0xfbf71737u, 0xd6d77e5fu, 0xfbf71737u, 0xd4424852u, 0x89631325u, 0xd4424852u, 0x89631325u, 0xd6d77e5fu, 0xfbf71737u, 0xd4424852u, 0x89631325u, 0x86d5360fu, 0xfaf40417u, 0x86d5360fu, 0xfaf40417u}, + {0xffffffffu, 0xde274210u, 0xffffffffu, 0xde274210u, 0xe96e12c9u, 0x65305d1cu, 0xe96e12c9u, 0x65305d1cu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xff375f1cu, 0xffffffffu, 0xff375f1cu, 0xe96e12c9u, 0x65305d1cu, 0xe96e12c9u, 0x65305d1cu, 0xffffffffu, 0xde274210u, 0xffffffffu, 0xde274210u, 0xffffffffu, 0xde274210u, 0xffffffffu, 0xde274210u}, + {0x1043c4f3u, 0x8a860eeau, 0x1043c4f3u, 0x8a860eeau, 0xe9579523u, 0x7563acb0u, 0xe9579523u, 0x7563acb0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xf957d5f3u, 0xffe7aefau, 0xf957d5f3u, 0xffe7aefau, 0xe9579523u, 0x7563acb0u, 0xe9579523u, 0x7563acb0u, 0x1043c4f3u, 0x8a860eeau, 0x1043c4f3u, 0x8a860eeau, 0x1043c4f3u, 0x8a860eeau, 0x1043c4f3u, 0x8a860eeau}, + {0x310b55c7u, 0x12345678u, 0x310b55c7u, 0x12345678u, 0x310b55c7u, 0x12345678u, 0x310b55c7u, 0x12345678u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x333f57ffu, 0x963cd77fu, 0x333f57ffu, 0x963cd77fu, 0x12345678u, 0x96188707u, 0x12345678u, 0x96188707u, 0x333f57ffu, 0x963cd77fu, 0x12345678u, 0x96188707u, 0x310b55c7u, 0x12345678u, 0x310b55c7u, 0x12345678u}, + {0x80000000u, 0xbcabb1bau, 0x80000000u, 0xbcabb1bau, 0x80000000u, 0xbcabb1bau, 0x80000000u, 0xbcabb1bau, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x92345678u, 0xffffffffu, 0x92345678u, 0xffffffffu, 0x12345678u, 0xffffffffu, 0x12345678u, 0xffffffffu, 0x92345678u, 0xffffffffu, 0x12345678u, 0xffffffffu, 0x80000000u, 0xbcabb1bau, 0x80000000u, 0xbcabb1bau}, + {0x72fdad03u, 0x00000001u, 0x72fdad03u, 0x00000001u, 0x72fdad03u, 0x00000001u, 0x72fdad03u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x72ffbf03u, 0x6c60da43u, 0x72ffbf03u, 0x6c60da43u, 0x028f3b02u, 0x6c60da42u, 0x028f3b02u, 0x6c60da42u, 0x72ffbf03u, 0x6c60da43u, 0x028f3b02u, 0x6c60da42u, 0x72fdad03u, 0x00000001u, 0x72fdad03u, 0x00000001u}, + {0xe473cdc9u, 0x75eb16c7u, 0xe473cdc9u, 0x75eb16c7u, 0x12345678u, 0xe0d59f3cu, 0x12345678u, 0xe0d59f3cu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xf677dff9u, 0xf5ff9fffu, 0xf677dff9u, 0xf5ff9fffu, 0x12345678u, 0xe0d59f3cu, 0x12345678u, 0xe0d59f3cu, 0xe473cdc9u, 0x75eb16c7u, 0xe473cdc9u, 0x75eb16c7u, 0xe473cdc9u, 0x75eb16c7u, 0xe473cdc9u, 0x75eb16c7u}, + {0x89abcdefu, 0x74102553u, 0x89abcdefu, 0x74102553u, 0x89abcdefu, 0x74102553u, 0x89abcdefu, 0x74102553u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x9dbbedffu, 0xf536affbu, 0x9dbbedffu, 0xf536affbu, 0x1cb3e931u, 0xe536aae9u, 0x1cb3e931u, 0xe536aae9u, 0x9dbbedffu, 0xf536affbu, 0x1cb3e931u, 0xe536aae9u, 0x89abcdefu, 0x74102553u, 0x89abcdefu, 0x74102553u}, + {0x4d126f38u, 0x7d42a12au, 0x4d126f38u, 0x7d42a12au, 0x4d126f38u, 0x7d42a12au, 0x4d126f38u, 0x7d42a12au, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x5d7b6ffdu, 0xffc3b3ffu, 0x5d7b6ffdu, 0xffc3b3ffu, 0x50692ce5u, 0xa68132ddu, 0x50692ce5u, 0xa68132ddu, 0x5d7b6ffdu, 0xffc3b3ffu, 0x50692ce5u, 0xa68132ddu, 0x4d126f38u, 0x7d42a12au, 0x4d126f38u, 0x7d42a12au}, + {0x744e3f0eu, 0x7fffffffu, 0x744e3f0eu, 0x7fffffffu, 0xde298785u, 0x80000000u, 0xde298785u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfe6fbf8fu, 0xffffffffu, 0xfe6fbf8fu, 0xffffffffu, 0xde298785u, 0x80000000u, 0xde298785u, 0x80000000u, 0x744e3f0eu, 0x7fffffffu, 0x744e3f0eu, 0x7fffffffu, 0x744e3f0eu, 0x7fffffffu, 0x744e3f0eu, 0x7fffffffu}, + {0xad554ba1u, 0x7c5153d0u, 0xad554ba1u, 0x7c5153d0u, 0xad554ba1u, 0x7c5153d0u, 0xad554ba1u, 0x7c5153d0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xeffdefefu, 0xfcfbfbffu, 0xeffdefefu, 0xfcfbfbffu, 0xeaf8edcfu, 0xc8aab92fu, 0xeaf8edcfu, 0xc8aab92fu, 0xeffdefefu, 0xfcfbfbffu, 0xeaf8edcfu, 0xc8aab92fu, 0xad554ba1u, 0x7c5153d0u, 0xad554ba1u, 0x7c5153d0u}, + {0xf8f008c5u, 0x0f119987u, 0xf8f008c5u, 0x0f119987u, 0xf8f008c5u, 0x0f119987u, 0xf8f008c5u, 0x0f119987u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfbf39cfdu, 0x5fd5bf97u, 0xfbf39cfdu, 0x5fd5bf97u, 0x1bd39c7cu, 0x5dc52692u, 0x1bd39c7cu, 0x5dc52692u, 0xfbf39cfdu, 0x5fd5bf97u, 0x1bd39c7cu, 0x5dc52692u, 0xf8f008c5u, 0x0f119987u, 0xf8f008c5u, 0x0f119987u}, + {0xd03b304du, 0x00ff00ffu, 0xd03b304du, 0x00ff00ffu, 0x89abcdefu, 0x991132c0u, 0x89abcdefu, 0x991132c0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xd9bbfdefu, 0x99ff32ffu, 0xd9bbfdefu, 0x99ff32ffu, 0x89abcdefu, 0x991132c0u, 0x89abcdefu, 0x991132c0u, 0xd03b304du, 0x00ff00ffu, 0xd03b304du, 0x00ff00ffu, 0xd03b304du, 0x00ff00ffu, 0xd03b304du, 0x00ff00ffu}, + {0xa92c8f72u, 0x07b9077du, 0xa92c8f72u, 0x07b9077du, 0xa92c8f72u, 0x07b9077du, 0xa92c8f72u, 0x07b9077du, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xef7ceff6u, 0xf7bdf7fdu, 0xef7ceff6u, 0xf7bdf7fdu, 0x66546794u, 0xf6a5f7c1u, 0x66546794u, 0xf6a5f7c1u, 0xef7ceff6u, 0xf7bdf7fdu, 0x66546794u, 0xf6a5f7c1u, 0xa92c8f72u, 0x07b9077du, 0xa92c8f72u, 0x07b9077du}, + {0xb5691c18u, 0x5d6c710au, 0xb5691c18u, 0x5d6c710au, 0x7ae6b997u, 0xbfe4abb0u, 0x7ae6b997u, 0xbfe4abb0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffefbd9fu, 0xffecfbbau, 0xffefbd9fu, 0xffecfbbau, 0x7ae6b997u, 0xbfe4abb0u, 0x7ae6b997u, 0xbfe4abb0u, 0xb5691c18u, 0x5d6c710au, 0xb5691c18u, 0x5d6c710au, 0xb5691c18u, 0x5d6c710au, 0xb5691c18u, 0x5d6c710au}, + {0xc9570a1cu, 0xefa96b6eu, 0xc9570a1cu, 0xefa96b6eu, 0xc9570a1cu, 0xefa96b6eu, 0xc9570a1cu, 0xefa96b6eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xc9570a1cu, 0xefbbffffu, 0xc9570a1cu, 0xefbbffffu, 0x00000000u, 0xcd12bfb3u, 0x00000000u, 0xcd12bfb3u, 0xc9570a1cu, 0xefbbffffu, 0x00000000u, 0xcd12bfb3u, 0xc9570a1cu, 0xefa96b6eu, 0xc9570a1cu, 0xefa96b6eu}, + {0x00000000u, 0x37eadcd2u, 0x00000000u, 0x37eadcd2u, 0x00000000u, 0x37eadcd2u, 0x00000000u, 0x37eadcd2u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3a6d60aau, 0xbfebddffu, 0x3a6d60aau, 0xbfebddffu, 0x3a6d60aau, 0x89abcdefu, 0x3a6d60aau, 0x89abcdefu, 0x3a6d60aau, 0xbfebddffu, 0x3a6d60aau, 0x89abcdefu, 0x00000000u, 0x37eadcd2u, 0x00000000u, 0x37eadcd2u}, + {0x114a2d32u, 0x00000000u, 0x114a2d32u, 0x00000000u, 0x89abcdefu, 0x12345678u, 0x89abcdefu, 0x12345678u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x99ebedffu, 0x12345678u, 0x99ebedffu, 0x12345678u, 0x89abcdefu, 0x12345678u, 0x89abcdefu, 0x12345678u, 0x114a2d32u, 0x00000000u, 0x114a2d32u, 0x00000000u, 0x114a2d32u, 0x00000000u, 0x114a2d32u, 0x00000000u}, + {0x14090b01u, 0xffffffffu, 0x14090b01u, 0xffffffffu, 0x14090b01u, 0xffffffffu, 0x14090b01u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x1eb98bedu, 0xffffffffu, 0x1eb98bedu, 0xffffffffu, 0x0eb083edu, 0x1a0caeefu, 0x0eb083edu, 0x1a0caeefu, 0x1eb98bedu, 0xffffffffu, 0x0eb083edu, 0x1a0caeefu, 0x14090b01u, 0xffffffffu, 0x14090b01u, 0xffffffffu}, + {0x12345678u, 0x4521d7efu, 0x12345678u, 0x4521d7efu, 0x00000001u, 0x80000000u, 0x00000001u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x12345679u, 0xc521d7efu, 0x12345679u, 0xc521d7efu, 0x00000001u, 0x80000000u, 0x00000001u, 0x80000000u, 0x12345678u, 0x4521d7efu, 0x12345678u, 0x4521d7efu, 0x12345678u, 0x4521d7efu, 0x12345678u, 0x4521d7efu}, + {0xefa94a8eu, 0x561bfb20u, 0xefa94a8eu, 0x561bfb20u, 0xefa94a8eu, 0x561bfb20u, 0xefa94a8eu, 0x561bfb20u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffb9cafeu, 0x7fbbfb3du, 0xffb9cafeu, 0x7fbbfb3du, 0x19b0c2f0u, 0x69b1111du, 0x19b0c2f0u, 0x69b1111du, 0xffb9cafeu, 0x7fbbfb3du, 0x19b0c2f0u, 0x69b1111du, 0xefa94a8eu, 0x561bfb20u, 0xefa94a8eu, 0x561bfb20u}, + {0xbcf7629fu, 0xaf1de605u, 0xbcf7629fu, 0xaf1de605u, 0x41622456u, 0x00000000u, 0x41622456u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfdf766dfu, 0xaf1de605u, 0xfdf766dfu, 0xaf1de605u, 0x41622456u, 0x00000000u, 0x41622456u, 0x00000000u, 0xbcf7629fu, 0xaf1de605u, 0xbcf7629fu, 0xaf1de605u, 0xbcf7629fu, 0xaf1de605u, 0xbcf7629fu, 0xaf1de605u}, + {0xd47a46a9u, 0x12da9645u, 0xd47a46a9u, 0x12da9645u, 0x7ded0e8du, 0x00000000u, 0x7ded0e8du, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfdff4eadu, 0x12da9645u, 0xfdff4eadu, 0x12da9645u, 0x7ded0e8du, 0x00000000u, 0x7ded0e8du, 0x00000000u, 0xd47a46a9u, 0x12da9645u, 0xd47a46a9u, 0x12da9645u, 0xd47a46a9u, 0x12da9645u, 0xd47a46a9u, 0x12da9645u}, + {0x697d50dbu, 0x89abcdefu, 0x697d50dbu, 0x89abcdefu, 0xdeacacaau, 0xcf93dd30u, 0xdeacacaau, 0xcf93dd30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffdfcfbu, 0xcfbbddffu, 0xfffdfcfbu, 0xcfbbddffu, 0xdeacacaau, 0xcf93dd30u, 0xdeacacaau, 0xcf93dd30u, 0x697d50dbu, 0x89abcdefu, 0x697d50dbu, 0x89abcdefu, 0x697d50dbu, 0x89abcdefu, 0x697d50dbu, 0x89abcdefu}, + {0x00ff00ffu, 0xb6a48819u, 0x00ff00ffu, 0xb6a48819u, 0x1f752ee6u, 0x80000000u, 0x1f752ee6u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x1fff2effu, 0xb6a48819u, 0x1fff2effu, 0xb6a48819u, 0x1f752ee6u, 0x80000000u, 0x1f752ee6u, 0x80000000u, 0x00ff00ffu, 0xb6a48819u, 0x00ff00ffu, 0xb6a48819u, 0x00ff00ffu, 0xb6a48819u, 0x00ff00ffu, 0xb6a48819u}, + {0x12345678u, 0xbb36bb87u, 0x12345678u, 0xbb36bb87u, 0x12345678u, 0xbb36bb87u, 0x12345678u, 0xbb36bb87u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x1abc7e7bu, 0xffb6ffd7u, 0x1abc7e7bu, 0xffb6ffd7u, 0x0888385bu, 0x5cb6c6d7u, 0x0888385bu, 0x5cb6c6d7u, 0x1abc7e7bu, 0xffb6ffd7u, 0x0888385bu, 0x5cb6c6d7u, 0x12345678u, 0xbb36bb87u, 0x12345678u, 0xbb36bb87u}, + {0xd24df64du, 0x9595f0c7u, 0xd24df64du, 0x9595f0c7u, 0xd24df64du, 0x9595f0c7u, 0xd24df64du, 0x9595f0c7u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xd24df64du, 0xffffffffu, 0xd24df64du, 0xffffffffu, 0x00000000u, 0x7fffffffu, 0x00000000u, 0x7fffffffu, 0xd24df64du, 0xffffffffu, 0x00000000u, 0x7fffffffu, 0xd24df64du, 0x9595f0c7u, 0xd24df64du, 0x9595f0c7u}, + {0xffffffffu, 0x00ff00ffu, 0xffffffffu, 0x00ff00ffu, 0xffffffffu, 0x00ff00ffu, 0xffffffffu, 0x00ff00ffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xfefffeffu, 0xffffffffu, 0xfefffeffu, 0x00000001u, 0xfe7ffe21u, 0x00000001u, 0xfe7ffe21u, 0xffffffffu, 0xfefffeffu, 0x00000001u, 0xfe7ffe21u, 0xffffffffu, 0x00ff00ffu, 0xffffffffu, 0x00ff00ffu}, + {0xdd6537d2u, 0x6c2ab542u, 0xdd6537d2u, 0x6c2ab542u, 0xdd6537d2u, 0x6c2ab542u, 0xdd6537d2u, 0x6c2ab542u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xfe7fffcau, 0xffffffffu, 0xfe7fffcau, 0xffffffffu, 0xde5ffb8au, 0xffffffffu, 0xde5ffb8au, 0xffffffffu, 0xfe7fffcau, 0xffffffffu, 0xde5ffb8au, 0xdd6537d2u, 0x6c2ab542u, 0xdd6537d2u, 0x6c2ab542u} +}; + +constexpr const char* Names[Checked] = { + "buffer_atomic_or_x2 returned lo", "buffer_atomic_or_x2 returned hi", "buffer_atomic_swap_x2 returned lo", "buffer_atomic_swap_x2 returned hi", + "buffer_atomic_or_x2 returned lo (exec-masked)", "buffer_atomic_or_x2 returned hi (exec-masked)", "buffer_atomic_swap_x2 returned lo (exec-masked)", "buffer_atomic_swap_x2 returned hi (exec-masked)", + "buffer_atomic_or_x2 returned lo (V# end)", "buffer_atomic_or_x2 returned hi (V# end)", "buffer_atomic_swap_x2 returned lo (V# end)", "buffer_atomic_swap_x2 returned hi (V# end)", + "or_x2 memory lo", "or_x2 memory hi", "or_x2 (no return) memory lo", "or_x2 (no return) memory hi", + "swap_x2 memory lo", "swap_x2 memory hi", "swap_x2 (no return) memory lo", "swap_x2 (no return) memory hi", + "or_x2 (exec-masked) memory lo", "or_x2 (exec-masked) memory hi", "swap_x2 (exec-masked) memory lo", "swap_x2 (exec-masked) memory hi", + "or_x2 (V# end) memory lo", "or_x2 (V# end) memory hi", "swap_x2 (V# end) memory lo", "swap_x2 (V# end) memory hi", +}; + +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, 0x31016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + Input.fill(0u); + for (std::uint32_t tid = 0; tid < waveSize; ++tid) std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), waveSize * Inputs * 4u); + const auto output = BufferDescriptor(Output.data(), waveSize * Results * 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{{waveSize, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + 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(); +} + +template +void Check(const std::uint32_t (&expected)[Lanes][Checked], const char* const (&names)[Checked], const char* run) { + for (std::uint32_t tid = 0; tid < Lanes; ++tid) { + for (std::uint32_t index = 0; index < Checked; ++index) { + const auto actual = Output[tid * Results + index]; + Require(actual == expected[tid][index], std::string("buffer 64-bit or/swap atomics ") + run + ": lane " + std::to_string(tid) + " " + names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device, Wave32Code, 32, device->Target()); + Check(Expected32, Names, "wave32"); + Run(*device, Wave64Code, 64, device->Target()); + Check(Expected64, Names, "wave64"); + Run(*device, Wave64Code, 64, device->ComputeTarget(32)); + Check(Expected64, Names, "wave64 split"); + std::puts("buffer 64-bit or/swap atomics tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} From cf10b5e60946a5e940851544c70f96dfb5e7a1f1 Mon Sep 17 00:00:00 2001 From: hamb3r Date: Mon, 5 Oct 2026 04:58:00 +0300 Subject: [PATCH 063/164] fix(avplayer): make decode-ahead buffers optional --- core/libs/prx/libSceAvPlayer/src/Source.cpp | 24 ++++++++++++- core/libs/tests/GuestAvPlayer.cpp | 40 +++++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAvPlayer/src/Source.cpp b/core/libs/prx/libSceAvPlayer/src/Source.cpp index 2b93a2eee..a59c07a47 100644 --- a/core/libs/prx/libSceAvPlayer/src/Source.cpp +++ b/core/libs/prx/libSceAvPlayer/src/Source.cpp @@ -327,6 +327,7 @@ public: return result; } } + addVideoDecodeAheadBuffers(); const auto start = startOffset; startOffset = 0; seek(start); @@ -605,7 +606,7 @@ private: pitch = AlignUp(static_cast(decoder.context->width), VideoPitchAlignment); bufferHeight = AlignUp(static_cast(decoder.context->height), VideoHeightAlignment); decoder.bufferSize = pitch * bufferHeight * 3 / 2; - count = settings.videoBuffers + VideoDecodeAheadFrames; + count = settings.videoBuffers; decoder.retained = settings.videoBuffers > 3 ? settings.videoBuffers - 3 : 0; texture = true; alignment = VideoBufferAlignment; @@ -625,6 +626,27 @@ private: return SCE_OK; } + void addVideoDecodeAheadBuffers() { + if (video.stream < 0) return; + + const auto& memory = settings.memory; + std::vector extra; + extra.reserve(VideoDecodeAheadFrames); + for (std::uint32_t index = 0; index < VideoDecodeAheadFrames; ++index) { + auto* buffer = static_cast(memory.allocate_texture( + memory.object_ptr, VideoBufferAlignment, video.bufferSize)); + if (!buffer) { + for (auto* allocated : extra) memory.deallocate_texture(memory.object_ptr, allocated); + return; + } + extra.push_back(buffer); + } + for (auto* buffer : extra) { + video.allocated.push_back(buffer); + video.free.push_back(buffer); + } + } + void releaseDecoders() { const auto& memory = settings.memory; for (auto* decoder : {&video, &audio}) { diff --git a/core/libs/tests/GuestAvPlayer.cpp b/core/libs/tests/GuestAvPlayer.cpp index 4092f512f..f365faced 100644 --- a/core/libs/tests/GuestAvPlayer.cpp +++ b/core/libs/tests/GuestAvPlayer.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -473,12 +474,17 @@ struct Allocations { std::mutex mutex; std::map blocks; int textures = 0; + std::optional textureLimit; }; Allocations allocations; void* Allocate(std::uint32_t alignment, std::uint32_t size, bool texture) { const auto aligned = static_cast(std::max(alignment, 16)); + { + std::lock_guard lock(allocations.mutex); + if (texture && allocations.textureLimit && allocations.textures >= *allocations.textureLimit) return nullptr; + } void* memory = ::operator new(size, aligned); std::lock_guard lock(allocations.mutex); allocations.blocks[memory] = {texture, aligned}; @@ -494,6 +500,7 @@ void Release(void* memory, bool texture) { if (found == allocations.blocks.end() || found->second.texture != texture) std::abort(); aligned = found->second.alignment; allocations.blocks.erase(found); + if (texture) --allocations.textures; } ::operator delete(memory, aligned); } @@ -509,6 +516,16 @@ bool IsAllocation(const void* memory, bool texture) { return found != allocations.blocks.end() && found->second.texture == texture; } +void SetTextureLimit(std::optional limit) { + std::lock_guard lock(allocations.mutex); + allocations.textureLimit = limit; +} + +int TextureCount() { + std::lock_guard lock(allocations.mutex); + return allocations.textures; +} + struct Events { std::mutex mutex; std::vector> received; @@ -898,6 +915,28 @@ void TestWithoutAllocators() { Check(sceAvPlayerClose(handle) == 0, "close failed"); } +void TestOptionalVideoBuffersRespectMemoryLimit() { + const auto run = [](int textureLimit, int expectedTextures) { + SetTextureLimit(textureLimit); + AvPlayerInitData init = InitData(nullptr); + init.num_output_video_framebuffers = 2; + auto* player = sceAvPlayerInit(&init); + Check(player != nullptr, "init with limited texture memory failed"); + Check(sceAvPlayerAddSource(player, "/app0/avplayer.mp4") == 0, "add source with limited texture memory failed"); + Check(WaitFor([&] { return TextureCount() == expectedTextures && sceAvPlayerIsActive(player); }), "unexpected optional video buffer count"); + AvPlayerFrameInfoEx frame{}; + Check(WaitFor([&] { return sceAvPlayerGetVideoDataEx(player, &frame) != 0; }), "no frame with limited texture memory"); + CheckVideoPicture(frame); + Check(sceAvPlayerClose(player) == 0, "close with limited texture memory failed"); + Check(TextureCount() == 0, "limited texture allocations were not released"); + }; + + run(2, 2); + run(4, 2); + run(6, 6); + SetTextureLimit(std::nullopt); +} + void TestFileReplacementAutoStart() { HostFile file; AvPlayerInitData init = InitData(nullptr); @@ -951,6 +990,7 @@ int main() { TestChangeStream(); TestExtendedExports(); TestWithoutAllocators(); + TestOptionalVideoBuffersRespectMemoryLimit(); TestFileReplacementAutoStart(); TestHandedOutFramesStayIntact(); std::puts("AvPlayer tests passed"); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 5c87a3d98..6d0d90210 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -24,6 +24,7 @@ Throughout the project, every function at every stage either **does exactly what ### Unknown function info +- [sceAvPlayerAddSource](../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) - requests up to 4 additional decode-ahead video framebuffers from the memory replacement beyond `num_output_video_framebuffers` when memory is available; the console's internal buffering behaviour is unknown - [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - the open param's first word is unknown (PPSA21564 passes 16; it is not the byte size, since the affinity mask is at offset 16); only 16 is accepted. Whether the param continues past offset 24 is also unknown - [sceRtcParseDateTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - accepted format assumed to be the ISO 8601 subset `YYYY-MM-DDTHH:MM:SS[.ffffff]` (T/t/space separator, optional fractional seconds) with the sibling RFC 3339 zone suffix (`Z` or `±HH:MM`); a string without a zone is taken as UTC, and anything outside this shape throws instead of returning a parse error, unverified against hardware - [AMPR WaitOnAddress / WaitOnCounter](../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libSceAmpr) - compare encoding assumed to be the WAIT_REG_MEM one (0 always, 1 <, 2 <=, 3 ==, 4 !=, 5 >=, 6 >) From f1a3507e90ed21dee629a64f6d4f794327a7532d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 04:01:05 +0200 Subject: [PATCH 064/164] test(shader): execute tbuffer_load_format_d16_xyzw against hardware results Co-Authored-By: Claude --- .../tests/execution/TbufferFormatD16.cpp | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/TbufferFormatD16.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/TbufferFormatD16.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/TbufferFormatD16.cpp new file mode 100644 index 000000000..8367d9e9f --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/TbufferFormatD16.cpp @@ -0,0 +1,183 @@ +#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 +#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, 0x34060086, 0xbe880300, 0xbe890301, 0xbe8a0302, 0xbe8b03ff, 0x0104bfac, 0xbe8c0300, + 0xbe8d0301, 0xbe8e0302, 0xbe8f03ff, 0x0104b80f, 0x7e1402ff, 0xdeadbeef, 0x7e1602ff, 0xdeadbeef, + 0x7e1802ff, 0xdeadbeef, 0x7e1a02ff, 0xdeadbeef, 0x7e1c02ff, 0xdeadbeef, 0x7e1e02ff, 0xdeadbeef, + 0x7e2002ff, 0xdeadbeef, 0x7e2202ff, 0xdeadbeef, 0x7e2402ff, 0xdeadbeef, 0x7e2602ff, 0xdeadbeef, + 0x7e2802ff, 0xdeadbeef, 0x7e2a02ff, 0xdeadbeef, 0x7e2c02ff, 0xdeadbeef, 0x7e2e02ff, 0xdeadbeef, + 0x7e3002ff, 0xdeadbeef, 0x7e3202ff, 0xdeadbeef, 0xea0b1000, 0x80220a01, 0xea131008, 0x80220c01, + 0xea1b1000, 0x80220e01, 0xea231008, 0x80221001, 0xe9c31000, 0x80231201, 0xe9eb1004, 0x80231401, + 0xea6b1000, 0x80221601, 0xea3b1008, 0x80231801, 0xbf8c3f70, 0xe0781000, 0x80010a03, 0xe0781010, + 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x0000ffffu, 0x80007fffu, 0x477ff000u, 0x7fc12345u}, + {0x80018000u, 0xffff0001u, 0x47800000u, 0x00000001u}, + {0x7c007bffu, 0xfc017e00u, 0x387fc000u, 0xc77fefffu}, + {0x3c00bc00u, 0x00ff7f80u, 0x33000001u, 0x7f800001u}, + {0x3f800000u, 0xbf800000u, 0x80000000u, 0xff800000u}, + {0x00000000u, 0xffffffffu, 0x7f7fffffu, 0x0001fffeu}, + {0xff812345u, 0x44288af2u, 0xd7027544u, 0xfd52c4d7u}, + {0x15c069bbu, 0xfce9537du, 0xf31476ddu, 0x16dc7861u}, + {0x199a4550u, 0xff812345u, 0x84aea175u, 0x51f08872u}, + {0x337fffffu, 0xd1213a4cu, 0x387fe000u, 0x337fffffu}, + {0x0158da62u, 0x7519a222u, 0x206d5e90u, 0x8ffa3554u}, + {0x3eaaaaabu, 0xd688103cu, 0xdaa5a003u, 0x8ca521f2u}, + {0xcf1d870du, 0x3eaaaaabu, 0x75e96b20u, 0x2b1c7d43u}, + {0x9d04abb9u, 0x477fe000u, 0x57a51839u, 0x821fd1c5u}, + {0x451afa03u, 0x3a9f5beeu, 0x61d759eeu, 0xdbd5edc9u}, + {0xecf74a37u, 0x82d0649bu, 0xee311fcdu, 0x6a5f6961u}, + {0x55353630u, 0x59016afbu, 0xd40f13f7u, 0x2df598f1u}, + {0xf86ec9a8u, 0x92559822u, 0x55b1a984u, 0xbe063793u}, + {0x509850eau, 0x38800000u, 0xbf800000u, 0x38800000u}, + {0x3b5736d1u, 0x48c9c423u, 0x8ee50022u, 0xe836ab09u}, + {0x1c06662cu, 0x14595e94u, 0xb19e16edu, 0xff0e1cb5u}, + {0x337fffffu, 0x1a490212u, 0xbf3b7a63u, 0x8ad4e305u}, + {0x63998cbau, 0x337fffffu, 0x8444c95au, 0xf34b3f43u}, + {0xff812345u, 0x477fe000u, 0xe01833bau, 0xb1764071u}, + {0x477fe000u, 0x44bf4210u, 0x07697348u, 0xf2ef3abbu}, + {0xad69e13cu, 0xd1645a10u, 0x5f2fe57fu, 0x091ebf4au}, + {0x3eaaaaabu, 0x198219beu, 0x20dbf8a0u, 0x5c43ebf6u}, + {0xdc0cdaf2u, 0x74bc740cu, 0xc9c00644u, 0xf2c8ae61u}, + {0xff812345u, 0xe8441553u, 0x38800000u, 0x7f800000u}, + {0x24d9a4e2u, 0xb286c097u, 0x38800000u, 0x5b60c5d2u}, + {0xc2c082a3u, 0x796e2dd3u, 0x4fea231au, 0xed6ec742u}, + {0x3f800000u, 0xd352bf48u, 0x5e6a7337u, 0xbf159734u} +}; + +constexpr std::uint32_t Expected[32][16] = { + {0x00003c00u, 0x38003800u, 0x3878b000u, 0x3bfc3469u, 0x00007bffu, 0x78007800u, 0x7478ec00u, 0x77fc7069u, 0x3c003c00u, 0x00000000u, 0x007fffffu, 0xff800000u, 0x80000000u, 0x7e097bffu, 0x477ff000u, 0x7fc12345u}, + {0x38003800u, 0x3c000100u, 0x38780000u, 0x00000200u, 0x78007800u, 0x7bff3c00u, 0x74780000u, 0x00003c00u, 0x38040000u, 0x38041c04u, 0x00000001u, 0xffffffffu, 0xfff88000u, 0x00007bffu, 0x47800000u, 0x00000001u}, + {0x37c037c0u, 0x3be037e0u, 0x3710b800u, 0xb710b000u, 0x77c077c0u, 0x7be077e0u, 0x7310f400u, 0xf310ec00u, 0x37b83c00u, 0x37c80000u, 0x007e0000u, 0xfffc0001u, 0xfbff7bffu, 0xfbff03ffu, 0x387fc000u, 0xc77fefffu}, + {0x338039e0u, 0x1bf837f8u, 0x36600200u, 0x3bf80200u, 0x738079e0u, 0x5bf877f8u, 0x72603c00u, 0x77f83c00u, 0x39e60000u, 0x33880000u, 0x007fff80u, 0x0000ffffu, 0x00002005u, 0x7c010000u, 0x33000001u, 0x7f800001u}, + {0x33f00000u, 0x39fc0000u, 0xbc000000u, 0x9c000000u, 0x73f00000u, 0x79fc0000u, 0xf8000000u, 0xd8000000u, 0x00000000u, 0x33e83804u, 0x00000000u, 0xffbfff80u, 0xbc003c00u, 0xfc008000u, 0x80000000u, 0xff800000u}, + {0x00000000u, 0x3c003c00u, 0x3bf88200u, 0x02008400u, 0x00000000u, 0x7bff7bffu, 0x77f8bc00u, 0x3c00c000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0xffff0000u, 0x00007bffu, 0x7f7fffffu, 0x0001fffeu}, + {0x3bfc3069u, 0x34433858u, 0xb5203b54u, 0xa55cb765u, 0x7bfc7069u, 0x74427858u, 0xf1207754u, 0xe15cf365u, 0x30643454u, 0x3c00380cu, 0xff8afff2u, 0x00440028u, 0x6144fc09u, 0xfbfffbffu, 0xd7027544u, 0xfd52c4d7u}, + {0x2d70369cu, 0x3be73538u, 0xae763b6eu, 0x31b73b86u, 0x6d70769cu, 0x7be77538u, 0xea76776eu, 0x6db77786u, 0x369739deu, 0x2d453a06u, 0x0053007du, 0xfffcffe9u, 0xfbff0000u, 0x0000fbffu, 0xf31476ddu, 0x16dc7861u}, + {0x2e673455u, 0x3bfc3069u, 0xbbb5b9e9u, 0x391fbb79u, 0x6e667455u, 0x7bfc7069u, 0xf7b5f5e9u, 0x751ff779u, 0x34543505u, 0x2e4638d5u, 0x00230045u, 0xffffff81u, 0xfc090000u, 0x7bff8000u, 0x84aea175u, 0x51f08872u}, + {0x32703c00u, 0x3a89334au, 0x3710b400u, 0x36708200u, 0x72707bffu, 0x7a89734au, 0x7310f000u, 0x7270bc00u, 0x3c003c00u, 0x326637f8u, 0x003a004cu, 0xffd10021u, 0xfbff0000u, 0x000003ffu, 0x387fe000u, 0x337fffffu}, + {0x1d603ad3u, 0x37523911u, 0x340e39e9u, 0xbb0036abu, 0x5d607ad3u, 0x77527911u, 0x700e75e9u, 0xf70072aau, 0x3ad73626u, 0x1c043586u, 0xffa20022u, 0x00750019u, 0x7bff0000u, 0x80000000u, 0x206d5e90u, 0x8ffa3554u}, + {0x33d53955u, 0x3ab42c0fu, 0xb4abba00u, 0xbb36343eu, 0x73d57955u, 0x7ab46c0fu, 0xf0abf600u, 0xf736703eu, 0x3955395du, 0x33c83955u, 0x0010003cu, 0xffd6ff88u, 0xfbff3555u, 0x8000fbffu, 0xdaa5a003u, 0x8ca521f2u}, + {0x3a793838u, 0x33d53955u, 0x3b5f3ab2u, 0x35643bd4u, 0x7a797838u, 0x73d57955u, 0x775f76b2u, 0x716477d4u, 0x383c2a87u, 0x3a7e2f47u, 0xffaaffabu, 0x003effaau, 0x3555fbffu, 0x00007bffu, 0x75e96b20u, 0x2b1c7d43u}, + {0x38e8395eu, 0x34783b00u, 0x397a320eu, 0xbbdeb5c7u, 0x78e8795eu, 0x74787b00u, 0x757a6e0eu, 0xf7def1c7u, 0x395d39ceu, 0x38ed2404u, 0xffe00000u, 0x0047007fu, 0x7bff8000u, 0x80007bffu, 0x57a51839u, 0x821fd1c5u}, + {0x34523bd0u, 0x335435bfu, 0x3a1d399fu, 0xb485b08eu, 0x74527bd0u, 0x735475bfu, 0x761d759fu, 0xf085ec8eu, 0x3bd82206u, 0x34542e87u, 0x005bffeeu, 0x003aff9fu, 0x14fa68d7u, 0xfbff7bffu, 0x61d759eeu, 0xdbd5edc9u}, + {0x3b6834a3u, 0x3817364au, 0xb07433f3u, 0x3aa63a96u, 0x7b6874a3u, 0x7816764au, 0xec746ff3u, 0x76a67696u, 0x34a532e7u, 0x3b673bc0u, 0x0064ff9bu, 0xff82ffd0u, 0x8000fbffu, 0x7bfffbffu, 0xee311fcdu, 0x6a5f6961u}, + {0x355332c6u, 0x359036b0u, 0xb57e30feu, 0x35bfba71u, 0x755372c6u, 0x759076b0u, 0xf17e6cfeu, 0x71bff671u, 0x32c73206u, 0x355532a7u, 0x006afffbu, 0x00590001u, 0x7bff7bffu, 0x0000fbffu, 0xd40f13f7u, 0x2df598f1u}, + {0x3bc33a4du, 0x389338c1u, 0x395bb968u, 0xb82036f2u, 0x7bc37a4du, 0x789378c1u, 0x755bf568u, 0xf42072f2u, 0x3a4e3945u, 0x3bc836e7u, 0xff980022u, 0xff920055u, 0x8000fbffu, 0xb0317bffu, 0x55b1a984u, 0xbe063793u}, + {0x350a350fu, 0x33100000u, 0xb8080000u, 0x37100000u, 0x750a750fu, 0x73100000u, 0xf4080000u, 0x73100000u, 0x35053b57u, 0x350538c5u, 0x00000000u, 0x0038ff80u, 0x04007bffu, 0x0400bc00u, 0xbf800000u, 0x38800000u}, + {0x336b32dau, 0x348d3a21u, 0xbb121440u, 0xb1f3b94fu, 0x736b72dau, 0x748d7a21u, 0xf7125040u, 0xedf2f54fu, 0x32c73a8fu, 0x33673575u, 0xffc40023u, 0x0048ffc9u, 0x7bff1ab9u, 0xfbff8000u, 0x8ee50022u, 0xe836ab09u}, + {0x2f023663u, 0x2d1635e9u, 0xb8e631bbu, 0x9f90332du, 0x6f027663u, 0x6d1675e9u, 0xf4e66dbbu, 0xdb906f2du, 0x36663186u, 0x2f072606u, 0x005eff94u, 0x00140059u, 0x00000000u, 0xfbff8000u, 0xb19e16edu, 0xff0e1cb5u}, + {0x32703c00u, 0x2e922024u, 0xb80c3ba6u, 0xbb53b33fu, 0x72707bffu, 0x6e926024u, 0xf40c77a6u, 0xf753ef3fu, 0x3c003c00u, 0x326637f8u, 0x00020012u, 0x001a0049u, 0x00000000u, 0x8000b9dbu, 0xbf3b7a63u, 0x8ad4e305u}, + {0x363a3866u, 0x32703c00u, 0xbbbcb6d5u, 0xae5b37e8u, 0x763a7866u, 0x72707bffu, 0xf7bcf2d5u, 0xea5a73e8u, 0x386439d6u, 0x363638cdu, 0xffffffffu, 0x0033007fu, 0x00007bffu, 0xfbff8000u, 0x8444c95au, 0xf34b3f43u}, + {0x3bfc3069u, 0x34783b00u, 0xb3fa3677u, 0xb8e93807u, 0x7bfc7069u, 0x74787b00u, 0xeffa7277u, 0xf4e97407u, 0x30643454u, 0x3c00380cu, 0xffe00000u, 0x0047007fu, 0x7bfffc09u, 0x8000fbffu, 0xe01833bau, 0xb1764071u}, + {0x34783b00u, 0x344c3421u, 0x2b693b35u, 0xae893757u, 0x74787b00u, 0x744c7421u, 0x67697734u, 0xea887357u, 0x3b070000u, 0x347437f8u, 0x00420010u, 0x0044ffbfu, 0x65fa7bffu, 0xfbff0000u, 0x07697348u, 0xf2ef3abbu}, + {0x396b3b0au, 0x3a8b35a1u, 0x39f3b2a0u, 0x2c8fb80bu, 0x796b7b0au, 0x7a8b75a1u, 0x75f3eea0u, 0x688ff40bu, 0x3b0f3388u, 0x396d3697u, 0x005a0010u, 0xffd10064u, 0xfbff8000u, 0x00007bffu, 0x5f2fe57fu, 0x091ebf4au}, + {0x33d53955u, 0x2e612e70u, 0x341bab60u, 0x39c4b103u, 0x73d57955u, 0x6e606e70u, 0x701be760u, 0x75c4ed02u, 0x3955395du, 0x33c83955u, 0x0019ffbeu, 0x0019ff82u, 0x00003555u, 0x7bff0000u, 0x20dbf8a0u, 0x5c43ebf6u}, + {0x3ae03ad8u, 0x374c3741u, 0xb6c82a44u, 0xae9cb91au, 0x7ae07ad8u, 0x774c7741u, 0xf2c86644u, 0xea9cf51au, 0x3ad73b98u, 0x3ae72a06u, 0x0074000cu, 0x0074ffbcu, 0x7bfffbffu, 0xfbfffbffu, 0xc9c00644u, 0xf2c8ae61u}, + {0x3bfc3069u, 0x3b422d55u, 0x37100000u, 0x3bf80000u, 0x7bfc7069u, 0x7b426d55u, 0x73100000u, 0x77f80000u, 0x30643454u, 0x3c00380cu, 0x00150053u, 0xffe80044u, 0xfbfffc09u, 0x7c000400u, 0x38800000u, 0x7f800000u}, + {0x309b3927u, 0x39943a05u, 0x37100000u, 0x39b6b746u, 0x709b7927u, 0x79947a05u, 0x73100000u, 0x75b6f346u, 0x39253b17u, 0x30853acfu, 0xffc0ff97u, 0xffb2ff86u, 0x80000000u, 0x7bff0400u, 0x38800000u, 0x5b60c5d2u}, + {0x3a163815u, 0x379731bau, 0x38ff3463u, 0xb0a5b718u, 0x7a167815u, 0x779771bau, 0x74ff7063u, 0xeca4f318u, 0x3814391du, 0x3a163a06u, 0x002dffd3u, 0x0079006eu, 0x7bffd604u, 0xfbff7bffu, 0x4fea231au, 0xed6ec742u}, + {0x33f00000u, 0x3a9b39fau, 0x39e73b33u, 0xb80fba8du, 0x73f00000u, 0x7a9b79fau, 0x75e77733u, 0xf40ff68du, 0x00000000u, 0x33e83804u, 0xffbf0048u, 0xffd30052u, 0xfbff3c00u, 0xb8ac7bffu, 0x5e6a7337u, 0xbf159734u} +}; + +constexpr const char* Names[Results] = { + "16_16_16_16_UNORM offset 0 xy", "16_16_16_16_UNORM offset 0 zw", + "16_16_16_16_SNORM offset 8 xy", "16_16_16_16_SNORM offset 8 zw", + "16_16_16_16_USCALED offset 0 xy", "16_16_16_16_USCALED offset 0 zw", + "16_16_16_16_SSCALED offset 8 xy", "16_16_16_16_SSCALED offset 8 zw", + "8_8_8_8_UNORM swizzled offset 0 xy", "8_8_8_8_UNORM swizzled offset 0 zw", + "8_8_8_8_SINT swizzled offset 4 xy", "8_8_8_8_SINT swizzled offset 4 zw", + "32_32_32_32_FLOAT offset 0 xy", "32_32_32_32_FLOAT offset 0 zw", + "16_16_16_16_FLOAT swizzled offset 8 xy", "16_16_16_16_FLOAT swizzled offset 8 zw", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("tbuffer format d16: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < Results; ++i) Expect(tid, out[i], Expected[tid][i], Names[i]); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("tbuffer format d16 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} From e08333825f730294e40b74e2be0216307cb71ddb Mon Sep 17 00:00:00 2001 From: Nicolas Abosch <88494836+nicolasabosch@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:00:07 -0300 Subject: [PATCH 065/164] test(agc): validate pixel LDS and expose pipeline statistics --- .../Execution/src/VulkanDevice.cpp | 18 ++ .../Graphics/include/Context.hpp | 1 + .../Graphics/include/Pipeline.hpp | 2 + .../libSceAgcDriver/Graphics/src/Pipeline.cpp | 47 +++++ .../tests/execution/PixelLds.cpp | 177 ++++++++++++++++++ docs/dev/BUILD.md | 4 + 6 files changed, 249 insertions(+) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/PixelLds.cpp diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index cc606ac60..98d526ccb 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -191,6 +191,7 @@ struct VulkanDevice::State { bool primitiveListRestart = false; bool depthClipControl = false; bool imageViewMinLod = false; + bool pipelineExecutableInfo = false; bool maintenance8 = false; bool depthClamp = false; bool depthBounds = false; @@ -957,6 +958,20 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u } else { std::fprintf(stderr, "[gpu] timeline semaphores unavailable or disabled; drains wait under the GPU mutex\n"); } + VkPhysicalDevicePipelineExecutablePropertiesFeaturesKHR pipelineFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_EXECUTABLE_PROPERTIES_FEATURES_KHR}; + const auto pipelineStats = std::getenv("APS5_PIPELINE_STATS"); + if (pipelineStats && std::string_view(pipelineStats) == "1") { + Graphics::Require(hasExtension(VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME), "APS5_PIPELINE_STATS requires VK_KHR_pipeline_executable_properties"); + VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &pipelineFeatures}; + state->InstanceFunction("vkGetPhysicalDeviceFeatures2")(selected, &features); + Graphics::Require(pipelineFeatures.pipelineExecutableInfo == VK_TRUE, "APS5_PIPELINE_STATS requires pipelineExecutableInfo"); + state->pipelineExecutableInfo = true; + deviceExtensions.push_back(VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME); + deviceInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + deviceInfo.ppEnabledExtensionNames = deviceExtensions.data(); + pipelineFeatures.pNext = byteFeatures.pNext; + byteFeatures.pNext = &pipelineFeatures; + } bdaFeatures.pNext = &byteFeatures; deviceInfo.pNext = &bdaFeatures; check(state->InstanceFunction("vkCreateDevice")(selected, &deviceInfo, nullptr, &state->device), "vkCreateDevice"); @@ -2305,6 +2320,7 @@ Graphics::Context VulkanDevice::buildContext() const { context.descriptorIndexing = state->descriptorIndexing; context.primitiveListRestart = state->primitiveListRestart; context.imageViewMinLod = state->imageViewMinLod; + context.pipelineExecutableInfo = state->pipelineExecutableInfo; return context; } @@ -3309,6 +3325,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec layoutInfo.pPushConstantRanges = pushStages != 0 ? &push : nullptr; check(state->DeviceFunction("vkCreatePipelineLayout")(state->device, &layoutInfo, nullptr, &objects->layout), "vkCreatePipelineLayout"); VkComputePipelineCreateInfo pipelineInfo{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; + pipelineInfo.flags = context.pipelineExecutableInfo ? VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR : 0; pipelineInfo.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; pipelineInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; pipelineInfo.stage.module = objects->module; @@ -3319,6 +3336,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec pipelineInfo.layout = objects->layout; if (profile && shader.spirv.size() > 100000) std::fprintf(stderr, "[dispatch] creating a pipeline for %zu SPIR-V words (program 0x%llx)\n", shader.spirv.size(), static_cast(programAddress)); check(state->DeviceFunction("vkCreateComputePipelines")(state->device, context.pipelineCache, 1, &pipelineInfo, nullptr, &objects->pipeline), "vkCreateComputePipelines"); + Graphics::LogPipelineStatistics_nid_no_patch(context, objects->pipeline); timing.Mark("pipeline_create"); if (pipelineKey != 0) { // Find-or-insert: objects another dispatch of the variant mapped meanwhile serve this diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp index 99f77ec86..238c05424 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp @@ -135,6 +135,7 @@ struct Context { bool descriptorIndexing = false; bool primitiveListRestart = false; bool imageViewMinLod = false; + bool pipelineExecutableInfo = false; template TFunction Function(const char* name) const { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp index 6f54f0fff..fe55c0dce 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp @@ -8,6 +8,8 @@ namespace AgcDriver::Graphics { struct VertexInputLayout; +void LogPipelineStatistics_nid_no_patch(const Context& context, VkPipeline pipeline); + // The attachments of one render pass instance. Work recorded with it references the handle until // the batch completes, so a recorded draw keeps the object (Recorder::Keep) like its pipeline. class Framebuffer { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp index 718587525..1a4c8a7a8 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp @@ -14,6 +14,51 @@ namespace AgcDriver::Graphics { +void LogPipelineStatistics_nid_no_patch(const Context& context, VkPipeline pipeline) { + if (!context.pipelineExecutableInfo) return; + const auto getProperties = context.Function("vkGetPipelineExecutablePropertiesKHR"); + const auto getStatistics = context.Function("vkGetPipelineExecutableStatisticsKHR"); + VkPipelineInfoKHR pipelineInfo{VK_STRUCTURE_TYPE_PIPELINE_INFO_KHR}; + pipelineInfo.pipeline = pipeline; + std::uint32_t count = 0; + Check(getProperties(context.device, &pipelineInfo, &count, nullptr), "vkGetPipelineExecutablePropertiesKHR count"); + std::vector properties(count, {VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_PROPERTIES_KHR}); + Check(getProperties(context.device, &pipelineInfo, &count, properties.data()), "vkGetPipelineExecutablePropertiesKHR"); + properties.resize(count); + for (std::uint32_t index = 0; index < properties.size(); ++index) { + VkPipelineExecutableInfoKHR executable{VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_INFO_KHR}; + executable.pipeline = pipeline; + executable.executableIndex = index; + count = 0; + Check(getStatistics(context.device, &executable, &count, nullptr), "vkGetPipelineExecutableStatisticsKHR count"); + std::vector statistics(count, {VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_STATISTIC_KHR}); + Check(getStatistics(context.device, &executable, &count, statistics.data()), "vkGetPipelineExecutableStatisticsKHR"); + statistics.resize(count); + const auto& property = properties[index]; + std::fprintf(stderr, "[pipeline-stats] executable=%u stages=0x%x subgroup=%u name=%s\n", index, property.stages, property.subgroupSize, property.name); + for (const auto& statistic : statistics) { + std::fprintf(stderr, "[pipeline-stats] executable=%u %s=", index, statistic.name); + switch (statistic.format) { + case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_BOOL32_KHR: + std::fprintf(stderr, "%s", statistic.value.b32 ? "true" : "false"); + break; + case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_INT64_KHR: + std::fprintf(stderr, "%lld", static_cast(statistic.value.i64)); + break; + case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_UINT64_KHR: + std::fprintf(stderr, "%llu", static_cast(statistic.value.u64)); + break; + case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_FLOAT64_KHR: + std::fprintf(stderr, "%.17g", statistic.value.f64); + break; + default: + throw std::runtime_error("unsupported pipeline statistic format"); + } + std::fprintf(stderr, " (%s)\n", statistic.description); + } + } +} + Framebuffer::Framebuffer(const Context& context, VkRenderPass renderPass, std::span targets, VkExtent2D extent) : context(context) { // Cached objects outlive their device's teardown; they must not keep its buffer pool alive past it. this->context.bufferPool.reset(); @@ -177,6 +222,7 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput blend.pAttachments = state.blends.empty() ? nullptr : state.blends.data(); std::copy(state.blendConstants.begin(), state.blendConstants.end(), blend.blendConstants); VkGraphicsPipelineCreateInfo pipelineInfo{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO}; + pipelineInfo.flags = context.pipelineExecutableInfo ? VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR : 0; pipelineInfo.stageCount = static_cast(stages.size()); pipelineInfo.pStages = stages.data(); VkPipelineTessellationStateCreateInfo tessellation{VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO}; @@ -195,6 +241,7 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput pipelineInfo.layout = layout; pipelineInfo.renderPass = renderPass; Check(context.Function("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateGraphicsPipelines"); + LogPipelineStatistics_nid_no_patch(context, pipeline); } catch (...) { release(); throw; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PixelLds.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelLds.cpp new file mode 100644 index 000000000..ba7b210d8 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelLds.cpp @@ -0,0 +1,177 @@ +#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 + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Sentinel = 0xdeadbeefu; +constexpr std::uint32_t Width = 64; +constexpr std::uint32_t Height = 32; +alignas(256) std::array Pixels{}; +alignas(256) std::array Results{}; + +alignas(256) constexpr std::array VertexCode{ + 0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000, +}; + +std::vector PixelCode(bool bounded) { + std::vector code{0x7e040f00, 0x7e060f01, 0xd5430006, 0x0409ff03, Width}; + const auto vop2 = [&](std::uint32_t opcode, std::uint32_t destination, std::uint32_t source, std::uint32_t vector) { + code.push_back(opcode | (destination << 17u) | (vector << 9u) | source); + }; + vop2(0x34000000, 8, 0x88, 6); + for (std::uint32_t index = 0; index < 32; ++index) { + vop2(0x4a000000, 4, 0x80 + index, 2); + vop2(0x36000000, 9, 0x9f, 4); + vop2(0x34000000, 13, 0x82, 9); + vop2(0x4a000000, 5, 0x80 + index, 8); + for (std::uint32_t bank = 0; bank < 3; ++bank) { + if (bank != 0) vop2(0x4a000000, 5, 0xa0, 5); + code.push_back(0xd8340000 | (bank * 256u)); + code.push_back(0x0000050d); + } + } + code.push_back(0xbf8cc07f); + if (bounded) vop2(0x36000000, 14, 0x9f, 3); + vop2(0x34000000, 15, 0x82, bounded ? 14 : 3); + for (std::uint32_t bank = 0; bank < 3; ++bank) { + code.push_back(0xd8d80000 | (bank * 256u)); + code.push_back(((10u + bank) << 24u) | 15u); + } + code.push_back(0xbf8cc07f); + for (std::uint32_t bank = 0; bank < 3; ++bank) { + code.push_back(0xe0702000 | (bank * 4u)); + code.push_back(0x80010006 | ((10u + bank) << 8u)); + } + code.insert(code.end(), {0x7e0e02f2, 0xf800180f, 0x07070707, 0xbf810000}); + return code; +} + +std::array BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu}; +} + +constexpr std::array, 3> Triangles{{ + {-1.0f, -1.0f, 0.5f, 1.0f}, {3.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 3.0f, 0.5f, 1.0f} +}}; + +void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, bool bounded) { + const auto& triangles = Triangles; + const auto pixelCode = PixelCode(bounded); + Pixels.fill(std::byte{0}); + Results.fill(Sentinel); + const auto target = device.Target(); + + std::vector vertexUserData(4, 0u); + const auto vertexBuffer = BufferDescriptor(triangles.data(), 16u, static_cast(triangles.size())); + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {waveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertex.useCache = false; + const auto vertexResult = ShaderRecompiler::Recompile(vertex); + const auto vertexPush = static_cast(vertexResult.pushConstants.size()); + + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.wave32 = waveSize == 32u; + pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionX) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionY); + pixel.posX = true; + pixel.posY = true; + pixel.targetOutputMode[0] = 9; + pixel.targetExportMapping.fill(0xe4u); + std::vector pixelUserData(8, 0u); + const auto results = BufferDescriptor(Results.data(), 16u, Width * Height); + std::copy(results.begin(), results.end(), pixelUserData.begin() + 4); + const std::array pixelMemory{{{reinterpret_cast(pixelCode.data()), std::as_bytes(std::span(pixelCode))}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, + {waveSize, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, + target, + {0, 0, vertexPush, 128 - vertexPush} + }; + fragment.useCache = false; + const auto pixelResult = ShaderRecompiler::Recompile(fragment); + const std::array shaders{{ + {ShaderStage::Vertex, &vertexResult, 0}, + {ShaderStage::Fragment, &pixelResult, vertexPush} + }}; + + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, waveSize, waveSize, std::nullopt, std::nullopt}; + state.color = {reinterpret_cast(Pixels.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Pixels.size(), 0xe4u}; + state.colors = {state.color}; + state.hasColorTarget = true; + state.renderExtent = {Width, Height}; + state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + state.viewport = {0, static_cast(Height), static_cast(Width), -static_cast(Height), 0, 1}; + state.negativeOneToOne = false; + state.scissor = {{0, 0}, {Width, Height}}; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blend.colorWriteMask = 15; + state.blends = {state.blend}; + state.blendConstants = {}; + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(triangles.size()), 0, 1, 0, false}; + device.Draw(state, draw, shaders); + device.WaitIdle(); +} + +void Check(std::uint32_t waveSize, bool bounded) { + const auto what = "pixel LDS wave" + std::to_string(waveSize) + (bounded ? " bounded" : " unbounded"); + for (std::uint32_t pixel = 0; pixel < Width * Height; ++pixel) { + for (std::uint32_t channel = 0; channel < 4; ++channel) { + Require(Pixels[pixel * 4u + channel] == std::byte{255}, what + ": missing color at pixel " + std::to_string(pixel)); + } + const auto x = pixel % Width; + const auto y = pixel / Width; + const auto index = (y + 32u - x) & 31u; + for (std::uint32_t bank = 0; bank < 3; ++bank) { + const auto expected = pixel * 256u + index + bank * 32u; + const auto actual = Results[pixel * 4u + bank]; + Require(actual == expected, what + ": pixel " + std::to_string(pixel) + " bank " + std::to_string(bank) + " expected " + std::to_string(expected) + " got " + std::to_string(actual)); + } + Require(Results[pixel * 4u + 3u] == Sentinel, what + ": output stride guard overwritten"); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < 32u) { + std::printf("skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); + return VulkanTestSkipped; + } + for (const auto waveSize : {64u, 32u}) { + for (const auto bounded : {true, false}) { + std::fprintf(stderr, "pixel LDS wave%u %s\n", waveSize, bounded ? "bounded" : "unbounded"); + for (std::uint32_t pass = 0; pass < 3; ++pass) { + Draw(*device, waveSize, bounded); + Check(waveSize, bounded); + } + } + } + std::puts("pixel LDS tests passed: 12 draws, 24576 pixels, 73728 LDS results"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/docs/dev/BUILD.md b/docs/dev/BUILD.md index cc8f15a80..847b63cf7 100644 --- a/docs/dev/BUILD.md +++ b/docs/dev/BUILD.md @@ -46,6 +46,10 @@ Build configuration parameters: SDL and FreeType settings forced by the root `CMakeLists.txt` cannot be overridden with `-D`. +## Pipeline statistics + +Set `APS5_PIPELINE_STATS=1` to capture and print driver statistics for each newly created graphics or compute pipeline. This requires `VK_KHR_pipeline_executable_properties` and `pipelineExecutableInfo`; an unsupported device fails with an error. Statistic names and units are driver-specific. Capturing statistics can increase pipeline compilation cost. The setting is disabled by default. + ## Shader recompiler The shader recompilation logic in [core/shader/recompiler](../../core/shader/recompiler) is isolated from the rest of the project and is a pure function of its input data, designed for integration into any other project. The current CMake target also includes cache support and links a supplied runtime target, glslang, and optionally SPIRV-Tools. From bb165d2ab45225a78adcf969545c5dc4cc9a6d14 Mon Sep 17 00:00:00 2001 From: hamb3r Date: Mon, 5 Oct 2026 05:06:36 +0300 Subject: [PATCH 066/164] test(avplayer): preserve texture allocation accounting --- core/libs/tests/GuestAvPlayer.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/libs/tests/GuestAvPlayer.cpp b/core/libs/tests/GuestAvPlayer.cpp index f365faced..30cab7e67 100644 --- a/core/libs/tests/GuestAvPlayer.cpp +++ b/core/libs/tests/GuestAvPlayer.cpp @@ -500,7 +500,6 @@ void Release(void* memory, bool texture) { if (found == allocations.blocks.end() || found->second.texture != texture) std::abort(); aligned = found->second.alignment; allocations.blocks.erase(found); - if (texture) --allocations.textures; } ::operator delete(memory, aligned); } @@ -523,7 +522,9 @@ void SetTextureLimit(std::optional limit) { int TextureCount() { std::lock_guard lock(allocations.mutex); - return allocations.textures; + return static_cast(std::count_if(allocations.blocks.begin(), allocations.blocks.end(), [](const auto& allocation) { + return allocation.second.texture; + })); } struct Events { From 86e5ae5cdb1a33d2f63a816aff06f6aa6d5e2cba Mon Sep 17 00:00:00 2001 From: hamb3r Date: Mon, 5 Oct 2026 05:13:21 +0300 Subject: [PATCH 067/164] test(avplayer): apply texture limits to active buffers --- core/libs/tests/GuestAvPlayer.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/core/libs/tests/GuestAvPlayer.cpp b/core/libs/tests/GuestAvPlayer.cpp index 30cab7e67..e9981ef53 100644 --- a/core/libs/tests/GuestAvPlayer.cpp +++ b/core/libs/tests/GuestAvPlayer.cpp @@ -479,11 +479,17 @@ struct Allocations { Allocations allocations; +int ActiveTextureCountLocked() { + return static_cast(std::count_if(allocations.blocks.begin(), allocations.blocks.end(), [](const auto& allocation) { + return allocation.second.texture; + })); +} + void* Allocate(std::uint32_t alignment, std::uint32_t size, bool texture) { const auto aligned = static_cast(std::max(alignment, 16)); { std::lock_guard lock(allocations.mutex); - if (texture && allocations.textureLimit && allocations.textures >= *allocations.textureLimit) return nullptr; + if (texture && allocations.textureLimit && ActiveTextureCountLocked() >= *allocations.textureLimit) return nullptr; } void* memory = ::operator new(size, aligned); std::lock_guard lock(allocations.mutex); @@ -522,9 +528,7 @@ void SetTextureLimit(std::optional limit) { int TextureCount() { std::lock_guard lock(allocations.mutex); - return static_cast(std::count_if(allocations.blocks.begin(), allocations.blocks.end(), [](const auto& allocation) { - return allocation.second.texture; - })); + return ActiveTextureCountLocked(); } struct Events { From 75f407b92644a96af0e67d61dd6a184255076399 Mon Sep 17 00:00:00 2001 From: NumberGuan Date: Sun, 4 Oct 2026 23:22:50 -0300 Subject: [PATCH 068/164] fix(relinker): discover bundled modules in prx directories Preserve each bundled module directory under app0 and resolve Linux dependencies across directories. Add integration coverage for both output formats and Windows startup. Refs #556; duplicate-export rejection remains unchanged. --- core/relinker/CMakeLists.txt | 2 + core/relinker/main.cpp | 10 +- .../relinker/src/guest/GuestModuleBuilder.cpp | 48 ++++--- .../tests/test_guest_module_directories.py | 131 ++++++++++++++++++ .../tests/test_linux_load_alignment.py | 2 +- docs/user/USAGE.md | 4 +- 6 files changed, 170 insertions(+), 27 deletions(-) create mode 100644 core/relinker/relinker/tests/test_guest_module_directories.py diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 5865b02a9..5452874a6 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -208,6 +208,8 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_intel_trampolines.py $) add_test(NAME guest_tls COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_tls.py $) + add_test(NAME guest_module_directories COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_module_directories.py $) add_test(NAME elf_header COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_elf_header.py $) add_test(NAME input_magic COMMAND ${Python3_EXECUTABLE} diff --git a/core/relinker/main.cpp b/core/relinker/main.cpp index 05b13295a..2e3b6553e 100644 --- a/core/relinker/main.cpp +++ b/core/relinker/main.cpp @@ -80,8 +80,8 @@ int main(const int argc, char* argv[]) { ); std::cout << "System: " << (args.toWindows ? "Windows" : "Linux") << "; unused-filter=" << args.unusedFilterLevel << "\n"; - std::cout << "sce_module/sce_modules processing: " << (args.skipSceModule ? "disabled (--skip-sce-module)" : "enabled") << '\n'; - for (const auto& name : args.excludedSceModules) std::cout << "sce_module excluded: " << name << '\n'; + std::cout << "sce_module/sce_modules/prx processing: " << (args.skipSceModule ? "disabled (--skip-sce-module)" : "enabled") << '\n'; + for (const auto& name : args.excludedSceModules) std::cout << "Guest module excluded: " << name << '\n'; auto result = pipeline->Relink(sourceBytes); for (const auto& patch : result.Patches) { if (patch.Offset > sourceBytes.size() || patch.Bytes.size() > sourceBytes.size() - patch.Offset) @@ -134,10 +134,8 @@ int main(const int argc, char* argv[]) { std::cout << "Expected runtime layout (relative to the output executable):\n" << std::filesystem::path(absPath).filename().string() << "\n" << "libs/\n *.prx\napp0/\n \n"; - if (!guestArtifacts.empty()) { - std::cout << " " << guestArtifacts.front().Path.parent_path().filename().string() << "/\n"; - for (const auto& artifact : guestArtifacts) std::cout << " " << artifact.Path.filename().string() << '\n'; - } + for (const auto& artifact : guestArtifacts) + std::cout << " " << artifact.Path.parent_path().filename().string() << "/" << artifact.Path.filename().string() << '\n'; std::cout << "Game resources and system libraries must be placed in this layout separately.\n"; if (args.runPath != "$ORIGIN/libs") std::cout << "Custom library search path (--rpath): " << args.runPath << '\n'; diff --git a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp index 27bd97b78..c14b3efae 100644 --- a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp +++ b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp @@ -15,26 +15,35 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path const auto root = std::filesystem::absolute(inputPath).parent_path(); const auto singular = root / "sce_module"; const auto plural = root / "sce_modules"; + const auto prx = root / "prx"; const bool hasSingular = std::filesystem::exists(singular); const bool hasPlural = std::filesystem::exists(plural); + const bool hasPrx = std::filesystem::exists(prx); if (hasSingular && hasPlural) throw Domain::RelinkerException("Both sce_module and sce_modules exist beside the input executable"); - if (!hasSingular && !hasPlural) throw Domain::RelinkerException("sce_module/sce_modules was not found beside the input executable: " + root.string() + ". Use --skip-sce-module only if this game can run without these modules."); - const auto directory = hasSingular ? singular : plural; - if (!std::filesystem::is_directory(directory)) throw Domain::RelinkerException("Guest module path is not a directory: " + directory.string()); + if (!hasSingular && !hasPlural && !hasPrx) throw Domain::RelinkerException("sce_module/sce_modules/prx was not found beside the input executable: " + root.string() + ". Use --skip-sce-module only if this game can run without these modules."); + std::vector directories; + if (hasSingular || hasPlural) directories.push_back(hasSingular ? singular : plural); + if (hasPrx) directories.push_back(prx); std::vector paths; std::set unmatchedExclusions = excludedModules; - for (const auto& entry : std::filesystem::directory_iterator(directory)) { - if (entry.path().filename().string().ends_with(GuestModuleSuffix)) continue; - if (unmatchedExclusions.erase(entry.path().filename().string()) != 0) continue; - if (!entry.is_regular_file()) continue; - std::ifstream stream(entry.path(), std::ios::binary); - if (!stream) throw Domain::RelinkerException("Cannot read guest candidate: " + entry.path().string()); - char magic[4]{}; - stream.read(magic, 4); - if (stream.bad()) throw Domain::RelinkerException("Cannot read guest candidate magic: " + entry.path().string()); - if (stream.gcount() == 4 && static_cast(magic[0]) == 0x7f && magic[1] == 'E' && magic[2] == 'L' && magic[3] == 'F') paths.push_back(entry.path()); + for (const auto& directory : directories) { + if (!std::filesystem::is_directory(directory)) throw Domain::RelinkerException("Guest module path is not a directory: " + directory.string()); + for (const auto& entry : std::filesystem::directory_iterator(directory)) { + if (entry.path().filename().string().ends_with(GuestModuleSuffix)) continue; + if (excludedModules.contains(entry.path().filename().string())) { + unmatchedExclusions.erase(entry.path().filename().string()); + continue; + } + if (!entry.is_regular_file()) continue; + std::ifstream stream(entry.path(), std::ios::binary); + if (!stream) throw Domain::RelinkerException("Cannot read guest candidate: " + entry.path().string()); + char magic[4]{}; + stream.read(magic, 4); + if (stream.bad()) throw Domain::RelinkerException("Cannot read guest candidate magic: " + entry.path().string()); + if (stream.gcount() == 4 && static_cast(magic[0]) == 0x7f && magic[1] == 'E' && magic[2] == 'L' && magic[3] == 'F') paths.push_back(entry.path()); + } } - if (!unmatchedExclusions.empty()) throw Domain::RelinkerException("Excluded sce_module file not found: " + *unmatchedExclusions.begin()); + if (!unmatchedExclusions.empty()) throw Domain::RelinkerException("Excluded guest module file not found: " + *unmatchedExclusions.begin()); std::sort(paths.begin(), paths.end()); if (paths.empty()) return {}; if (lazyBinding) throw Domain::RelinkerException("Guest modules require eager binding; --lazy-binding is incompatible"); @@ -132,8 +141,7 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path Io::AppendU64(dynamic.DynamicSegmentData, dynamic.DynStrData.size()); Io::AppendString(dynamic.DynStrData, name); }; - const auto relativeDirectory = "app0/" + directory.filename().generic_string(); - for (const auto index : order) if (!windows) addNeeded("$ORIGIN/" + relativeDirectory + "/" + images[index].OutputName); + for (const auto index : order) if (!windows) addNeeded("$ORIGIN/app0/" + images[index].SourcePath.parent_path().filename().generic_string() + "/" + images[index].OutputName); for (const auto& name : hostLibraries) addNeeded(name); std::string guestRunPath = runPath; if (!windows) { @@ -142,9 +150,10 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path else if (!std::filesystem::path(guestRunPath).is_absolute()) throw Domain::RelinkerException("Guest Linux run path must be absolute or begin with $ORIGIN"); } std::vector artifacts; - const auto destination = std::filesystem::absolute(outputPath).parent_path() / relativeDirectory; for (const auto index : order) { const auto& image = images[index]; + const auto relativeDirectory = "app0/" + image.SourcePath.parent_path().filename().generic_string(); + const auto destination = std::filesystem::absolute(outputPath).parent_path() / relativeDirectory; const auto target = destination / image.OutputName; if (target.lexically_normal() == std::filesystem::absolute(outputPath).lexically_normal()) throw Domain::RelinkerException("Guest output collides with the executable output"); for (const auto& source : paths) if (std::filesystem::exists(target) && std::filesystem::equivalent(source, target)) throw Domain::RelinkerException("Guest output would overwrite an input module: " + target.string()); @@ -156,7 +165,10 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path if (windows) output = Elfpatcher::GuestModuleWriter().WriteWindows(image, runtime); else { std::vector needed; - for (const auto dependency : dependencies[index]) needed.push_back("$ORIGIN/" + images[dependency].OutputName); + for (const auto dependency : dependencies[index]) { + const auto dependencyPath = images[dependency].SourcePath.parent_path() / images[dependency].OutputName; + needed.push_back("$ORIGIN/" + dependencyPath.lexically_relative(image.SourcePath.parent_path()).generic_string()); + } needed.insert(needed.end(), hostLibraries.begin(), hostLibraries.end()); output = Elfpatcher::GuestModuleWriter().WriteLinux(image, needed, guestRunPath); } diff --git a/core/relinker/relinker/tests/test_guest_module_directories.py b/core/relinker/relinker/tests/test_guest_module_directories.py new file mode 100644 index 000000000..5d0389cb2 --- /dev/null +++ b/core/relinker/relinker/tests/test_guest_module_directories.py @@ -0,0 +1,131 @@ +from pathlib import Path +import os +import struct +import subprocess +import sys +import tempfile + +from test_guest_intel_trampolines import PLAIN_SITE, elf_loads, guest_fixture, main_fixture + + +def module_with_symbol(exported): + image = guest_fixture(PLAIN_SITE) + name = b"\0shared#A#B\0" + image[0x800:0x800 + len(name)] = name + struct.pack_into(" ``` -Use `sce_modules/` instead of `sce_module/` if that is the input directory name. Relinker writes converted modules under `app0/` and prints their exact paths. Place app resources in `app0/` separately. Copy the built system libraries from `build/core/libs/libs/*.prx` into `libs/`; use libraries built for the target OS. A custom `--rpath` changes the system library location. +Use `sce_modules/` or `prx/` instead of `sce_module/` if that is the input directory name. Relinker preserves each module's directory under `app0/` and prints its exact path. Place app resources in `app0/` separately. Copy the built system libraries from `build/core/libs/libs/*.prx` into `libs/`; use libraries built for the target OS. A custom `--rpath` changes the system library location. Linux: From aa08977a59db89bd1d8ab2cdf40d2571bc182704 Mon Sep 17 00:00:00 2001 From: Nicolas Abosch <88494836+nicolasabosch@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:27:17 -0300 Subject: [PATCH 069/164] fix(libc): preserve 64-bit long integer conversions --- core/libs/prx/libc/src/Strings.cpp | 9 ++-- core/libs/tests/GuestMath.cpp | 85 ++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libc/src/Strings.cpp b/core/libs/prx/libc/src/Strings.cpp index 263d2050a..5bcad4d2a 100644 --- a/core/libs/prx/libc/src/Strings.cpp +++ b/core/libs/prx/libc/src/Strings.cpp @@ -1,5 +1,6 @@ #include "prx/libc/include/ApplicationHeap.hpp" #include +#include #include #include #include @@ -82,12 +83,12 @@ size_t APS5_VABI strlcpy_nid_postfix(char* dest, const char* src, size_t size) { return srcLen; } -long APS5_VABI strtol_nid_postfix(const char* str, char** endptr, int base) { - return std::strtol(str, endptr, base); +std::int64_t APS5_VABI strtol_nid_postfix(const char* str, char** endptr, int base) { + return std::strtoll(str, endptr, base); } -unsigned long APS5_VABI strtoul_nid_postfix(const char* str, char** endptr, int base) { - return std::strtoul(str, endptr, base); +std::uint64_t APS5_VABI strtoul_nid_postfix(const char* str, char** endptr, int base) { + return std::strtoull(str, endptr, base); } long long APS5_VABI strtoll_nid_postfix(const char* str, char** endptr, int base) { diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 96ccf6af8..7ef93b7ec 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -1,12 +1,15 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include #include +#include #include #include extern "C" { double APS5_VABI atof_nid_postfix(const char*); float APS5_VABI strtof_nid_postfix(const char*, char**); long double APS5_VABI strtold_nid_postfix(const char*, char**); +std::int64_t APS5_VABI strtol_nid_postfix(const char*, char**, int); +std::uint64_t APS5_VABI strtoul_nid_postfix(const char*, char**, int); int* APS5_VABI __error_nid_postfix(); float APS5_VABI fmodf_nid_postfix(float, float); float APS5_VABI asinf_nid_postfix(float); @@ -29,7 +32,89 @@ int APS5_VABI __isnormalf_nid_postfix(float); int APS5_VABI __isinff_nid_postfix(float); } static void Require(bool value) { if (!value) std::abort(); } + +static void CheckIntegerConversions() { + struct SignedCase { + const char* text; + int base; + std::int64_t value; + std::size_t consumed; + int error; + }; + const SignedCase signedCases[] = { + {"-42tail", 10, -42, 3, 0}, + {"2147483648!", 10, INT64_C(2147483648), 10, 0}, + {"-2147483649!", 10, -INT64_C(2147483649), 11, 0}, + {"4294967296!", 10, INT64_C(4294967296), 10, 0}, + {"9223372036854775807!", 10, INT64_MAX, 19, 0}, + {"-9223372036854775808!", 10, INT64_MIN, 20, 0}, + {"9223372036854775808!", 10, INT64_MAX, 19, 34}, + {"-9223372036854775809!", 10, INT64_MIN, 20, 34}, + {"18446744073709551616000!", 10, INT64_MAX, 23, 34}, + {" \t+0x100000000z", 0, INT64_C(4294967296), 14, 0}, + {"-0x8000000000000000!", 0, INT64_MIN, 19, 0}, + {"0x8000000000000000!", 16, INT64_MAX, 18, 34}, + {"0100000000000!", 0, INT64_C(8589934592), 13, 0}, + {"100000000000000000000000000000000!", 2, INT64_C(4294967296), 33, 0}, + {"z!", 36, 35, 1, 0}, + {"", 10, 0, 0, 0}, + {" \t+!", 10, 0, 0, 0}, + {"123!", 10, 123, 3, 0} + }; + for (const auto& test : signedCases) { + char* end = nullptr; + *__error_nid_postfix() = 0; + const auto value = strtol_nid_postfix(test.text, &end, test.base); + if (value != test.value || end != test.text + test.consumed || *__error_nid_postfix() != test.error) { + std::fprintf(stderr, "Guest strtol failed for '%s' in base %d\n", test.text, test.base); + std::abort(); + } + } + struct UnsignedCase { + const char* text; + int base; + std::uint64_t value; + std::size_t consumed; + int error; + }; + const UnsignedCase unsignedCases[] = { + {"4294967296!", 10, UINT64_C(4294967296), 10, 0}, + {"9223372036854775808!", 10, UINT64_C(9223372036854775808), 19, 0}, + {"18446744073709551615!", 10, UINT64_MAX, 20, 0}, + {"18446744073709551616!", 10, UINT64_MAX, 20, 34}, + {"18446744073709551616000!", 10, UINT64_MAX, 23, 34}, + {"-1!", 10, UINT64_MAX, 2, 0}, + {"-4294967296!", 10, UINT64_MAX - UINT64_C(4294967295), 11, 0}, + {"-18446744073709551615!", 10, 1, 21, 0}, + {"-18446744073709551616!", 10, UINT64_MAX, 21, 34}, + {" \t+0xffffffffffffffffz", 0, UINT64_MAX, 21, 0}, + {"0x10000000000000000!", 16, UINT64_MAX, 19, 34}, + {"0100000000000!", 0, UINT64_C(8589934592), 13, 0}, + {"100000000000000000000000000000000!", 2, UINT64_C(4294967296), 33, 0}, + {"z!", 36, 35, 1, 0}, + {"", 10, 0, 0, 0}, + {" \t-!", 10, 0, 0, 0}, + {"123!", 10, 123, 3, 0} + }; + for (const auto& test : unsignedCases) { + char* end = nullptr; + *__error_nid_postfix() = 0; + const auto value = strtoul_nid_postfix(test.text, &end, test.base); + if (value != test.value || end != test.text + test.consumed || *__error_nid_postfix() != test.error) { + std::fprintf(stderr, "Guest strtoul failed for '%s' in base %d\n", test.text, test.base); + std::abort(); + } + } + *__error_nid_postfix() = 13; + Require(strtol_nid_postfix("-4294967296", nullptr, 10) == -INT64_C(4294967296)); + Require(*__error_nid_postfix() == 13); + Require(strtoul_nid_postfix("4294967296", nullptr, 10) == UINT64_C(4294967296)); + Require(*__error_nid_postfix() == 13); + *__error_nid_postfix() = 0; +} + int main() { + CheckIntegerConversions(); Require(atof_nid_postfix(" -12.5tail") == -12.5); char* end = nullptr; const char input[] = "0x1.8p+2 remainder"; From 47b71dbaa47d01a4cf58f136974e39f46ed17f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 04:29:33 +0200 Subject: [PATCH 070/164] fix(shader): return zero when an s_buffer_load offset passes 2^32 The byte address of each dword of s_buffer_load (soffset + imm + 4*i) was computed in 32 bits, so a sum past 2^32 wrapped around and read the start of the buffer. On hardware the sum doesn't wrap: such dwords are out of range and read 0, like any dword at or past num_records. Both the bound V# path and the GPU-computed V# path now check the carry. The new execution test runs s_buffer_load_dwordx4/x16 and s_load_dwordx16 against values captured on hardware. Co-Authored-By: Claude --- .../tests/execution/ScalarWideLoads.cpp | 249 ++++++++++++++++++ .../SpirvMemory/SpirvMemoryInstructions.cpp | 5 +- 2 files changed, 252 insertions(+), 2 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/ScalarWideLoads.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarWideLoads.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarWideLoads.cpp new file mode 100644 index 000000000..c38e112d1 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarWideLoads.cpp @@ -0,0 +1,249 @@ +#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 +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint32_t Results = 96; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x160600ff, 0x00000180, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xbea40400, 0xbea60402, + 0xbea20380, 0xd7600008, 0x02004504, 0xd7600009, 0x02004505, 0xd760000a, 0x02004506, 0xf4280312, + 0x10000000, 0xf4280412, 0x12000014, 0xf4280512, 0x1201fff0, 0xf4280612, 0xfa0001f8, 0xbf8cc07f, + 0xd761000a, 0x0200440c, 0xd761000b, 0x0200440d, 0xd761000c, 0x0200440e, 0xd761000d, 0x0200440f, + 0xd761000e, 0x02004410, 0xd761000f, 0x02004411, 0xd7610010, 0x02004412, 0xd7610011, 0x02004413, + 0xd7610012, 0x02004414, 0xd7610013, 0x02004415, 0xd7610014, 0x02004416, 0xd7610015, 0x02004417, + 0xd7610016, 0x02004418, 0xd7610017, 0x02004419, 0xd7610018, 0x0200441a, 0xd7610019, 0x0200441b, + 0x80228122, 0xbf0aa022, 0xbf85ffce, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, + 0x80011203, 0xe0781030, 0x80011603, 0xbea20380, 0xd7600008, 0x02004504, 0xd7600009, 0x02004505, + 0xd760000a, 0x02004506, 0xf4300312, 0x10000000, 0xbf8cc07f, 0xd761000a, 0x0200440c, 0xd761000b, + 0x0200440d, 0xd761000c, 0x0200440e, 0xd761000d, 0x0200440f, 0xd761000e, 0x02004410, 0xd761000f, + 0x02004411, 0xd7610010, 0x02004412, 0xd7610011, 0x02004413, 0xd7610012, 0x02004414, 0xd7610013, + 0x02004415, 0xd7610014, 0x02004416, 0xd7610015, 0x02004417, 0xd7610016, 0x02004418, 0xd7610017, + 0x02004419, 0xd7610018, 0x0200441a, 0xd7610019, 0x0200441b, 0x80228122, 0xbf0aa022, 0xbf85ffd4, + 0xe0781040, 0x80010a03, 0xe0781050, 0x80010e03, 0xe0781060, 0x80011203, 0xe0781070, 0x80011603, + 0xbea20380, 0xd7600008, 0x02004504, 0xd7600009, 0x02004505, 0xd760000a, 0x02004506, 0xf4300312, + 0x12000014, 0xbf8cc07f, 0xd761000a, 0x0200440c, 0xd761000b, 0x0200440d, 0xd761000c, 0x0200440e, + 0xd761000d, 0x0200440f, 0xd761000e, 0x02004410, 0xd761000f, 0x02004411, 0xd7610010, 0x02004412, + 0xd7610011, 0x02004413, 0xd7610012, 0x02004414, 0xd7610013, 0x02004415, 0xd7610014, 0x02004416, + 0xd7610015, 0x02004417, 0xd7610016, 0x02004418, 0xd7610017, 0x02004419, 0xd7610018, 0x0200441a, + 0xd7610019, 0x0200441b, 0x80228122, 0xbf0aa022, 0xbf85ffd4, 0xe0781080, 0x80010a03, 0xe0781090, + 0x80010e03, 0xe07810a0, 0x80011203, 0xe07810b0, 0x80011603, 0xbea20380, 0xd7600008, 0x02004504, + 0xd7600009, 0x02004505, 0xd760000a, 0x02004506, 0xf4280312, 0x100001f0, 0xf4280412, 0xfa0001fc, + 0xf4280512, 0x100fffff, 0xf4280612, 0x12000100, 0xbf8cc07f, 0xd761000a, 0x0200440c, 0xd761000b, + 0x0200440d, 0xd761000c, 0x0200440e, 0xd761000d, 0x0200440f, 0xd761000e, 0x02004410, 0xd761000f, + 0x02004411, 0xd7610010, 0x02004412, 0xd7610011, 0x02004413, 0xd7610012, 0x02004414, 0xd7610013, + 0x02004415, 0xd7610014, 0x02004416, 0xd7610015, 0x02004417, 0xd7610016, 0x02004418, 0xd7610017, + 0x02004419, 0xd7610018, 0x0200441a, 0xd7610019, 0x0200441b, 0x80228122, 0xbf0aa022, 0xbf85ffce, + 0xe07810c0, 0x80010a03, 0xe07810d0, 0x80010e03, 0xe07810e0, 0x80011203, 0xe07810f0, 0x80011603, + 0x7e580507, 0x882a2c26, 0xbea80424, 0xbeab0327, 0xbea20380, 0xd7600008, 0x02004504, 0xd7600009, + 0x02004505, 0xd760000a, 0x02004506, 0xf4200314, 0x12000014, 0xbf8cc07f, 0xd761000a, 0x0200440c, + 0x80228122, 0xbf0aa022, 0xbf85fff2, 0xe0781100, 0x80010a03, 0xe0781110, 0x80010e03, 0xe0781120, + 0x80011203, 0xe0781130, 0x80011603, 0xbea20380, 0xd7600008, 0x02004504, 0xd7600009, 0x02004505, + 0xd760000a, 0x02004506, 0xf4100312, 0xfa000026, 0xbf8cc07f, 0xd761000a, 0x0200440c, 0xd761000b, + 0x0200440d, 0xd761000c, 0x0200440e, 0xd761000d, 0x0200440f, 0xd761000e, 0x02004410, 0xd761000f, + 0x02004411, 0xd7610010, 0x02004412, 0xd7610011, 0x02004413, 0xd7610012, 0x02004414, 0xd7610013, + 0x02004415, 0xd7610014, 0x02004416, 0xd7610015, 0x02004417, 0xd7610016, 0x02004418, 0xd7610017, + 0x02004419, 0xd7610018, 0x0200441a, 0xd7610019, 0x0200441b, 0x80228122, 0xbf0aa022, 0xbf85ffd4, + 0xe0781140, 0x80010a03, 0xe0781150, 0x80010e03, 0xe0781160, 0x80011203, 0xe0781170, 0x80011603, + 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000240u, 0x00000000u, 0x00000000u}, + {0x00000004u, 0x00000201u, 0x00000004u, 0x8b2b5909u}, + {0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu}, + {0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du}, + {0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u}, + {0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u}, + {0x00000010u, 0x000001fdu, 0x00000040u, 0x830b46acu}, + {0x000001c0u, 0x00000204u, 0x000001a0u, 0xc01ff328u}, + {0x000001c4u, 0x000001ffu, 0x000001a1u, 0x38487fe1u}, + {0x000001c1u, 0x00000010u, 0x000001a2u, 0xd1999ef1u}, + {0x000001d0u, 0x000001c4u, 0x000001a3u, 0x692734d6u}, + {0x000001ecu, 0xffffffe1u, 0x0000019cu, 0xf568863bu}, + {0x000001f0u, 0x00000006u, 0x00000010u, 0xc93eb5e4u}, + {0x000001f3u, 0xfffffff0u, 0x00000014u, 0x78f588b9u}, + {0x000001f4u, 0x7ffffffcu, 0x00000020u, 0x84052a95u}, + {0x000001f8u, 0xffffffecu, 0x00000022u, 0x24c83161u}, + {0x000001fcu, 0x000001c0u, 0x00000088u, 0xc0983b6du}, + {0x000001fdu, 0x000001f4u, 0x0000008cu, 0x4e9d2c57u}, + {0x000001ffu, 0x00000002u, 0x0000008du, 0xc1b9aecfu}, + {0x00000200u, 0x00000004u, 0x00000100u, 0x4c9f5e6du}, + {0x00000201u, 0x00000000u, 0x00000104u, 0x964bc1d0u}, + {0x00000204u, 0x000001f3u, 0x00000108u, 0xb53d4566u}, + {0x00000240u, 0x000001f0u, 0x0000010bu, 0xa5293a5bu}, + {0xffffffecu, 0x000001e0u, 0x00000150u, 0x78adc6ecu}, + {0xfffffff0u, 0x000001fcu, 0x00000180u, 0x65b49589u}, + {0xfffffffcu, 0x00000001u, 0x00000184u, 0xd22a8a62u}, + {0xffffffe1u, 0x000001c1u, 0x00000030u, 0x9e321a50u}, + {0x80000000u, 0x00000100u, 0x00000034u, 0xd933a5c9u}, + {0x7ffffffcu, 0x80000000u, 0x00000035u, 0xc6f918bcu}, + {0x00000100u, 0x00000200u, 0x0000005cu, 0xe5308181u}, + {0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u}, + {0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u} +}; +constexpr std::uint32_t Expected[32][96] = { + {0x00000000u, 0x00000240u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000240u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000240u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000240u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000240u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000240u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000240u, 0x00000000u, 0x00000000u, 0x00000035u, 0xc6f918bcu, 0x00000100u, 0x00000200u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000240u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000035u, 0xc6f918bcu, 0x00000100u, 0x00000200u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000035u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000240u, 0x00000000u, 0x00000000u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000240u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001ecu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000240u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000240u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000004u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x7ffffffcu, 0x80000000u, 0x00000035u, 0xc6f918bcu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x7ffffffcu, 0x80000000u, 0x00000035u, 0xc6f918bcu, 0x00000100u, 0x00000200u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x80000000u, 0x00000035u, 0xc6f918bcu, 0x00000100u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000035u, 0xc6f918bcu, 0x00000100u, 0x00000200u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x7ffffffcu, 0x80000000u, 0x00000035u, 0xc6f918bcu, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x7ffffffcu, 0x80000000u, 0x00000035u, 0xc6f918bcu, 0x00000100u, 0x00000200u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001fdu, 0x000001f4u, 0x0000008cu, 0x4e9d2c57u, 0xfffffffcu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000100u, 0x00000200u, 0x0000005cu, 0xe5308181u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x000001ecu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000100u, 0x00000200u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000005cu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001c0u, 0x00000088u, 0xc0983b6du, 0x000001fdu, 0x00000004u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000200u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000200u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000200u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001fcu, 0x000001c0u, 0x00000088u, 0xc0983b6du, 0x00000201u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001c0u, 0x00000088u, 0xc0983b6du, 0x000001fdu, 0x00000004u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001fcu, 0x000001c0u, 0x00000088u, 0xc0983b6du, 0x00000201u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001fcu, 0x000001c0u, 0x00000088u, 0xc0983b6du, 0x00000201u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000200u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000200u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000200u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001f4u, 0x0000008cu, 0x4e9d2c57u, 0x000001ffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001f4u, 0x0000008cu, 0x4e9d2c57u, 0x000001ffu, 0x00000002u, 0x0000008du, 0xc1b9aecfu, 0x00000200u, 0x00000004u, 0x00000100u, 0x4c9f5e6du, 0x00000201u, 0x00000000u, 0x00000104u, 0x964bc1d0u, 0x00000204u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001f4u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x000001fcu, 0x000001c0u, 0x00000088u, 0xc0983b6du, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x000001fcu, 0x000001c0u, 0x00000088u, 0xc0983b6du, 0x000001fdu, 0x000001f4u, 0x0000008cu, 0x4e9d2c57u, 0x000001ffu, 0x00000002u, 0x0000008du, 0xc1b9aecfu, 0x00000200u, 0x00000004u, 0x00000100u, 0x4c9f5e6du, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000100u, 0x00000034u, 0xd933a5c9u, 0x7ffffffcu, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000100u, 0x00000034u, 0xd933a5c9u, 0x7ffffffcu, 0x80000000u, 0x00000035u, 0xc6f918bcu, 0x00000100u, 0x00000200u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001fcu, 0x000001c0u, 0x00000088u, 0xc0983b6du, 0x00000201u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u} +}; +constexpr const char* Names[96] = {"buffer_load_dwordx4 s8 dword 0", "buffer_load_dwordx4 s8 dword 1", "buffer_load_dwordx4 s8 dword 2", "buffer_load_dwordx4 s8 dword 3", "buffer_load_dwordx4 s9 offset 0x14 dword 0", "buffer_load_dwordx4 s9 offset 0x14 dword 1", "buffer_load_dwordx4 s9 offset 0x14 dword 2", "buffer_load_dwordx4 s9 offset 0x14 dword 3", "buffer_load_dwordx4 s9 offset 0x1fff0 dword 0", "buffer_load_dwordx4 s9 offset 0x1fff0 dword 1", "buffer_load_dwordx4 s9 offset 0x1fff0 dword 2", "buffer_load_dwordx4 s9 offset 0x1fff0 dword 3", "buffer_load_dwordx4 imm 0x1f8 dword 0", "buffer_load_dwordx4 imm 0x1f8 dword 1", "buffer_load_dwordx4 imm 0x1f8 dword 2", "buffer_load_dwordx4 imm 0x1f8 dword 3", "buffer_load_dwordx16 s8 dword 0", "buffer_load_dwordx16 s8 dword 1", "buffer_load_dwordx16 s8 dword 2", "buffer_load_dwordx16 s8 dword 3", "buffer_load_dwordx16 s8 dword 4", "buffer_load_dwordx16 s8 dword 5", "buffer_load_dwordx16 s8 dword 6", "buffer_load_dwordx16 s8 dword 7", "buffer_load_dwordx16 s8 dword 8", "buffer_load_dwordx16 s8 dword 9", "buffer_load_dwordx16 s8 dword 10", "buffer_load_dwordx16 s8 dword 11", "buffer_load_dwordx16 s8 dword 12", "buffer_load_dwordx16 s8 dword 13", "buffer_load_dwordx16 s8 dword 14", "buffer_load_dwordx16 s8 dword 15", "buffer_load_dwordx16 s9 offset 0x14 dword 0", "buffer_load_dwordx16 s9 offset 0x14 dword 1", "buffer_load_dwordx16 s9 offset 0x14 dword 2", "buffer_load_dwordx16 s9 offset 0x14 dword 3", "buffer_load_dwordx16 s9 offset 0x14 dword 4", "buffer_load_dwordx16 s9 offset 0x14 dword 5", "buffer_load_dwordx16 s9 offset 0x14 dword 6", "buffer_load_dwordx16 s9 offset 0x14 dword 7", "buffer_load_dwordx16 s9 offset 0x14 dword 8", "buffer_load_dwordx16 s9 offset 0x14 dword 9", "buffer_load_dwordx16 s9 offset 0x14 dword 10", "buffer_load_dwordx16 s9 offset 0x14 dword 11", "buffer_load_dwordx16 s9 offset 0x14 dword 12", "buffer_load_dwordx16 s9 offset 0x14 dword 13", "buffer_load_dwordx16 s9 offset 0x14 dword 14", "buffer_load_dwordx16 s9 offset 0x14 dword 15", "buffer_load_dwordx4 s8 offset 0x1f0 dword 0", "buffer_load_dwordx4 s8 offset 0x1f0 dword 1", "buffer_load_dwordx4 s8 offset 0x1f0 dword 2", "buffer_load_dwordx4 s8 offset 0x1f0 dword 3", "buffer_load_dwordx4 imm 0x1fc dword 0", "buffer_load_dwordx4 imm 0x1fc dword 1", "buffer_load_dwordx4 imm 0x1fc dword 2", "buffer_load_dwordx4 imm 0x1fc dword 3", "buffer_load_dwordx4 s8 offset 0xfffff dword 0", "buffer_load_dwordx4 s8 offset 0xfffff dword 1", "buffer_load_dwordx4 s8 offset 0xfffff dword 2", "buffer_load_dwordx4 s8 offset 0xfffff dword 3", "buffer_load_dwordx4 s9 offset 0x100 dword 0", "buffer_load_dwordx4 s9 offset 0x100 dword 1", "buffer_load_dwordx4 s9 offset 0x100 dword 2", "buffer_load_dwordx4 s9 offset 0x100 dword 3", "buffer_load_dword s9 offset 0x14 gpu descriptor", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "load_dwordx16 imm 0x26 dword 0", "load_dwordx16 imm 0x26 dword 1", "load_dwordx16 imm 0x26 dword 2", "load_dwordx16 imm 0x26 dword 3", "load_dwordx16 imm 0x26 dword 4", "load_dwordx16 imm 0x26 dword 5", "load_dwordx16 imm 0x26 dword 6", "load_dwordx16 imm 0x26 dword 7", "load_dwordx16 imm 0x26 dword 8", "load_dwordx16 imm 0x26 dword 9", "load_dwordx16 imm 0x26 dword 10", "load_dwordx16 imm 0x26 dword 11", "load_dwordx16 imm 0x26 dword 12", "load_dwordx16 imm 0x26 dword 13", "load_dwordx16 imm 0x26 dword 14", "load_dwordx16 imm 0x26 dword 15"}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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, "scalar wide loads: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~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::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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("scalar wide loads: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + std::memcpy(guest.Data(), Input.data(), Input.size() * 4u); + const auto input = BufferDescriptor(guest.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::span code(Code); + const std::span guestBytes(guest.Data(), Input.size() * 4u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}, {reinterpret_cast(guest.Data()), std::as_bytes(guestBytes)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 96; ++i) { + if (Names[i] != nullptr) Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("scalar wide loads tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp index 090c4df4c..16041f517 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp @@ -1234,6 +1234,7 @@ void EmitReadConstBuffer(SpirvValueEmitContext& ctx, const IrValue& inst) { } auto& state = ctx.state; const auto address = Binary(state, spv::OpIAdd, TypeU32(state), ctx.Arg(inst, 1), ConstantU32(state, mem.offset)); + const auto noCarry = Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), address, ctx.Arg(inst, 1)); if (mem.gpuDescriptor) { const IrValue* handle = inst.Argument(0)->Resolve(); if (handle->Opcode() != IrOpcode::GetBufferResource || handle->ArgumentCount() != 4u) { @@ -1245,7 +1246,7 @@ void EmitReadConstBuffer(SpirvValueEmitContext& ctx, const IrValue& inst) { const auto stride = EmitBitFieldUExtract(state, word1, ConstantU32(state, 16u), ConstantU32(state, 14u)); const auto size = Select(state, u32, Binary(state, spv::OpIEqual, TypeBool(state), stride, ConstantU32(state, 0u)), records, Binary(state, spv::OpIMul, u32, stride, records)); const auto byte = Binary(state, spv::OpBitwiseAnd, u32, address, ConstantU32(state, ~3u)); - const auto inBounds = Binary(state, spv::OpULessThan, TypeBool(state), byte, size); + const auto inBounds = AndCondition(state, noCarry, Binary(state, spv::OpULessThan, TypeBool(state), byte, size)); const auto base = DeviceAddressFromWords(state, ctx.Arg(*handle, 0), EmitBitFieldUExtract(state, word1, ConstantU32(state, 0u), ConstantU32(state, 16u))); const auto guest = Binary(state, spv::OpIAdd, TypeScalarU64(state), base, Unary(state, spv::OpUConvert, TypeScalarU64(state), byte)); ctx.Define(inst, EmitValueOrZeroIfCondition(state, inBounds, [&] { return EmitBdaRead(ctx, inst, guest, 32u); })); @@ -1254,7 +1255,7 @@ void EmitReadConstBuffer(SpirvValueEmitContext& ctx, const IrValue& inst) { const auto rawIndex = Binary(state, spv::OpShiftRightLogical, TypeU32(state), address, ConstantU32(state, 2u)); const auto access = PrepareMemoryResourceAccess(state, mem); const auto element = EmitMemoryElementIndex(state, access, rawIndex); - const auto inBounds = EmitMemoryElementInBounds(state, access, element); + const auto inBounds = AndCondition(state, noCarry, EmitMemoryElementInBounds(state, access, element)); ctx.Define(inst, EmitValueOrZeroIfCondition(state, inBounds, [&]() { const auto value = state.module.AllocateId(); state.module.AddFunction(spv::OpLoad, TypeU32(state), value, EmitMemoryElementPointer(state, access, element)); From c97030cc7bc90d4197675579f31d8753f560af69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 04:34:00 +0200 Subject: [PATCH 071/164] test(shader): execute ds_swizzle_b32 in all three offset modes Co-Authored-By: Claude --- .../tests/execution/DsSwizzle.cpp | 289 ++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/DsSwizzle.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DsSwizzle.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DsSwizzle.cpp new file mode 100644 index 000000000..4ef9a3949 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DsSwizzle.cpp @@ -0,0 +1,289 @@ +#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 MaxThreads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Checked = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Wave32Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0x7e3202ff, 0xdeaddead, 0xd8d4001f, 0x0a000004, 0xd8d4201f, + 0x0b000004, 0xd8d400b8, 0x0c000004, 0xd8d47c1f, 0x0d000004, 0xd8d42493, 0x0e000004, 0xd8d4804b, + 0x0f000004, 0xd8d4bf4b, 0x10000004, 0xd8d4c020, 0x11000004, 0xd8d4c460, 0x12000004, 0xd8d4c3e0, + 0x13000004, 0xd8d4c4ac, 0x14000004, 0xd8d4d8e0, 0x15000004, 0xbefe03ff, 0x33cc0ff3, 0xd8d4201f, + 0x16000004, 0xd8d4804b, 0x17000004, 0xd8d4c460, 0x18000004, 0xd8d400b8, 0x19000004, 0xbf8cc07f, + 0xbefe03c1, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, + 0x80011603, 0xbf810000, +}; + +alignas(256) constexpr std::array Wave64Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0x7e3202ff, 0xdeaddead, 0xd8d4001f, 0x0a000004, 0xd8d4201f, + 0x0b000004, 0xd8d400b8, 0x0c000004, 0xd8d47c1f, 0x0d000004, 0xd8d42493, 0x0e000004, 0xd8d4804b, + 0x0f000004, 0xd8d4bf4b, 0x10000004, 0xd8d4c020, 0x11000004, 0xd8d4c460, 0x12000004, 0xd8d4c3e0, + 0x13000004, 0xd8d4c4ac, 0x14000004, 0xd8d4d8e0, 0x15000004, 0xbefe03ff, 0x33cc0ff3, 0xbeff03ff, + 0xc3a5960f, 0xd8d4201f, 0x16000004, 0xd8d4804b, 0x17000004, 0xd8d4c460, 0x18000004, 0xd8d400b8, + 0x19000004, 0xbf8cc07f, 0xbefe04c1, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, + 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[64][4] = { + {0x1e7ea419u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x51c9bc70u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80a4df5au, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xf38b2ffcu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x8306d03bu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xa5aec797u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xdc28ff90u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xf3f49249u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x1a466884u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xe255accbu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x39292d22u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xe5121482u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x99dd251du, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x9f199504u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x8e7aa6e9u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x6bad6be2u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc88b2875u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x9293de8fu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x8c3d5f16u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xd7a7a3ccu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xbb049a79u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc6cd75e9u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc4a334bfu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x7dabe929u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc0433cbdu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc5e818fau, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x96263ae6u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x70eb9a0au, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3d6c51e3u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00a61f93u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x9d3c7decu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x14aa4e71u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x1c593af5u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x49889310u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xd11745adu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x1919e93au, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x731c9452u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x02f0ee99u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xd0944996u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xe4163207u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xaf67d461u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x7d836e77u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xadec6f11u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x5071950eu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x35d14880u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x65b00a2du, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x406329bcu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x59001ac9u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xfadb8908u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xffb88309u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xede7bd0cu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x5b4c8012u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xce1d62e0u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x60581ccau, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xffe976abu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xbef7e268u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x837a7935u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xa341738cu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x1389b372u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xb91dddd9u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x57241955u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x16f44881u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x8eb5140fu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x89be9c1cu, 0x00000000u, 0x00000000u, 0x00000000u} +}; + +constexpr std::uint32_t Expected32[32][16] = { + {0x1e7ea419u, 0x1a466884u, 0xa5aec797u, 0x14aa4e71u, 0x9f199504u, 0xf38b2ffcu, 0xf38b2ffcu, 0x51c9bc70u, 0x00a61f93u, 0x14aa4e71u, 0xd7a7a3ccu, 0xf3f49249u, 0x1a466884u, 0x00000000u, 0x00a61f93u, 0xa5aec797u}, + {0x51c9bc70u, 0xe255accbu, 0xa5aec797u, 0x9d3c7decu, 0x99dd251du, 0x80a4df5au, 0x80a4df5au, 0x80a4df5au, 0x9d3c7decu, 0x1e7ea419u, 0xc88b2875u, 0x1a466884u, 0xe255accbu, 0x00000000u, 0x00000000u, 0xa5aec797u}, + {0x80a4df5au, 0x39292d22u, 0xa5aec797u, 0x00a61f93u, 0x6bad6be2u, 0x1e7ea419u, 0x1e7ea419u, 0xf38b2ffcu, 0x14aa4e71u, 0x51c9bc70u, 0x9293de8fu, 0xe255accbu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xf38b2ffcu, 0xe5121482u, 0xa5aec797u, 0x3d6c51e3u, 0x8e7aa6e9u, 0x51c9bc70u, 0x51c9bc70u, 0x8306d03bu, 0x1e7ea419u, 0x80a4df5au, 0x8c3d5f16u, 0x39292d22u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8306d03bu, 0x99dd251du, 0xa5aec797u, 0x70eb9a0au, 0x9f199504u, 0xf3f49249u, 0xf3f49249u, 0xa5aec797u, 0x51c9bc70u, 0xf38b2ffcu, 0x7dabe929u, 0xe5121482u, 0x00000000u, 0xf3f49249u, 0x51c9bc70u, 0xa5aec797u}, + {0xa5aec797u, 0x9f199504u, 0xa5aec797u, 0x96263ae6u, 0x99dd251du, 0xdc28ff90u, 0xdc28ff90u, 0xdc28ff90u, 0x80a4df5au, 0x8306d03bu, 0x8306d03bu, 0x99dd251du, 0x00000000u, 0xdc28ff90u, 0x00000000u, 0xa5aec797u}, + {0xdc28ff90u, 0x8e7aa6e9u, 0xa5aec797u, 0xc5e818fau, 0x6bad6be2u, 0x8306d03bu, 0x8306d03bu, 0xf3f49249u, 0xf38b2ffcu, 0xa5aec797u, 0xa5aec797u, 0x9f199504u, 0x00000000u, 0x8306d03bu, 0x00000000u, 0xa5aec797u}, + {0xf3f49249u, 0x6bad6be2u, 0xa5aec797u, 0xc0433cbdu, 0x8e7aa6e9u, 0xa5aec797u, 0xa5aec797u, 0x1a466884u, 0x8306d03bu, 0xdc28ff90u, 0xdc28ff90u, 0x8e7aa6e9u, 0x00000000u, 0xa5aec797u, 0x8306d03bu, 0xa5aec797u}, + {0x1a466884u, 0x1e7ea419u, 0x9f199504u, 0x7dabe929u, 0x9f199504u, 0xe5121482u, 0xe5121482u, 0xe255accbu, 0xa5aec797u, 0xf3f49249u, 0xe5121482u, 0x6bad6be2u, 0x1e7ea419u, 0xe5121482u, 0xa5aec797u, 0x00000000u}, + {0xe255accbu, 0x51c9bc70u, 0x9f199504u, 0xc4a334bfu, 0x99dd251du, 0x39292d22u, 0x39292d22u, 0x39292d22u, 0xdc28ff90u, 0x1a466884u, 0x1a466884u, 0xc88b2875u, 0x51c9bc70u, 0x39292d22u, 0xdc28ff90u, 0x00000000u}, + {0x39292d22u, 0x80a4df5au, 0x9f199504u, 0xc6cd75e9u, 0x6bad6be2u, 0x1a466884u, 0x1a466884u, 0xe5121482u, 0xf3f49249u, 0xe255accbu, 0xe255accbu, 0x9293de8fu, 0x00000000u, 0x1a466884u, 0xf3f49249u, 0x00000000u}, + {0xe5121482u, 0xf38b2ffcu, 0x9f199504u, 0xbb049a79u, 0x8e7aa6e9u, 0xe255accbu, 0xe255accbu, 0x99dd251du, 0x1a466884u, 0x39292d22u, 0x39292d22u, 0x8c3d5f16u, 0x00000000u, 0xe255accbu, 0x1a466884u, 0x00000000u}, + {0x99dd251du, 0x8306d03bu, 0x9f199504u, 0xd7a7a3ccu, 0x9f199504u, 0x6bad6be2u, 0x6bad6be2u, 0x9f199504u, 0xe255accbu, 0xe5121482u, 0x6bad6be2u, 0xd7a7a3ccu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9f199504u, 0xa5aec797u, 0x9f199504u, 0x8c3d5f16u, 0x99dd251du, 0x8e7aa6e9u, 0x8e7aa6e9u, 0x8e7aa6e9u, 0x39292d22u, 0x99dd251du, 0x99dd251du, 0xbb049a79u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8e7aa6e9u, 0xdc28ff90u, 0x9f199504u, 0x9293de8fu, 0x6bad6be2u, 0x99dd251du, 0x99dd251du, 0x6bad6be2u, 0xe5121482u, 0x9f199504u, 0x9f199504u, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x6bad6be2u, 0xf3f49249u, 0x9f199504u, 0xc88b2875u, 0x8e7aa6e9u, 0x9f199504u, 0x9f199504u, 0xc88b2875u, 0x99dd251du, 0x8e7aa6e9u, 0x8e7aa6e9u, 0xc4a334bfu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc88b2875u, 0xc0433cbdu, 0xc6cd75e9u, 0x6bad6be2u, 0x00a61f93u, 0xd7a7a3ccu, 0xd7a7a3ccu, 0x9293de8fu, 0x9f199504u, 0x6bad6be2u, 0xf38b2ffcu, 0x7dabe929u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9293de8fu, 0xc5e818fau, 0xc6cd75e9u, 0x8e7aa6e9u, 0x3d6c51e3u, 0x8c3d5f16u, 0x8c3d5f16u, 0x8c3d5f16u, 0x8e7aa6e9u, 0xc88b2875u, 0x1e7ea419u, 0xc0433cbdu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8c3d5f16u, 0x96263ae6u, 0xc6cd75e9u, 0x9f199504u, 0x14aa4e71u, 0xc88b2875u, 0xc88b2875u, 0xd7a7a3ccu, 0x6bad6be2u, 0x9293de8fu, 0x51c9bc70u, 0xc5e818fau, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xd7a7a3ccu, 0x70eb9a0au, 0xc6cd75e9u, 0x99dd251du, 0x9d3c7decu, 0x9293de8fu, 0x9293de8fu, 0xbb049a79u, 0xc88b2875u, 0x8c3d5f16u, 0x80a4df5au, 0x96263ae6u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xbb049a79u, 0x3d6c51e3u, 0xc6cd75e9u, 0xe5121482u, 0x00a61f93u, 0x7dabe929u, 0x7dabe929u, 0xc6cd75e9u, 0x9293de8fu, 0xd7a7a3ccu, 0xf3f49249u, 0x70eb9a0au, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc6cd75e9u, 0x00a61f93u, 0xc6cd75e9u, 0x39292d22u, 0x3d6c51e3u, 0xc4a334bfu, 0xc4a334bfu, 0xc4a334bfu, 0x8c3d5f16u, 0xbb049a79u, 0xbb049a79u, 0x3d6c51e3u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc4a334bfu, 0x9d3c7decu, 0xc6cd75e9u, 0xe255accbu, 0x14aa4e71u, 0xbb049a79u, 0xbb049a79u, 0x7dabe929u, 0xd7a7a3ccu, 0xc6cd75e9u, 0xc6cd75e9u, 0x00a61f93u, 0x00000000u, 0x00000000u, 0xd7a7a3ccu, 0x00000000u}, + {0x7dabe929u, 0x14aa4e71u, 0xc6cd75e9u, 0x1a466884u, 0x9d3c7decu, 0xc6cd75e9u, 0xc6cd75e9u, 0xc0433cbdu, 0xbb049a79u, 0xc4a334bfu, 0xc4a334bfu, 0x9d3c7decu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc0433cbdu, 0xc88b2875u, 0x00a61f93u, 0xf3f49249u, 0x00a61f93u, 0x70eb9a0au, 0x70eb9a0au, 0xc5e818fau, 0xc6cd75e9u, 0x7dabe929u, 0x70eb9a0au, 0x14aa4e71u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00a61f93u}, + {0xc5e818fau, 0x9293de8fu, 0x00a61f93u, 0xdc28ff90u, 0x3d6c51e3u, 0x96263ae6u, 0x96263ae6u, 0x96263ae6u, 0xc4a334bfu, 0xc0433cbdu, 0xc0433cbdu, 0x1e7ea419u, 0x00000000u, 0x00000000u, 0xc4a334bfu, 0x00a61f93u}, + {0x96263ae6u, 0x8c3d5f16u, 0x00a61f93u, 0xa5aec797u, 0x14aa4e71u, 0xc0433cbdu, 0xc0433cbdu, 0x70eb9a0au, 0x7dabe929u, 0xc5e818fau, 0xc5e818fau, 0x51c9bc70u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x70eb9a0au, 0xd7a7a3ccu, 0x00a61f93u, 0x8306d03bu, 0x9d3c7decu, 0xc5e818fau, 0xc5e818fau, 0x3d6c51e3u, 0xc0433cbdu, 0x96263ae6u, 0x96263ae6u, 0x80a4df5au, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x3d6c51e3u, 0xbb049a79u, 0x00a61f93u, 0xf38b2ffcu, 0x00a61f93u, 0x14aa4e71u, 0x14aa4e71u, 0x00a61f93u, 0xc5e818fau, 0x70eb9a0au, 0x14aa4e71u, 0xf38b2ffcu, 0x00000000u, 0x00000000u, 0xc5e818fau, 0x00a61f93u}, + {0x00a61f93u, 0xc6cd75e9u, 0x00a61f93u, 0x80a4df5au, 0x3d6c51e3u, 0x9d3c7decu, 0x9d3c7decu, 0x9d3c7decu, 0x96263ae6u, 0x3d6c51e3u, 0x3d6c51e3u, 0x8306d03bu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00a61f93u}, + {0x9d3c7decu, 0xc4a334bfu, 0x00a61f93u, 0x51c9bc70u, 0x14aa4e71u, 0x3d6c51e3u, 0x3d6c51e3u, 0x14aa4e71u, 0x70eb9a0au, 0x00a61f93u, 0x00a61f93u, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x14aa4e71u, 0x7dabe929u, 0x00a61f93u, 0x1e7ea419u, 0x9d3c7decu, 0x00a61f93u, 0x00a61f93u, 0x1e7ea419u, 0x3d6c51e3u, 0x9d3c7decu, 0x9d3c7decu, 0xdc28ff90u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu} +}; +constexpr std::uint32_t Expected64[64][16] = { + {0x1e7ea419u, 0x1a466884u, 0xa5aec797u, 0x14aa4e71u, 0x9f199504u, 0xf38b2ffcu, 0xf38b2ffcu, 0x51c9bc70u, 0x00a61f93u, 0x14aa4e71u, 0xd7a7a3ccu, 0xf3f49249u, 0x1a466884u, 0x00000000u, 0x00a61f93u, 0xa5aec797u}, + {0x51c9bc70u, 0xe255accbu, 0xa5aec797u, 0x9d3c7decu, 0x99dd251du, 0x80a4df5au, 0x80a4df5au, 0x80a4df5au, 0x9d3c7decu, 0x1e7ea419u, 0xc88b2875u, 0x1a466884u, 0xe255accbu, 0x00000000u, 0x00000000u, 0xa5aec797u}, + {0x80a4df5au, 0x39292d22u, 0xa5aec797u, 0x00a61f93u, 0x6bad6be2u, 0x1e7ea419u, 0x1e7ea419u, 0xf38b2ffcu, 0x14aa4e71u, 0x51c9bc70u, 0x9293de8fu, 0xe255accbu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xf38b2ffcu, 0xe5121482u, 0xa5aec797u, 0x3d6c51e3u, 0x8e7aa6e9u, 0x51c9bc70u, 0x51c9bc70u, 0x8306d03bu, 0x1e7ea419u, 0x80a4df5au, 0x8c3d5f16u, 0x39292d22u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8306d03bu, 0x99dd251du, 0xa5aec797u, 0x70eb9a0au, 0x9f199504u, 0xf3f49249u, 0xf3f49249u, 0xa5aec797u, 0x51c9bc70u, 0xf38b2ffcu, 0x7dabe929u, 0xe5121482u, 0x00000000u, 0xf3f49249u, 0x51c9bc70u, 0xa5aec797u}, + {0xa5aec797u, 0x9f199504u, 0xa5aec797u, 0x96263ae6u, 0x99dd251du, 0xdc28ff90u, 0xdc28ff90u, 0xdc28ff90u, 0x80a4df5au, 0x8306d03bu, 0x8306d03bu, 0x99dd251du, 0x00000000u, 0xdc28ff90u, 0x00000000u, 0xa5aec797u}, + {0xdc28ff90u, 0x8e7aa6e9u, 0xa5aec797u, 0xc5e818fau, 0x6bad6be2u, 0x8306d03bu, 0x8306d03bu, 0xf3f49249u, 0xf38b2ffcu, 0xa5aec797u, 0xa5aec797u, 0x9f199504u, 0x00000000u, 0x8306d03bu, 0x00000000u, 0xa5aec797u}, + {0xf3f49249u, 0x6bad6be2u, 0xa5aec797u, 0xc0433cbdu, 0x8e7aa6e9u, 0xa5aec797u, 0xa5aec797u, 0x1a466884u, 0x8306d03bu, 0xdc28ff90u, 0xdc28ff90u, 0x8e7aa6e9u, 0x00000000u, 0xa5aec797u, 0x8306d03bu, 0xa5aec797u}, + {0x1a466884u, 0x1e7ea419u, 0x9f199504u, 0x7dabe929u, 0x9f199504u, 0xe5121482u, 0xe5121482u, 0xe255accbu, 0xa5aec797u, 0xf3f49249u, 0xe5121482u, 0x6bad6be2u, 0x1e7ea419u, 0xe5121482u, 0xa5aec797u, 0x00000000u}, + {0xe255accbu, 0x51c9bc70u, 0x9f199504u, 0xc4a334bfu, 0x99dd251du, 0x39292d22u, 0x39292d22u, 0x39292d22u, 0xdc28ff90u, 0x1a466884u, 0x1a466884u, 0xc88b2875u, 0x51c9bc70u, 0x39292d22u, 0xdc28ff90u, 0x00000000u}, + {0x39292d22u, 0x80a4df5au, 0x9f199504u, 0xc6cd75e9u, 0x6bad6be2u, 0x1a466884u, 0x1a466884u, 0xe5121482u, 0xf3f49249u, 0xe255accbu, 0xe255accbu, 0x9293de8fu, 0x00000000u, 0x1a466884u, 0xf3f49249u, 0x00000000u}, + {0xe5121482u, 0xf38b2ffcu, 0x9f199504u, 0xbb049a79u, 0x8e7aa6e9u, 0xe255accbu, 0xe255accbu, 0x99dd251du, 0x1a466884u, 0x39292d22u, 0x39292d22u, 0x8c3d5f16u, 0x00000000u, 0xe255accbu, 0x1a466884u, 0x00000000u}, + {0x99dd251du, 0x8306d03bu, 0x9f199504u, 0xd7a7a3ccu, 0x9f199504u, 0x6bad6be2u, 0x6bad6be2u, 0x9f199504u, 0xe255accbu, 0xe5121482u, 0x6bad6be2u, 0xd7a7a3ccu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9f199504u, 0xa5aec797u, 0x9f199504u, 0x8c3d5f16u, 0x99dd251du, 0x8e7aa6e9u, 0x8e7aa6e9u, 0x8e7aa6e9u, 0x39292d22u, 0x99dd251du, 0x99dd251du, 0xbb049a79u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8e7aa6e9u, 0xdc28ff90u, 0x9f199504u, 0x9293de8fu, 0x6bad6be2u, 0x99dd251du, 0x99dd251du, 0x6bad6be2u, 0xe5121482u, 0x9f199504u, 0x9f199504u, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x6bad6be2u, 0xf3f49249u, 0x9f199504u, 0xc88b2875u, 0x8e7aa6e9u, 0x9f199504u, 0x9f199504u, 0xc88b2875u, 0x99dd251du, 0x8e7aa6e9u, 0x8e7aa6e9u, 0xc4a334bfu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc88b2875u, 0xc0433cbdu, 0xc6cd75e9u, 0x6bad6be2u, 0x00a61f93u, 0xd7a7a3ccu, 0xd7a7a3ccu, 0x9293de8fu, 0x9f199504u, 0x6bad6be2u, 0xf38b2ffcu, 0x7dabe929u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9293de8fu, 0xc5e818fau, 0xc6cd75e9u, 0x8e7aa6e9u, 0x3d6c51e3u, 0x8c3d5f16u, 0x8c3d5f16u, 0x8c3d5f16u, 0x8e7aa6e9u, 0xc88b2875u, 0x1e7ea419u, 0xc0433cbdu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8c3d5f16u, 0x96263ae6u, 0xc6cd75e9u, 0x9f199504u, 0x14aa4e71u, 0xc88b2875u, 0xc88b2875u, 0xd7a7a3ccu, 0x6bad6be2u, 0x9293de8fu, 0x51c9bc70u, 0xc5e818fau, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xd7a7a3ccu, 0x70eb9a0au, 0xc6cd75e9u, 0x99dd251du, 0x9d3c7decu, 0x9293de8fu, 0x9293de8fu, 0xbb049a79u, 0xc88b2875u, 0x8c3d5f16u, 0x80a4df5au, 0x96263ae6u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xbb049a79u, 0x3d6c51e3u, 0xc6cd75e9u, 0xe5121482u, 0x00a61f93u, 0x7dabe929u, 0x7dabe929u, 0xc6cd75e9u, 0x9293de8fu, 0xd7a7a3ccu, 0xf3f49249u, 0x70eb9a0au, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc6cd75e9u, 0x00a61f93u, 0xc6cd75e9u, 0x39292d22u, 0x3d6c51e3u, 0xc4a334bfu, 0xc4a334bfu, 0xc4a334bfu, 0x8c3d5f16u, 0xbb049a79u, 0xbb049a79u, 0x3d6c51e3u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc4a334bfu, 0x9d3c7decu, 0xc6cd75e9u, 0xe255accbu, 0x14aa4e71u, 0xbb049a79u, 0xbb049a79u, 0x7dabe929u, 0xd7a7a3ccu, 0xc6cd75e9u, 0xc6cd75e9u, 0x00a61f93u, 0x00000000u, 0x00000000u, 0xd7a7a3ccu, 0x00000000u}, + {0x7dabe929u, 0x14aa4e71u, 0xc6cd75e9u, 0x1a466884u, 0x9d3c7decu, 0xc6cd75e9u, 0xc6cd75e9u, 0xc0433cbdu, 0xbb049a79u, 0xc4a334bfu, 0xc4a334bfu, 0x9d3c7decu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc0433cbdu, 0xc88b2875u, 0x00a61f93u, 0xf3f49249u, 0x00a61f93u, 0x70eb9a0au, 0x70eb9a0au, 0xc5e818fau, 0xc6cd75e9u, 0x7dabe929u, 0x70eb9a0au, 0x14aa4e71u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00a61f93u}, + {0xc5e818fau, 0x9293de8fu, 0x00a61f93u, 0xdc28ff90u, 0x3d6c51e3u, 0x96263ae6u, 0x96263ae6u, 0x96263ae6u, 0xc4a334bfu, 0xc0433cbdu, 0xc0433cbdu, 0x1e7ea419u, 0x00000000u, 0x00000000u, 0xc4a334bfu, 0x00a61f93u}, + {0x96263ae6u, 0x8c3d5f16u, 0x00a61f93u, 0xa5aec797u, 0x14aa4e71u, 0xc0433cbdu, 0xc0433cbdu, 0x70eb9a0au, 0x7dabe929u, 0xc5e818fau, 0xc5e818fau, 0x51c9bc70u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x70eb9a0au, 0xd7a7a3ccu, 0x00a61f93u, 0x8306d03bu, 0x9d3c7decu, 0xc5e818fau, 0xc5e818fau, 0x3d6c51e3u, 0xc0433cbdu, 0x96263ae6u, 0x96263ae6u, 0x80a4df5au, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x3d6c51e3u, 0xbb049a79u, 0x00a61f93u, 0xf38b2ffcu, 0x00a61f93u, 0x14aa4e71u, 0x14aa4e71u, 0x00a61f93u, 0xc5e818fau, 0x70eb9a0au, 0x14aa4e71u, 0xf38b2ffcu, 0x00000000u, 0x00000000u, 0xc5e818fau, 0x00a61f93u}, + {0x00a61f93u, 0xc6cd75e9u, 0x00a61f93u, 0x80a4df5au, 0x3d6c51e3u, 0x9d3c7decu, 0x9d3c7decu, 0x9d3c7decu, 0x96263ae6u, 0x3d6c51e3u, 0x3d6c51e3u, 0x8306d03bu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00a61f93u}, + {0x9d3c7decu, 0xc4a334bfu, 0x00a61f93u, 0x51c9bc70u, 0x14aa4e71u, 0x3d6c51e3u, 0x3d6c51e3u, 0x14aa4e71u, 0x70eb9a0au, 0x00a61f93u, 0x00a61f93u, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x14aa4e71u, 0x7dabe929u, 0x00a61f93u, 0x1e7ea419u, 0x9d3c7decu, 0x00a61f93u, 0x00a61f93u, 0x1e7ea419u, 0x3d6c51e3u, 0x9d3c7decu, 0x9d3c7decu, 0xdc28ff90u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x1c593af5u, 0xaf67d461u, 0x02f0ee99u, 0x89be9c1cu, 0x65b00a2du, 0x1919e93au, 0x1919e93au, 0x49889310u, 0x16f44881u, 0x89be9c1cu, 0x5b4c8012u, 0xe4163207u, 0x00000000u, 0x1919e93au, 0x00000000u, 0x00000000u}, + {0x49889310u, 0x7d836e77u, 0x02f0ee99u, 0x8eb5140fu, 0x35d14880u, 0xd11745adu, 0xd11745adu, 0xd11745adu, 0x8eb5140fu, 0x1c593af5u, 0xfadb8908u, 0xaf67d461u, 0x7d836e77u, 0xd11745adu, 0x8eb5140fu, 0x00000000u}, + {0xd11745adu, 0xadec6f11u, 0x02f0ee99u, 0x16f44881u, 0x59001ac9u, 0x1c593af5u, 0x1c593af5u, 0x1919e93au, 0x89be9c1cu, 0x49889310u, 0xffb88309u, 0x7d836e77u, 0xadec6f11u, 0x1c593af5u, 0x89be9c1cu, 0x00000000u}, + {0x1919e93au, 0x5071950eu, 0x02f0ee99u, 0x57241955u, 0x406329bcu, 0x49889310u, 0x49889310u, 0x731c9452u, 0x1c593af5u, 0xd11745adu, 0xede7bd0cu, 0xadec6f11u, 0x00000000u, 0x49889310u, 0x1c593af5u, 0x00000000u}, + {0x731c9452u, 0x35d14880u, 0x02f0ee99u, 0xb91dddd9u, 0x65b00a2du, 0xe4163207u, 0xe4163207u, 0x02f0ee99u, 0x49889310u, 0x1919e93au, 0xbef7e268u, 0x5071950eu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x02f0ee99u, 0x65b00a2du, 0x02f0ee99u, 0x1389b372u, 0x35d14880u, 0xd0944996u, 0xd0944996u, 0xd0944996u, 0xd11745adu, 0x731c9452u, 0x731c9452u, 0x35d14880u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xd0944996u, 0x406329bcu, 0x02f0ee99u, 0xa341738cu, 0x59001ac9u, 0x731c9452u, 0x731c9452u, 0xe4163207u, 0x1919e93au, 0x02f0ee99u, 0x02f0ee99u, 0x65b00a2du, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xe4163207u, 0x59001ac9u, 0x02f0ee99u, 0x837a7935u, 0x406329bcu, 0x02f0ee99u, 0x02f0ee99u, 0xaf67d461u, 0x731c9452u, 0xd0944996u, 0xd0944996u, 0x406329bcu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xaf67d461u, 0x1c593af5u, 0x65b00a2du, 0xbef7e268u, 0x65b00a2du, 0x5071950eu, 0x5071950eu, 0x7d836e77u, 0x02f0ee99u, 0xe4163207u, 0x5071950eu, 0x59001ac9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x7d836e77u, 0x49889310u, 0x65b00a2du, 0xffe976abu, 0x35d14880u, 0xadec6f11u, 0xadec6f11u, 0xadec6f11u, 0xd0944996u, 0xaf67d461u, 0xaf67d461u, 0xfadb8908u, 0x49889310u, 0xadec6f11u, 0x00000000u, 0x00000000u}, + {0xadec6f11u, 0xd11745adu, 0x65b00a2du, 0x60581ccau, 0x59001ac9u, 0xaf67d461u, 0xaf67d461u, 0x5071950eu, 0xe4163207u, 0x7d836e77u, 0x7d836e77u, 0xffb88309u, 0xd11745adu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x5071950eu, 0x1919e93au, 0x65b00a2du, 0xce1d62e0u, 0x406329bcu, 0x7d836e77u, 0x7d836e77u, 0x35d14880u, 0xaf67d461u, 0xadec6f11u, 0xadec6f11u, 0xede7bd0cu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x35d14880u, 0x731c9452u, 0x65b00a2du, 0x5b4c8012u, 0x65b00a2du, 0x59001ac9u, 0x59001ac9u, 0x65b00a2du, 0x7d836e77u, 0x5071950eu, 0x59001ac9u, 0x5b4c8012u, 0x00000000u, 0x59001ac9u, 0x7d836e77u, 0x00000000u}, + {0x65b00a2du, 0x02f0ee99u, 0x65b00a2du, 0xede7bd0cu, 0x35d14880u, 0x406329bcu, 0x406329bcu, 0x406329bcu, 0xadec6f11u, 0x35d14880u, 0x35d14880u, 0xce1d62e0u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x406329bcu, 0xd0944996u, 0x65b00a2du, 0xffb88309u, 0x59001ac9u, 0x35d14880u, 0x35d14880u, 0x59001ac9u, 0x5071950eu, 0x65b00a2du, 0x65b00a2du, 0x60581ccau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x59001ac9u, 0xe4163207u, 0x65b00a2du, 0xfadb8908u, 0x406329bcu, 0x65b00a2du, 0x65b00a2du, 0xfadb8908u, 0x35d14880u, 0x406329bcu, 0x406329bcu, 0xffe976abu, 0x00000000u, 0x00000000u, 0x35d14880u, 0x00000000u}, + {0xfadb8908u, 0x837a7935u, 0x60581ccau, 0x59001ac9u, 0x16f44881u, 0x5b4c8012u, 0x5b4c8012u, 0xffb88309u, 0x65b00a2du, 0x59001ac9u, 0x1919e93au, 0xbef7e268u, 0x837a7935u, 0x00000000u, 0x00000000u, 0x60581ccau}, + {0xffb88309u, 0xa341738cu, 0x60581ccau, 0x406329bcu, 0x57241955u, 0xede7bd0cu, 0xede7bd0cu, 0xede7bd0cu, 0x406329bcu, 0xfadb8908u, 0x1c593af5u, 0x837a7935u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xede7bd0cu, 0x1389b372u, 0x60581ccau, 0x65b00a2du, 0x89be9c1cu, 0xfadb8908u, 0xfadb8908u, 0x5b4c8012u, 0x59001ac9u, 0xffb88309u, 0x49889310u, 0xa341738cu, 0x00000000u, 0xfadb8908u, 0x59001ac9u, 0x60581ccau}, + {0x5b4c8012u, 0xb91dddd9u, 0x60581ccau, 0x35d14880u, 0x8eb5140fu, 0xffb88309u, 0xffb88309u, 0xce1d62e0u, 0xfadb8908u, 0xede7bd0cu, 0xd11745adu, 0x1389b372u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xce1d62e0u, 0x57241955u, 0x60581ccau, 0x5071950eu, 0x16f44881u, 0xbef7e268u, 0xbef7e268u, 0x60581ccau, 0xffb88309u, 0x5b4c8012u, 0xe4163207u, 0xb91dddd9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x60581ccau, 0x16f44881u, 0x60581ccau, 0xadec6f11u, 0x57241955u, 0xffe976abu, 0xffe976abu, 0xffe976abu, 0xede7bd0cu, 0xce1d62e0u, 0xce1d62e0u, 0x57241955u, 0x00000000u, 0x00000000u, 0xede7bd0cu, 0x60581ccau}, + {0xffe976abu, 0x8eb5140fu, 0x60581ccau, 0x7d836e77u, 0x89be9c1cu, 0xce1d62e0u, 0xce1d62e0u, 0xbef7e268u, 0x5b4c8012u, 0x60581ccau, 0x60581ccau, 0x16f44881u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xbef7e268u, 0x89be9c1cu, 0x60581ccau, 0xaf67d461u, 0x8eb5140fu, 0x60581ccau, 0x60581ccau, 0x837a7935u, 0xce1d62e0u, 0xffe976abu, 0xffe976abu, 0x8eb5140fu, 0x89be9c1cu, 0x60581ccau, 0x00000000u, 0x60581ccau}, + {0x837a7935u, 0xfadb8908u, 0x16f44881u, 0xe4163207u, 0x16f44881u, 0xb91dddd9u, 0xb91dddd9u, 0xa341738cu, 0x60581ccau, 0xbef7e268u, 0xb91dddd9u, 0x89be9c1cu, 0xfadb8908u, 0x00000000u, 0x60581ccau, 0x00000000u}, + {0xa341738cu, 0xffb88309u, 0x16f44881u, 0xd0944996u, 0x57241955u, 0x1389b372u, 0x1389b372u, 0x1389b372u, 0xffe976abu, 0x837a7935u, 0x837a7935u, 0x1c593af5u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x1389b372u, 0xede7bd0cu, 0x16f44881u, 0x02f0ee99u, 0x89be9c1cu, 0x837a7935u, 0x837a7935u, 0xb91dddd9u, 0xbef7e268u, 0xa341738cu, 0xa341738cu, 0x49889310u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb91dddd9u, 0x5b4c8012u, 0x16f44881u, 0x731c9452u, 0x8eb5140fu, 0xa341738cu, 0xa341738cu, 0x57241955u, 0x837a7935u, 0x1389b372u, 0x1389b372u, 0xd11745adu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x57241955u, 0xce1d62e0u, 0x16f44881u, 0x1919e93au, 0x16f44881u, 0x89be9c1cu, 0x89be9c1cu, 0x16f44881u, 0xa341738cu, 0xb91dddd9u, 0x89be9c1cu, 0x1919e93au, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x16f44881u, 0x60581ccau, 0x16f44881u, 0xd11745adu, 0x57241955u, 0x8eb5140fu, 0x8eb5140fu, 0x8eb5140fu, 0x1389b372u, 0x57241955u, 0x57241955u, 0x731c9452u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8eb5140fu, 0xffe976abu, 0x16f44881u, 0x49889310u, 0x89be9c1cu, 0x57241955u, 0x57241955u, 0x89be9c1cu, 0xb91dddd9u, 0x16f44881u, 0x16f44881u, 0x02f0ee99u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x89be9c1cu, 0xbef7e268u, 0x16f44881u, 0x1c593af5u, 0x8eb5140fu, 0x16f44881u, 0x16f44881u, 0x1c593af5u, 0x57241955u, 0x8eb5140fu, 0x8eb5140fu, 0xd0944996u, 0xbef7e268u, 0x00000000u, 0x00000000u, 0x00000000u} +}; + +constexpr const char* Names[Checked] = {"bitmask_identity", "bitmask_swap8", "bitmask_broadcast", "bitmask_reverse", "bitmask_mixed", "quad_perm", "quad_perm_high_bits", "rotate_left_1", "rotate_right_3", "rotate_left_31", "rotate_masked", "rotate_high_bits", "inactive_swap8", "inactive_quad_perm", "inactive_rotate", "inactive_broadcast"}; + +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; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + for (std::uint32_t tid = 0; tid < MaxThreads; ++tid) std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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{{waveSize, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + 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(); +} + +template +void Check(const std::uint32_t (&expected)[Lanes][Checked]) { + for (std::uint32_t tid = 0; tid < Lanes; ++tid) { + for (std::uint32_t index = 0; index < Checked; ++index) { + const auto actual = Output[tid * Results + index]; + Require(actual == expected[tid][index], "ds_swizzle wave" + std::to_string(Lanes) + ": lane " + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < 32) { + std::printf("skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); + return VulkanTestSkipped; + } + Run(*device, Wave32Code, 32, device->ComputeTarget(32)); + Check(Expected32); + Run(*device, Wave64Code, 64, device->Target()); + Check(Expected64); + Run(*device, Wave64Code, 64, device->ComputeTarget(32)); + Check(Expected64); + std::puts("ds_swizzle tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} From 9edb57d6b51e0fbfce87e7b044cd2a52902c3763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 04:38:40 +0200 Subject: [PATCH 072/164] fix(shader): keep v_permlane16 lanes whose source lane is inactive Without fetch-inactive or bound_ctrl, a v_permlane16_b32/v_permlanex16_b32 lane whose source lane is inactive keeps its old value on hardware; AnyPS5 wrote 0 there. bound_ctrl alone now writes 0. Co-Authored-By: Claude --- .../tests/execution/Permlane16.cpp | 293 ++++++++++++++++++ .../src/IrOpcode.cpp | 2 +- .../src/SpirvAlu/SpirvAluEmitterMath.cpp | 2 +- .../src/ControlFlowInstructions.cpp | 3 +- 4 files changed, 297 insertions(+), 3 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Permlane16.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Permlane16.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Permlane16.cpp new file mode 100644 index 000000000..d66899aff --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Permlane16.cpp @@ -0,0 +1,293 @@ +#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 MaxThreads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Checked = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Wave32Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xbe9403ff, 0x76543210, 0xbe9503ff, + 0xfedcba98, 0xbe9603ff, 0x89abcdef, 0xbe9703ff, 0x01234567, 0xbe9803ff, 0x1f2e3d4c, 0xbe9903ff, + 0x8a9b7c60, 0xbe9a03ff, 0x55555555, 0xbe9b03ff, 0x55555555, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0x7e3202ff, 0xdeaddead, 0xd777000a, 0x00542904, 0xd777000b, + 0x005c2d04, 0xd777000c, 0x00643104, 0xd777000d, 0x02010104, 0xd777000e, 0x03058304, 0xd778000f, + 0x00542904, 0xd7780010, 0x005c2d04, 0xd7780011, 0x00643104, 0xd7780012, 0x006c3504, 0xbefe03ff, + 0x33cc0ff3, 0xd7770013, 0x00643104, 0xd7770814, 0x00643104, 0xd7771015, 0x00643104, 0xd7771816, + 0x00643104, 0xd7780017, 0x005c2d04, 0xd7780818, 0x005c2d04, 0xd7781019, 0x005c2d04, 0xbefe03c1, + 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, + 0xbf810000, +}; + +alignas(256) constexpr std::array Wave64Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xbe9403ff, 0x76543210, 0xbe9503ff, + 0xfedcba98, 0xbe9603ff, 0x89abcdef, 0xbe9703ff, 0x01234567, 0xbe9803ff, 0x1f2e3d4c, 0xbe9903ff, + 0x8a9b7c60, 0xbe9a03ff, 0x55555555, 0xbe9b03ff, 0x55555555, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0x7e3202ff, 0xdeaddead, 0xd777000a, 0x00542904, 0xd777000b, + 0x005c2d04, 0xd777000c, 0x00643104, 0xd777000d, 0x02010104, 0xd777000e, 0x03058304, 0xd778000f, + 0x00542904, 0xd7780010, 0x005c2d04, 0xd7780011, 0x00643104, 0xd7780012, 0x006c3504, 0xbefe03ff, + 0x33cc0ff3, 0xbeff03ff, 0xc3a5960f, 0xd7770013, 0x00643104, 0xd7770814, 0x00643104, 0xd7771015, + 0x00643104, 0xd7771816, 0x00643104, 0xd7780017, 0x005c2d04, 0xd7780818, 0x005c2d04, 0xd7781019, + 0x005c2d04, 0xbefe04c1, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, + 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[64][4] = { + {0x1e7ea419u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x51c9bc70u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80a4df5au, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xf38b2ffcu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x8306d03bu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xa5aec797u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xdc28ff90u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xf3f49249u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x1a466884u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xe255accbu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x39292d22u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xe5121482u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x99dd251du, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x9f199504u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x8e7aa6e9u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x6bad6be2u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc88b2875u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x9293de8fu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x8c3d5f16u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xd7a7a3ccu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xbb049a79u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc6cd75e9u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc4a334bfu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x7dabe929u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc0433cbdu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc5e818fau, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x96263ae6u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x70eb9a0au, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3d6c51e3u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00a61f93u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x9d3c7decu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x14aa4e71u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x1c593af5u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x49889310u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xd11745adu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x1919e93au, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x731c9452u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x02f0ee99u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xd0944996u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xe4163207u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xaf67d461u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x7d836e77u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xadec6f11u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x5071950eu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x35d14880u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x65b00a2du, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x406329bcu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x59001ac9u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xfadb8908u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xffb88309u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xede7bd0cu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x5b4c8012u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xce1d62e0u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x60581ccau, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xffe976abu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xbef7e268u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x837a7935u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xa341738cu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x1389b372u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xb91dddd9u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x57241955u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x16f44881u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x8eb5140fu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x89be9c1cu, 0x00000000u, 0x00000000u, 0x00000000u} +}; + +constexpr std::uint32_t Expected32[32][16] = { + {0x1e7ea419u, 0x6bad6be2u, 0x99dd251du, 0x1e7ea419u, 0x6bad6be2u, 0xc88b2875u, 0x14aa4e71u, 0x3d6c51e3u, 0xc6cd75e9u, 0xdeaddeadu, 0x99dd251du, 0x00000000u, 0x99dd251du, 0xdeaddeadu, 0x14aa4e71u, 0x00000000u}, + {0x51c9bc70u, 0x8e7aa6e9u, 0x8306d03bu, 0x1e7ea419u, 0x6bad6be2u, 0x9293de8fu, 0x9d3c7decu, 0xbb049a79u, 0xc6cd75e9u, 0x8306d03bu, 0x8306d03bu, 0x8306d03bu, 0x8306d03bu, 0xdeaddeadu, 0x9d3c7decu, 0x00000000u}, + {0x80a4df5au, 0x9f199504u, 0x9f199504u, 0x1e7ea419u, 0x6bad6be2u, 0x8c3d5f16u, 0x00a61f93u, 0x00a61f93u, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xf38b2ffcu, 0x99dd251du, 0xf38b2ffcu, 0x1e7ea419u, 0x6bad6be2u, 0xd7a7a3ccu, 0x3d6c51e3u, 0xd7a7a3ccu, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8306d03bu, 0xe5121482u, 0x8e7aa6e9u, 0x1e7ea419u, 0x6bad6be2u, 0xbb049a79u, 0x70eb9a0au, 0x9d3c7decu, 0xc6cd75e9u, 0xdeaddeadu, 0x8e7aa6e9u, 0x00000000u, 0x8e7aa6e9u, 0xdeaddeadu, 0x70eb9a0au, 0x00000000u}, + {0xa5aec797u, 0x39292d22u, 0x80a4df5au, 0x1e7ea419u, 0x6bad6be2u, 0xc6cd75e9u, 0x96263ae6u, 0x8c3d5f16u, 0xc6cd75e9u, 0xdeaddeadu, 0x80a4df5au, 0x00000000u, 0x80a4df5au, 0xdeaddeadu, 0x96263ae6u, 0x00000000u}, + {0xdc28ff90u, 0xe255accbu, 0x6bad6be2u, 0x1e7ea419u, 0x6bad6be2u, 0xc4a334bfu, 0xc5e818fau, 0x14aa4e71u, 0xc6cd75e9u, 0xdeaddeadu, 0x6bad6be2u, 0x00000000u, 0x6bad6be2u, 0xc5e818fau, 0xc5e818fau, 0xc5e818fau}, + {0xf3f49249u, 0x1a466884u, 0x51c9bc70u, 0x1e7ea419u, 0x6bad6be2u, 0x7dabe929u, 0xc0433cbdu, 0x9293de8fu, 0xc6cd75e9u, 0x51c9bc70u, 0x51c9bc70u, 0x51c9bc70u, 0x51c9bc70u, 0xc0433cbdu, 0xc0433cbdu, 0xc0433cbdu}, + {0x1a466884u, 0xf3f49249u, 0x1e7ea419u, 0x1e7ea419u, 0x6bad6be2u, 0xc0433cbdu, 0x7dabe929u, 0xc88b2875u, 0xc6cd75e9u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x7dabe929u, 0x7dabe929u, 0x7dabe929u}, + {0xe255accbu, 0xdc28ff90u, 0xdc28ff90u, 0x1e7ea419u, 0x6bad6be2u, 0xc5e818fau, 0xc4a334bfu, 0xc4a334bfu, 0xc6cd75e9u, 0xdc28ff90u, 0xdc28ff90u, 0xdc28ff90u, 0xdc28ff90u, 0xc4a334bfu, 0xc4a334bfu, 0xc4a334bfu}, + {0x39292d22u, 0xa5aec797u, 0x99dd251du, 0x1e7ea419u, 0x6bad6be2u, 0x96263ae6u, 0xc6cd75e9u, 0x3d6c51e3u, 0xc6cd75e9u, 0xdeaddeadu, 0x99dd251du, 0x00000000u, 0x99dd251du, 0xdeaddeadu, 0xc6cd75e9u, 0x00000000u}, + {0xe5121482u, 0x8306d03bu, 0xf3f49249u, 0x1e7ea419u, 0x6bad6be2u, 0x70eb9a0au, 0xbb049a79u, 0x7dabe929u, 0xc6cd75e9u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u, 0xdeaddeadu, 0xbb049a79u, 0x00000000u}, + {0x99dd251du, 0xf38b2ffcu, 0xe5121482u, 0x1e7ea419u, 0x6bad6be2u, 0x3d6c51e3u, 0xd7a7a3ccu, 0x70eb9a0au, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9f199504u, 0x80a4df5au, 0xe255accbu, 0x1e7ea419u, 0x6bad6be2u, 0x00a61f93u, 0x8c3d5f16u, 0xc5e818fau, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8e7aa6e9u, 0x51c9bc70u, 0x39292d22u, 0x1e7ea419u, 0x6bad6be2u, 0x9d3c7decu, 0x9293de8fu, 0x96263ae6u, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x6bad6be2u, 0x1e7ea419u, 0x1a466884u, 0x1e7ea419u, 0x6bad6be2u, 0x14aa4e71u, 0xc88b2875u, 0xc0433cbdu, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc88b2875u, 0x14aa4e71u, 0x3d6c51e3u, 0xc88b2875u, 0x14aa4e71u, 0x1e7ea419u, 0x6bad6be2u, 0x99dd251du, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9293de8fu, 0x9d3c7decu, 0xbb049a79u, 0xc88b2875u, 0x14aa4e71u, 0x51c9bc70u, 0x8e7aa6e9u, 0x8306d03bu, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8c3d5f16u, 0x00a61f93u, 0x00a61f93u, 0xc88b2875u, 0x14aa4e71u, 0x80a4df5au, 0x9f199504u, 0x9f199504u, 0xa5aec797u, 0x00a61f93u, 0x00a61f93u, 0x00a61f93u, 0x00a61f93u, 0xdeaddeadu, 0x9f199504u, 0x00000000u}, + {0xd7a7a3ccu, 0x3d6c51e3u, 0xd7a7a3ccu, 0xc88b2875u, 0x14aa4e71u, 0xf38b2ffcu, 0x99dd251du, 0xf38b2ffcu, 0xa5aec797u, 0xd7a7a3ccu, 0xd7a7a3ccu, 0xd7a7a3ccu, 0xd7a7a3ccu, 0xdeaddeadu, 0x99dd251du, 0x00000000u}, + {0xbb049a79u, 0x70eb9a0au, 0x9d3c7decu, 0xc88b2875u, 0x14aa4e71u, 0x8306d03bu, 0xe5121482u, 0x8e7aa6e9u, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc6cd75e9u, 0x96263ae6u, 0x8c3d5f16u, 0xc88b2875u, 0x14aa4e71u, 0xa5aec797u, 0x39292d22u, 0x80a4df5au, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc4a334bfu, 0xc5e818fau, 0x14aa4e71u, 0xc88b2875u, 0x14aa4e71u, 0xdc28ff90u, 0xe255accbu, 0x6bad6be2u, 0xa5aec797u, 0xdeaddeadu, 0x14aa4e71u, 0x00000000u, 0x14aa4e71u, 0xe255accbu, 0xe255accbu, 0xe255accbu}, + {0x7dabe929u, 0xc0433cbdu, 0x9293de8fu, 0xc88b2875u, 0x14aa4e71u, 0xf3f49249u, 0x1a466884u, 0x51c9bc70u, 0xa5aec797u, 0xdeaddeadu, 0x9293de8fu, 0x00000000u, 0x9293de8fu, 0x1a466884u, 0x1a466884u, 0x1a466884u}, + {0xc0433cbdu, 0x7dabe929u, 0xc88b2875u, 0xc88b2875u, 0x14aa4e71u, 0x1a466884u, 0xf3f49249u, 0x1e7ea419u, 0xa5aec797u, 0xdeaddeadu, 0xc88b2875u, 0x00000000u, 0xc88b2875u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u}, + {0xc5e818fau, 0xc4a334bfu, 0xc4a334bfu, 0xc88b2875u, 0x14aa4e71u, 0xe255accbu, 0xdc28ff90u, 0xdc28ff90u, 0xa5aec797u, 0xc4a334bfu, 0xc4a334bfu, 0xc4a334bfu, 0xc4a334bfu, 0xdc28ff90u, 0xdc28ff90u, 0xdc28ff90u}, + {0x96263ae6u, 0xc6cd75e9u, 0x3d6c51e3u, 0xc88b2875u, 0x14aa4e71u, 0x39292d22u, 0xa5aec797u, 0x99dd251du, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x70eb9a0au, 0xbb049a79u, 0x7dabe929u, 0xc88b2875u, 0x14aa4e71u, 0xe5121482u, 0x8306d03bu, 0xf3f49249u, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x3d6c51e3u, 0xd7a7a3ccu, 0x70eb9a0au, 0xc88b2875u, 0x14aa4e71u, 0x99dd251du, 0xf38b2ffcu, 0xe5121482u, 0xa5aec797u, 0xdeaddeadu, 0x70eb9a0au, 0x00000000u, 0x70eb9a0au, 0xdeaddeadu, 0xf38b2ffcu, 0x00000000u}, + {0x00a61f93u, 0x8c3d5f16u, 0xc5e818fau, 0xc88b2875u, 0x14aa4e71u, 0x9f199504u, 0x80a4df5au, 0xe255accbu, 0xa5aec797u, 0xc5e818fau, 0xc5e818fau, 0xc5e818fau, 0xc5e818fau, 0xdeaddeadu, 0x80a4df5au, 0x00000000u}, + {0x9d3c7decu, 0x9293de8fu, 0x96263ae6u, 0xc88b2875u, 0x14aa4e71u, 0x8e7aa6e9u, 0x51c9bc70u, 0x39292d22u, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x14aa4e71u, 0xc88b2875u, 0xc0433cbdu, 0xc88b2875u, 0x14aa4e71u, 0x6bad6be2u, 0x1e7ea419u, 0x1a466884u, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu} +}; +constexpr std::uint32_t Expected64[64][16] = { + {0x1e7ea419u, 0x6bad6be2u, 0x99dd251du, 0x1e7ea419u, 0x6bad6be2u, 0xc88b2875u, 0x14aa4e71u, 0x3d6c51e3u, 0xc6cd75e9u, 0xdeaddeadu, 0x99dd251du, 0x00000000u, 0x99dd251du, 0xdeaddeadu, 0x14aa4e71u, 0x00000000u}, + {0x51c9bc70u, 0x8e7aa6e9u, 0x8306d03bu, 0x1e7ea419u, 0x6bad6be2u, 0x9293de8fu, 0x9d3c7decu, 0xbb049a79u, 0xc6cd75e9u, 0x8306d03bu, 0x8306d03bu, 0x8306d03bu, 0x8306d03bu, 0xdeaddeadu, 0x9d3c7decu, 0x00000000u}, + {0x80a4df5au, 0x9f199504u, 0x9f199504u, 0x1e7ea419u, 0x6bad6be2u, 0x8c3d5f16u, 0x00a61f93u, 0x00a61f93u, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xf38b2ffcu, 0x99dd251du, 0xf38b2ffcu, 0x1e7ea419u, 0x6bad6be2u, 0xd7a7a3ccu, 0x3d6c51e3u, 0xd7a7a3ccu, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8306d03bu, 0xe5121482u, 0x8e7aa6e9u, 0x1e7ea419u, 0x6bad6be2u, 0xbb049a79u, 0x70eb9a0au, 0x9d3c7decu, 0xc6cd75e9u, 0xdeaddeadu, 0x8e7aa6e9u, 0x00000000u, 0x8e7aa6e9u, 0xdeaddeadu, 0x70eb9a0au, 0x00000000u}, + {0xa5aec797u, 0x39292d22u, 0x80a4df5au, 0x1e7ea419u, 0x6bad6be2u, 0xc6cd75e9u, 0x96263ae6u, 0x8c3d5f16u, 0xc6cd75e9u, 0xdeaddeadu, 0x80a4df5au, 0x00000000u, 0x80a4df5au, 0xdeaddeadu, 0x96263ae6u, 0x00000000u}, + {0xdc28ff90u, 0xe255accbu, 0x6bad6be2u, 0x1e7ea419u, 0x6bad6be2u, 0xc4a334bfu, 0xc5e818fau, 0x14aa4e71u, 0xc6cd75e9u, 0xdeaddeadu, 0x6bad6be2u, 0x00000000u, 0x6bad6be2u, 0xc5e818fau, 0xc5e818fau, 0xc5e818fau}, + {0xf3f49249u, 0x1a466884u, 0x51c9bc70u, 0x1e7ea419u, 0x6bad6be2u, 0x7dabe929u, 0xc0433cbdu, 0x9293de8fu, 0xc6cd75e9u, 0x51c9bc70u, 0x51c9bc70u, 0x51c9bc70u, 0x51c9bc70u, 0xc0433cbdu, 0xc0433cbdu, 0xc0433cbdu}, + {0x1a466884u, 0xf3f49249u, 0x1e7ea419u, 0x1e7ea419u, 0x6bad6be2u, 0xc0433cbdu, 0x7dabe929u, 0xc88b2875u, 0xc6cd75e9u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x7dabe929u, 0x7dabe929u, 0x7dabe929u}, + {0xe255accbu, 0xdc28ff90u, 0xdc28ff90u, 0x1e7ea419u, 0x6bad6be2u, 0xc5e818fau, 0xc4a334bfu, 0xc4a334bfu, 0xc6cd75e9u, 0xdc28ff90u, 0xdc28ff90u, 0xdc28ff90u, 0xdc28ff90u, 0xc4a334bfu, 0xc4a334bfu, 0xc4a334bfu}, + {0x39292d22u, 0xa5aec797u, 0x99dd251du, 0x1e7ea419u, 0x6bad6be2u, 0x96263ae6u, 0xc6cd75e9u, 0x3d6c51e3u, 0xc6cd75e9u, 0xdeaddeadu, 0x99dd251du, 0x00000000u, 0x99dd251du, 0xdeaddeadu, 0xc6cd75e9u, 0x00000000u}, + {0xe5121482u, 0x8306d03bu, 0xf3f49249u, 0x1e7ea419u, 0x6bad6be2u, 0x70eb9a0au, 0xbb049a79u, 0x7dabe929u, 0xc6cd75e9u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u, 0xdeaddeadu, 0xbb049a79u, 0x00000000u}, + {0x99dd251du, 0xf38b2ffcu, 0xe5121482u, 0x1e7ea419u, 0x6bad6be2u, 0x3d6c51e3u, 0xd7a7a3ccu, 0x70eb9a0au, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9f199504u, 0x80a4df5au, 0xe255accbu, 0x1e7ea419u, 0x6bad6be2u, 0x00a61f93u, 0x8c3d5f16u, 0xc5e818fau, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8e7aa6e9u, 0x51c9bc70u, 0x39292d22u, 0x1e7ea419u, 0x6bad6be2u, 0x9d3c7decu, 0x9293de8fu, 0x96263ae6u, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x6bad6be2u, 0x1e7ea419u, 0x1a466884u, 0x1e7ea419u, 0x6bad6be2u, 0x14aa4e71u, 0xc88b2875u, 0xc0433cbdu, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc88b2875u, 0x14aa4e71u, 0x3d6c51e3u, 0xc88b2875u, 0x14aa4e71u, 0x1e7ea419u, 0x6bad6be2u, 0x99dd251du, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9293de8fu, 0x9d3c7decu, 0xbb049a79u, 0xc88b2875u, 0x14aa4e71u, 0x51c9bc70u, 0x8e7aa6e9u, 0x8306d03bu, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8c3d5f16u, 0x00a61f93u, 0x00a61f93u, 0xc88b2875u, 0x14aa4e71u, 0x80a4df5au, 0x9f199504u, 0x9f199504u, 0xa5aec797u, 0x00a61f93u, 0x00a61f93u, 0x00a61f93u, 0x00a61f93u, 0xdeaddeadu, 0x9f199504u, 0x00000000u}, + {0xd7a7a3ccu, 0x3d6c51e3u, 0xd7a7a3ccu, 0xc88b2875u, 0x14aa4e71u, 0xf38b2ffcu, 0x99dd251du, 0xf38b2ffcu, 0xa5aec797u, 0xd7a7a3ccu, 0xd7a7a3ccu, 0xd7a7a3ccu, 0xd7a7a3ccu, 0xdeaddeadu, 0x99dd251du, 0x00000000u}, + {0xbb049a79u, 0x70eb9a0au, 0x9d3c7decu, 0xc88b2875u, 0x14aa4e71u, 0x8306d03bu, 0xe5121482u, 0x8e7aa6e9u, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc6cd75e9u, 0x96263ae6u, 0x8c3d5f16u, 0xc88b2875u, 0x14aa4e71u, 0xa5aec797u, 0x39292d22u, 0x80a4df5au, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc4a334bfu, 0xc5e818fau, 0x14aa4e71u, 0xc88b2875u, 0x14aa4e71u, 0xdc28ff90u, 0xe255accbu, 0x6bad6be2u, 0xa5aec797u, 0xdeaddeadu, 0x14aa4e71u, 0x00000000u, 0x14aa4e71u, 0xe255accbu, 0xe255accbu, 0xe255accbu}, + {0x7dabe929u, 0xc0433cbdu, 0x9293de8fu, 0xc88b2875u, 0x14aa4e71u, 0xf3f49249u, 0x1a466884u, 0x51c9bc70u, 0xa5aec797u, 0xdeaddeadu, 0x9293de8fu, 0x00000000u, 0x9293de8fu, 0x1a466884u, 0x1a466884u, 0x1a466884u}, + {0xc0433cbdu, 0x7dabe929u, 0xc88b2875u, 0xc88b2875u, 0x14aa4e71u, 0x1a466884u, 0xf3f49249u, 0x1e7ea419u, 0xa5aec797u, 0xdeaddeadu, 0xc88b2875u, 0x00000000u, 0xc88b2875u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u}, + {0xc5e818fau, 0xc4a334bfu, 0xc4a334bfu, 0xc88b2875u, 0x14aa4e71u, 0xe255accbu, 0xdc28ff90u, 0xdc28ff90u, 0xa5aec797u, 0xc4a334bfu, 0xc4a334bfu, 0xc4a334bfu, 0xc4a334bfu, 0xdc28ff90u, 0xdc28ff90u, 0xdc28ff90u}, + {0x96263ae6u, 0xc6cd75e9u, 0x3d6c51e3u, 0xc88b2875u, 0x14aa4e71u, 0x39292d22u, 0xa5aec797u, 0x99dd251du, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x70eb9a0au, 0xbb049a79u, 0x7dabe929u, 0xc88b2875u, 0x14aa4e71u, 0xe5121482u, 0x8306d03bu, 0xf3f49249u, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x3d6c51e3u, 0xd7a7a3ccu, 0x70eb9a0au, 0xc88b2875u, 0x14aa4e71u, 0x99dd251du, 0xf38b2ffcu, 0xe5121482u, 0xa5aec797u, 0xdeaddeadu, 0x70eb9a0au, 0x00000000u, 0x70eb9a0au, 0xdeaddeadu, 0xf38b2ffcu, 0x00000000u}, + {0x00a61f93u, 0x8c3d5f16u, 0xc5e818fau, 0xc88b2875u, 0x14aa4e71u, 0x9f199504u, 0x80a4df5au, 0xe255accbu, 0xa5aec797u, 0xc5e818fau, 0xc5e818fau, 0xc5e818fau, 0xc5e818fau, 0xdeaddeadu, 0x80a4df5au, 0x00000000u}, + {0x9d3c7decu, 0x9293de8fu, 0x96263ae6u, 0xc88b2875u, 0x14aa4e71u, 0x8e7aa6e9u, 0x51c9bc70u, 0x39292d22u, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x14aa4e71u, 0xc88b2875u, 0xc0433cbdu, 0xc88b2875u, 0x14aa4e71u, 0x6bad6be2u, 0x1e7ea419u, 0x1a466884u, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x1c593af5u, 0x59001ac9u, 0x35d14880u, 0x1c593af5u, 0x59001ac9u, 0xfadb8908u, 0x89be9c1cu, 0x57241955u, 0x60581ccau, 0x35d14880u, 0x35d14880u, 0x35d14880u, 0x35d14880u, 0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu}, + {0x49889310u, 0x406329bcu, 0x731c9452u, 0x1c593af5u, 0x59001ac9u, 0xffb88309u, 0x8eb5140fu, 0xce1d62e0u, 0x60581ccau, 0xdeaddeadu, 0x731c9452u, 0x00000000u, 0x731c9452u, 0x8eb5140fu, 0x8eb5140fu, 0x8eb5140fu}, + {0xd11745adu, 0x65b00a2du, 0x65b00a2du, 0x1c593af5u, 0x59001ac9u, 0xede7bd0cu, 0x16f44881u, 0x16f44881u, 0x60581ccau, 0xdeaddeadu, 0x65b00a2du, 0x00000000u, 0x65b00a2du, 0xdeaddeadu, 0x16f44881u, 0x00000000u}, + {0x1919e93au, 0x35d14880u, 0x1919e93au, 0x1c593af5u, 0x59001ac9u, 0x5b4c8012u, 0x57241955u, 0x5b4c8012u, 0x60581ccau, 0x1919e93au, 0x1919e93au, 0x1919e93au, 0x1919e93au, 0xdeaddeadu, 0x57241955u, 0x00000000u}, + {0x731c9452u, 0x5071950eu, 0x406329bcu, 0x1c593af5u, 0x59001ac9u, 0xce1d62e0u, 0xb91dddd9u, 0x8eb5140fu, 0x60581ccau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x02f0ee99u, 0xadec6f11u, 0xd11745adu, 0x1c593af5u, 0x59001ac9u, 0x60581ccau, 0x1389b372u, 0xede7bd0cu, 0x60581ccau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xd0944996u, 0x7d836e77u, 0x59001ac9u, 0x1c593af5u, 0x59001ac9u, 0xffe976abu, 0xa341738cu, 0x89be9c1cu, 0x60581ccau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xe4163207u, 0xaf67d461u, 0x49889310u, 0x1c593af5u, 0x59001ac9u, 0xbef7e268u, 0x837a7935u, 0xffb88309u, 0x60581ccau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xaf67d461u, 0xe4163207u, 0x1c593af5u, 0x1c593af5u, 0x59001ac9u, 0x837a7935u, 0xbef7e268u, 0xfadb8908u, 0x60581ccau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x7d836e77u, 0xd0944996u, 0xd0944996u, 0x1c593af5u, 0x59001ac9u, 0xa341738cu, 0xffe976abu, 0xffe976abu, 0x60581ccau, 0xdeaddeadu, 0xd0944996u, 0x00000000u, 0xd0944996u, 0xdeaddeadu, 0xffe976abu, 0x00000000u}, + {0xadec6f11u, 0x02f0ee99u, 0x35d14880u, 0x1c593af5u, 0x59001ac9u, 0x1389b372u, 0x60581ccau, 0x57241955u, 0x60581ccau, 0x35d14880u, 0x35d14880u, 0x35d14880u, 0x35d14880u, 0x60581ccau, 0x60581ccau, 0x60581ccau}, + {0x5071950eu, 0x731c9452u, 0xe4163207u, 0x1c593af5u, 0x59001ac9u, 0xb91dddd9u, 0xce1d62e0u, 0xbef7e268u, 0x60581ccau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x35d14880u, 0x1919e93au, 0x5071950eu, 0x1c593af5u, 0x59001ac9u, 0x57241955u, 0x5b4c8012u, 0xb91dddd9u, 0x60581ccau, 0xdeaddeadu, 0x5071950eu, 0x00000000u, 0x5071950eu, 0xdeaddeadu, 0x5b4c8012u, 0x00000000u}, + {0x65b00a2du, 0xd11745adu, 0x7d836e77u, 0x1c593af5u, 0x59001ac9u, 0x16f44881u, 0xede7bd0cu, 0xa341738cu, 0x60581ccau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x406329bcu, 0x49889310u, 0xadec6f11u, 0x1c593af5u, 0x59001ac9u, 0x8eb5140fu, 0xffb88309u, 0x1389b372u, 0x60581ccau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x59001ac9u, 0x1c593af5u, 0xaf67d461u, 0x1c593af5u, 0x59001ac9u, 0x89be9c1cu, 0xfadb8908u, 0x837a7935u, 0x60581ccau, 0xdeaddeadu, 0xaf67d461u, 0x00000000u, 0xaf67d461u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u}, + {0xfadb8908u, 0x89be9c1cu, 0x57241955u, 0xfadb8908u, 0x89be9c1cu, 0x1c593af5u, 0x59001ac9u, 0x35d14880u, 0x02f0ee99u, 0xdeaddeadu, 0x57241955u, 0x00000000u, 0x57241955u, 0x59001ac9u, 0x59001ac9u, 0x59001ac9u}, + {0xffb88309u, 0x8eb5140fu, 0xce1d62e0u, 0xfadb8908u, 0x89be9c1cu, 0x49889310u, 0x406329bcu, 0x731c9452u, 0x02f0ee99u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xede7bd0cu, 0x16f44881u, 0x16f44881u, 0xfadb8908u, 0x89be9c1cu, 0xd11745adu, 0x65b00a2du, 0x65b00a2du, 0x02f0ee99u, 0xdeaddeadu, 0x16f44881u, 0x00000000u, 0x16f44881u, 0xdeaddeadu, 0x65b00a2du, 0x00000000u}, + {0x5b4c8012u, 0x57241955u, 0x5b4c8012u, 0xfadb8908u, 0x89be9c1cu, 0x1919e93au, 0x35d14880u, 0x1919e93au, 0x02f0ee99u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xce1d62e0u, 0xb91dddd9u, 0x8eb5140fu, 0xfadb8908u, 0x89be9c1cu, 0x731c9452u, 0x5071950eu, 0x406329bcu, 0x02f0ee99u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x60581ccau, 0x1389b372u, 0xede7bd0cu, 0xfadb8908u, 0x89be9c1cu, 0x02f0ee99u, 0xadec6f11u, 0xd11745adu, 0x02f0ee99u, 0xede7bd0cu, 0xede7bd0cu, 0xede7bd0cu, 0xede7bd0cu, 0xadec6f11u, 0xadec6f11u, 0xadec6f11u}, + {0xffe976abu, 0xa341738cu, 0x89be9c1cu, 0xfadb8908u, 0x89be9c1cu, 0xd0944996u, 0x7d836e77u, 0x59001ac9u, 0x02f0ee99u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xbef7e268u, 0x837a7935u, 0xffb88309u, 0xfadb8908u, 0x89be9c1cu, 0xe4163207u, 0xaf67d461u, 0x49889310u, 0x02f0ee99u, 0xdeaddeadu, 0xffb88309u, 0x00000000u, 0xffb88309u, 0xdeaddeadu, 0xaf67d461u, 0x00000000u}, + {0x837a7935u, 0xbef7e268u, 0xfadb8908u, 0xfadb8908u, 0x89be9c1cu, 0xaf67d461u, 0xe4163207u, 0x1c593af5u, 0x02f0ee99u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u, 0xdeaddeadu, 0xe4163207u, 0x00000000u}, + {0xa341738cu, 0xffe976abu, 0xffe976abu, 0xfadb8908u, 0x89be9c1cu, 0x7d836e77u, 0xd0944996u, 0xd0944996u, 0x02f0ee99u, 0xdeaddeadu, 0xffe976abu, 0x00000000u, 0xffe976abu, 0xdeaddeadu, 0xd0944996u, 0x00000000u}, + {0x1389b372u, 0x60581ccau, 0x57241955u, 0xfadb8908u, 0x89be9c1cu, 0xadec6f11u, 0x02f0ee99u, 0x35d14880u, 0x02f0ee99u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb91dddd9u, 0xce1d62e0u, 0xbef7e268u, 0xfadb8908u, 0x89be9c1cu, 0x5071950eu, 0x731c9452u, 0xe4163207u, 0x02f0ee99u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x57241955u, 0x5b4c8012u, 0xb91dddd9u, 0xfadb8908u, 0x89be9c1cu, 0x35d14880u, 0x1919e93au, 0x5071950eu, 0x02f0ee99u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x16f44881u, 0xede7bd0cu, 0xa341738cu, 0xfadb8908u, 0x89be9c1cu, 0x65b00a2du, 0xd11745adu, 0x7d836e77u, 0x02f0ee99u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8eb5140fu, 0xffb88309u, 0x1389b372u, 0xfadb8908u, 0x89be9c1cu, 0x406329bcu, 0x49889310u, 0xadec6f11u, 0x02f0ee99u, 0xdeaddeadu, 0x1389b372u, 0x00000000u, 0x1389b372u, 0x49889310u, 0x49889310u, 0x49889310u}, + {0x89be9c1cu, 0xfadb8908u, 0x837a7935u, 0xfadb8908u, 0x89be9c1cu, 0x59001ac9u, 0x1c593af5u, 0xaf67d461u, 0x02f0ee99u, 0x837a7935u, 0x837a7935u, 0x837a7935u, 0x837a7935u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u} +}; + +constexpr const char* Names[Checked] = {"permlane16_identity", "permlane16_reverse", "permlane16_mixed", "permlane16_zero", "permlane16_all_ones", "permlanex16_identity", "permlanex16_reverse", "permlanex16_mixed", "permlanex16_broadcast", "inactive_permlane16", "inactive_permlane16_fi", "inactive_permlane16_bc", "inactive_permlane16_fi_bc", "inactive_permlanex16", "inactive_permlanex16_fi", "inactive_permlanex16_bc"}; + +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; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + for (std::uint32_t tid = 0; tid < MaxThreads; ++tid) std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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{{waveSize, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + 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(); +} + +template +void Check(const std::uint32_t (&expected)[Lanes][Checked]) { + for (std::uint32_t tid = 0; tid < Lanes; ++tid) { + for (std::uint32_t index = 0; index < Checked; ++index) { + const auto actual = Output[tid * Results + index]; + Require(actual == expected[tid][index], "permlane16 wave" + std::to_string(Lanes) + ": lane " + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < 32) { + std::printf("skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); + return VulkanTestSkipped; + } + Run(*device, Wave32Code, 32, device->ComputeTarget(32)); + Check(Expected32); + Run(*device, Wave64Code, 64, device->Target()); + Check(Expected64); + Run(*device, Wave64Code, 64, device->ComputeTarget(32)); + Check(Expected64); + std::puts("permlane16 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp index b0b7461d7..1b963d32d 100644 --- a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp +++ b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp @@ -308,7 +308,7 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("ConvertU32F64", U32, U64), makeMeta("LaneId", U32), makeMeta("WriteLane", U32, U32, U32, U32), - makeMeta("Permlane16U32", U32, U32, U32, U32, U1), + makeMeta("Permlane16U32", U32, U32, U32, U32, U1, U32), makeMeta("BpermuteU32", U32, U32, U32, U1), makeMeta("PermuteU32", U32, U32, U32, U1), makeMeta("GetSrtResource", SrtResource), diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp index 13b9b7244..5dbfbb50e 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp @@ -591,7 +591,7 @@ std::uint32_t EmitPermlane16U32(SpirvValueEmitContext& ctx, const IrValue& inst) } const auto sourceExec = ctx.Shuffle(inst, 3, target); const auto result = state.module.AllocateId(); - state.module.AddFunction(spv::OpSelect, TypeU32(state), result, sourceExec, shuffled, ConstantU32(state, 0u)); + state.module.AddFunction(spv::OpSelect, TypeU32(state), result, sourceExec, shuffled, flags.boundControl ? ConstantU32(state, 0u) : ctx.Arg(inst, 4)); return result; } diff --git a/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp b/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp index 48fedcf5c..45edc6d6d 100644 --- a/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp +++ b/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp @@ -334,8 +334,9 @@ void TranslationContext::vPermlane16B32(const RdnaInstruction& inst, bool x16) { const IrU32 value = readU32(sourceAt(inst, 0u)); const IrU32 selectLow = readU32(sourceAt(inst, 1u)); const IrU32 selectHigh = readU32(sourceAt(inst, 2u)); + const IrU32 previous = readRawU32(plainOperand(inst.destination)); const PermlaneFlags flags{x16, inst.destination.opSel, inst.destination.opSelHi}; - const IrU32 result(ir.Emit(IrOpcode::Permlane16U32, IrType::U32, {&value.Value(), &selectLow.Value(), &selectHigh.Value(), &ir.GetExec()}, flags)); + const IrU32 result(ir.Emit(IrOpcode::Permlane16U32, IrType::U32, {&value.Value(), &selectLow.Value(), &selectHigh.Value(), &ir.GetExec(), &previous.Value()}, flags)); writeRawU32(inst.destination, result); } From c0a98e3a6a09e68c5bec9976c1b8ff39d09c99bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 04:45:57 +0200 Subject: [PATCH 073/164] fix(shader): refuse s_setpc_b64 after s_sub with the immediate first Co-Authored-By: Claude --- .../tests/execution/ScalarSetpc.cpp | 316 ++++++++++++++++++ .../ControlFlow/src/GraphBuilder.cpp | 2 +- 2 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/ScalarSetpc.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSetpc.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSetpc.cpp new file mode 100644 index 000000000..bc390fee3 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSetpc.cpp @@ -0,0 +1,316 @@ +#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 MaxThreads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Checked = 5; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Wave32Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeadbeef, 0x7e1602ff, + 0xdeadbeef, 0x7e1802ff, 0xdeadbeef, 0x7e1a02ff, 0xdeadbeef, 0x7e1c02ff, 0xdeadbeef, 0xbe940380, + 0xd7600008, 0x02002904, 0xd760000b, 0x02002905, 0xd760000a, 0x02002906, 0xbe930308, 0x83888308, + 0x8f088308, 0xbe8c1f00, 0x800cff0c, 0x00000178, 0x820d800d, 0xf4040386, 0x10000000, 0xbf8cc07f, + 0xbe901f00, 0x80100e10, 0x82110f11, 0xbe802010, 0x8009ff0a, 0x11110000, 0xbf820008, 0x8009ff0a, + 0x22220000, 0xbf820005, 0x8009ff0a, 0x33330000, 0xbf820002, 0x8009ff0a, 0x44440000, 0xd761000a, + 0x02002809, 0xbe880313, 0x83888208, 0x8f088308, 0xbe981f00, 0x8018ff18, 0x00000124, 0x82198019, + 0xf404068c, 0x10000008, 0xbf8cc07f, 0xbe9c1f00, 0x801c1a1c, 0x821d1b1d, 0xbe80201c, 0x8909ff0a, + 0x0000a000, 0xbf820006, 0x8909ff0a, 0x0000b000, 0xbf820003, 0x8909ff0a, 0x0000c000, 0xbf820000, + 0xd761000b, 0x02002809, 0xbe89030a, 0xbe901f00, 0x8010ff10, 0x00000018, 0xbe802010, 0xbe8903ff, + 0x00000bad, 0x8909c109, 0x80098109, 0xd761000c, 0x02002809, 0x8712870b, 0xbe890380, 0x80090a09, + 0x80928112, 0xbf06c112, 0xbf850004, 0xbe901f00, 0x8110ff10, 0xffffffec, 0xbe802010, 0xd761000d, + 0x02002809, 0x9012840b, 0x87128312, 0xbe89030a, 0x8f098109, 0x8909ff09, 0x9e3779b9, 0xbf068012, + 0xbf850005, 0x80928112, 0xbe901f00, 0x8090ff10, 0x0000001c, 0xbe802010, 0xd761000e, 0x02002809, + 0x80148114, 0xbf0aa014, 0xbf85ff9d, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xbf810000, + 0x0000000c, 0x00000000, 0x00000018, 0x00000000, 0x00000024, 0x00000000, 0x00000030, 0x00000000, + 0x0000000c, 0x00000000, 0x00000018, 0x00000000, 0x00000024, 0x00000000, +}; + +alignas(256) constexpr std::array Wave64Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeadbeef, 0x7e1602ff, + 0xdeadbeef, 0x7e1802ff, 0xdeadbeef, 0x7e1a02ff, 0xdeadbeef, 0x7e1c02ff, 0xdeadbeef, 0xbe940380, + 0xd7600008, 0x02002904, 0xd760000b, 0x02002905, 0xd760000a, 0x02002906, 0xbe930308, 0x83888308, + 0x8f088308, 0xbe8c1f00, 0x800cff0c, 0x00000178, 0x820d800d, 0xf4040386, 0x10000000, 0xbf8cc07f, + 0xbe901f00, 0x80100e10, 0x82110f11, 0xbe802010, 0x8009ff0a, 0x11110000, 0xbf820008, 0x8009ff0a, + 0x22220000, 0xbf820005, 0x8009ff0a, 0x33330000, 0xbf820002, 0x8009ff0a, 0x44440000, 0xd761000a, + 0x02002809, 0xbe880313, 0x83888208, 0x8f088308, 0xbe981f00, 0x8018ff18, 0x00000124, 0x82198019, + 0xf404068c, 0x10000008, 0xbf8cc07f, 0xbe9c1f00, 0x801c1a1c, 0x821d1b1d, 0xbe80201c, 0x8909ff0a, + 0x0000a000, 0xbf820006, 0x8909ff0a, 0x0000b000, 0xbf820003, 0x8909ff0a, 0x0000c000, 0xbf820000, + 0xd761000b, 0x02002809, 0xbe89030a, 0xbe901f00, 0x8010ff10, 0x00000018, 0xbe802010, 0xbe8903ff, + 0x00000bad, 0x8909c109, 0x80098109, 0xd761000c, 0x02002809, 0x8712870b, 0xbe890380, 0x80090a09, + 0x80928112, 0xbf06c112, 0xbf850004, 0xbe901f00, 0x8110ff10, 0xffffffec, 0xbe802010, 0xd761000d, + 0x02002809, 0x9012840b, 0x87128312, 0xbe89030a, 0x8f098109, 0x8909ff09, 0x9e3779b9, 0xbf068012, + 0xbf850005, 0x80928112, 0xbe901f00, 0x8090ff10, 0x0000001c, 0xbe802010, 0xd761000e, 0x02002809, + 0x80148114, 0xbf0ac014, 0xbf85ff9d, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xbf810000, + 0x0000000c, 0x00000000, 0x00000018, 0x00000000, 0x00000024, 0x00000000, 0x00000030, 0x00000000, + 0x0000000c, 0x00000000, 0x00000018, 0x00000000, 0x00000024, 0x00000000, +}; + +alignas(256) constexpr std::array ReversedSubtractCode{ + 0xbe901f00, 0x809010c0, 0xbe802010, 0xbf810000, +}; + +constexpr std::uint32_t Rows[64][4] = { + {0x00000000u, 0x1e7ea419u, 0x51c9bc70u, 0x00000000u}, + {0x00000001u, 0x80a4df5au, 0xf38b2ffcu, 0x00000000u}, + {0x00000002u, 0x8306d03bu, 0xa5aec797u, 0x00000000u}, + {0x00000003u, 0xdc28ff90u, 0xf3f49249u, 0x00000000u}, + {0x00000004u, 0x1a466884u, 0xe255accbu, 0x00000000u}, + {0x00000005u, 0x39292d22u, 0xe5121482u, 0x00000000u}, + {0x00000007u, 0x99dd251du, 0x9f199504u, 0x00000000u}, + {0x00000008u, 0x8e7aa6e9u, 0x6bad6be2u, 0x00000000u}, + {0x00000010u, 0xc88b2875u, 0x9293de8fu, 0x00000000u}, + {0x7fffffffu, 0x8c3d5f16u, 0xd7a7a3ccu, 0x00000000u}, + {0x80000000u, 0xbb049a79u, 0xc6cd75e9u, 0x00000000u}, + {0xfffffff8u, 0xc4a334bfu, 0x7dabe929u, 0x00000000u}, + {0xffffffffu, 0xc0433cbdu, 0xc5e818fau, 0x00000000u}, + {0x00000000u, 0x96263ae6u, 0x70eb9a0au, 0x00000000u}, + {0x00000001u, 0x3d6c51e3u, 0x00a61f93u, 0x00000000u}, + {0x00000002u, 0x9d3c7decu, 0x14aa4e71u, 0x00000000u}, + {0x00000003u, 0x1c593af5u, 0x49889310u, 0x00000000u}, + {0x00000004u, 0xd11745adu, 0x1919e93au, 0x00000000u}, + {0x00000005u, 0x731c9452u, 0x02f0ee99u, 0x00000000u}, + {0x00000007u, 0xd0944996u, 0xe4163207u, 0x00000000u}, + {0x00000008u, 0xaf67d461u, 0x7d836e77u, 0x00000000u}, + {0x00000010u, 0xadec6f11u, 0x5071950eu, 0x00000000u}, + {0x7fffffffu, 0x35d14880u, 0x65b00a2du, 0x00000000u}, + {0x80000000u, 0x406329bcu, 0x59001ac9u, 0x00000000u}, + {0xfffffff8u, 0xfadb8908u, 0xffb88309u, 0x00000000u}, + {0xffffffffu, 0xede7bd0cu, 0x5b4c8012u, 0x00000000u}, + {0xffffffffu, 0x60581ccau, 0xffe976abu, 0x00000000u}, + {0xfffffff8u, 0x837a7935u, 0xa341738cu, 0x00000000u}, + {0x00000001u, 0xb91dddd9u, 0x57241955u, 0x00000000u}, + {0x00000001u, 0x8eb5140fu, 0x89be9c1cu, 0x00000000u}, + {0x00000004u, 0x49a047dcu, 0x751b4c83u, 0x00000000u}, + {0x00000002u, 0xe060a724u, 0xa51149bbu, 0x00000000u}, + {0xfffffff8u, 0xb766ff10u, 0x93d20470u, 0x00000000u}, + {0x00000004u, 0x06555096u, 0xd9090b89u, 0x00000000u}, + {0xfffffff8u, 0x5e6c9992u, 0x5d111a9du, 0x00000000u}, + {0x00000008u, 0x6c167229u, 0x17371472u, 0x00000000u}, + {0x00000007u, 0xe41686cdu, 0x95f19a51u, 0x00000000u}, + {0x00000010u, 0x7f9ac098u, 0xde0279e1u, 0x00000000u}, + {0x00000001u, 0x6ce9da66u, 0x81a0ba05u, 0x00000000u}, + {0xffffffffu, 0xc0099ebau, 0xdde3a361u, 0x00000000u}, + {0x7fffffffu, 0xe01c683eu, 0x7db4304du, 0x00000000u}, + {0x00000007u, 0xeab06e9bu, 0x864fa3f3u, 0x00000000u}, + {0x00000004u, 0xd82fb0f1u, 0x6b82e6c9u, 0x00000000u}, + {0x7fffffffu, 0xed1d9d16u, 0xfb759e0fu, 0x00000000u}, + {0x00000008u, 0x8350a73fu, 0x849acfb5u, 0x00000000u}, + {0xffffffffu, 0x067f0cfcu, 0x93550840u, 0x00000000u}, + {0x00000003u, 0xc40d31b5u, 0x2106119eu, 0x00000000u}, + {0x00000000u, 0xf675299bu, 0xb464c554u, 0x00000000u}, + {0x00000010u, 0xe306f0c4u, 0xaba4c70eu, 0x00000000u}, + {0x00000001u, 0xb7a1774fu, 0x9f4fb02bu, 0x00000000u}, + {0x00000007u, 0x78549014u, 0x2475263cu, 0x00000000u}, + {0x00000008u, 0xe261e34au, 0x3a9afa39u, 0x00000000u}, + {0x00000001u, 0xa06d7a69u, 0xb3618e1cu, 0x00000000u}, + {0x00000008u, 0xac9f21dfu, 0x782afe6bu, 0x00000000u}, + {0xfffffff8u, 0xedf165dau, 0xf5a9ca5fu, 0x00000000u}, + {0x00000010u, 0x4041f5eeu, 0x8f4ecb4fu, 0x00000000u}, + {0x00000007u, 0xab74fe57u, 0xff8b2a6au, 0x00000000u}, + {0x00000004u, 0x8c53765fu, 0xccbfd2ecu, 0x00000000u}, + {0x80000000u, 0x807c7e30u, 0xb2c55523u, 0x00000000u}, + {0x00000008u, 0xce0d8d87u, 0xe26b524au, 0x00000000u}, + {0x00000007u, 0x6edd77d8u, 0x33d91808u, 0x00000000u}, + {0x00000004u, 0xd4f398eeu, 0x44df8a13u, 0x00000000u}, + {0x00000000u, 0x0879d955u, 0x265c28eau, 0x00000000u}, + {0x80000000u, 0xe1cd7be8u, 0x98e6e5b7u, 0x00000000u} +}; + +constexpr std::uint32_t Expected32[32][5] = { + {0x62dabc70u, 0x51c91c70u, 0x51c9bc71u, 0xa39378e0u, 0xe57f7b0bu}, + {0x15ad2ffcu, 0xf38b9ffcu, 0xf38b2ffdu, 0xdaa18ff4u, 0x6c75353bu}, + {0xd8e1c797u, 0xa5ae0797u, 0xa5aec798u, 0x96bb1e5cu, 0x71d3d897u}, + {0x38389249u, 0xf3f45249u, 0xf3f4924au, 0xf3f49249u, 0x6d8bc3efu}, + {0x2699accbu, 0xe2556ccbu, 0xe255acccu, 0x6bac5ff7u, 0x5a9c202fu}, + {0x29561482u, 0xe512d482u, 0xe5121483u, 0xaf363d86u, 0xf214c83fu}, + {0xe35d9504u, 0x9f195504u, 0x9f199505u, 0xba997e18u, 0xde3fdedbu}, + {0xaff16be2u, 0x6badabe2u, 0x6bad6be3u, 0xd75ad7c4u, 0x87ef333fu}, + {0xd6d7de8fu, 0x92931e8fu, 0x9293de90u, 0x6f77375au, 0x02024917u}, + {0x1beba3ccu, 0xd7a763ccu, 0xd7a7a3cdu, 0xe5957a94u, 0xfcc705fbu}, + {0x0b1175e9u, 0xc6cdb5e9u, 0xc6cd75eau, 0x8d9aebd2u, 0x47e8ff77u}, + {0xc1efe929u, 0x7dab2929u, 0x7dabe92au, 0xed5f4948u, 0xf1813377u}, + {0x0a2c18fau, 0xc5e8d8fau, 0xc5e818fbu, 0xa37095dcu, 0x75be2e47u}, + {0x81fc9a0au, 0x70eb3a0au, 0x70eb9a0bu, 0x16713646u, 0x5dd8bc7fu}, + {0x22c81f93u, 0x00a6af93u, 0x00a61f94u, 0x02987e4cu, 0xdfb490b7u}, + {0x47dd4e71u, 0x14aa8e71u, 0x14aa4e72u, 0x67538835u, 0x7fd61fa7u}, + {0x8dcc9310u, 0x49885310u, 0x49889311u, 0xb9337260u, 0xb3b690e7u}, + {0x5d5de93au, 0x1919293au, 0x1919e93bu, 0x969b775cu, 0x124b25ffu}, + {0x4734ee99u, 0x02f02e99u, 0x02f0ee9au, 0x08d2cbcbu, 0xa99a30afu}, + {0x285a3207u, 0xe416f207u, 0xe4163208u, 0x3c9b5e31u, 0x320142d7u}, + {0xc1c76e77u, 0x7d83ae77u, 0x7d836e78u, 0xfb06dceeu, 0x369f1f97u}, + {0x94b5950eu, 0x5071550eu, 0x5071950fu, 0xa0e32a1cu, 0xe39fdef3u}, + {0xa9f40a2du, 0x65b0ca2du, 0x65b00a2eu, 0x65b00a2du, 0x55576de3u}, + {0x9d441ac9u, 0x5900dac9u, 0x59001acau, 0xbd0085edu, 0xbb3e0d77u}, + {0x43fc8309u, 0xffb84309u, 0xffb8830au, 0xffb88309u, 0x61467fabu}, + {0x9f908012u, 0x5b4c4012u, 0x5b4c8013u, 0xc87e805au, 0x28ae799du}, + {0x442d76abu, 0xffe9b6abu, 0xffe976acu, 0xffbc6401u, 0x61e594efu}, + {0xe785738cu, 0xa341b38cu, 0xa341738du, 0xd388b548u, 0x1f289927u}, + {0x79461955u, 0x5724a955u, 0x57241956u, 0xae4832aau, 0xfec9ef9fu}, + {0xabe09c1cu, 0x89be2c1cu, 0x89be9c1du, 0x4df4e0e0u, 0x8d4a4181u}, + {0xb95f4c83u, 0x751b8c83u, 0x751b4c84u, 0x49887e8fu, 0x7634b8c7u}, + {0xd84449bbu, 0xa51189bbu, 0xa51149bcu, 0x395670a7u, 0xf20e21f7u} +}; +constexpr std::uint32_t Expected64[64][5] = { + {0x62dabc70u, 0x51c91c70u, 0x51c9bc71u, 0xa39378e0u, 0xe57f7b0bu}, + {0x15ad2ffcu, 0xf38b9ffcu, 0xf38b2ffdu, 0xdaa18ff4u, 0x6c75353bu}, + {0xd8e1c797u, 0xa5ae0797u, 0xa5aec798u, 0x96bb1e5cu, 0x71d3d897u}, + {0x38389249u, 0xf3f45249u, 0xf3f4924au, 0xf3f49249u, 0x6d8bc3efu}, + {0x2699accbu, 0xe2556ccbu, 0xe255acccu, 0x6bac5ff7u, 0x5a9c202fu}, + {0x29561482u, 0xe512d482u, 0xe5121483u, 0xaf363d86u, 0xf214c83fu}, + {0xe35d9504u, 0x9f195504u, 0x9f199505u, 0xba997e18u, 0xde3fdedbu}, + {0xaff16be2u, 0x6badabe2u, 0x6bad6be3u, 0xd75ad7c4u, 0x87ef333fu}, + {0xd6d7de8fu, 0x92931e8fu, 0x9293de90u, 0x6f77375au, 0x02024917u}, + {0x1beba3ccu, 0xd7a763ccu, 0xd7a7a3cdu, 0xe5957a94u, 0xfcc705fbu}, + {0x0b1175e9u, 0xc6cdb5e9u, 0xc6cd75eau, 0x8d9aebd2u, 0x47e8ff77u}, + {0xc1efe929u, 0x7dab2929u, 0x7dabe92au, 0xed5f4948u, 0xf1813377u}, + {0x0a2c18fau, 0xc5e8d8fau, 0xc5e818fbu, 0xa37095dcu, 0x75be2e47u}, + {0x81fc9a0au, 0x70eb3a0au, 0x70eb9a0bu, 0x16713646u, 0x5dd8bc7fu}, + {0x22c81f93u, 0x00a6af93u, 0x00a61f94u, 0x02987e4cu, 0xdfb490b7u}, + {0x47dd4e71u, 0x14aa8e71u, 0x14aa4e72u, 0x67538835u, 0x7fd61fa7u}, + {0x8dcc9310u, 0x49885310u, 0x49889311u, 0xb9337260u, 0xb3b690e7u}, + {0x5d5de93au, 0x1919293au, 0x1919e93bu, 0x969b775cu, 0x124b25ffu}, + {0x4734ee99u, 0x02f02e99u, 0x02f0ee9au, 0x08d2cbcbu, 0xa99a30afu}, + {0x285a3207u, 0xe416f207u, 0xe4163208u, 0x3c9b5e31u, 0x320142d7u}, + {0xc1c76e77u, 0x7d83ae77u, 0x7d836e78u, 0xfb06dceeu, 0x369f1f97u}, + {0x94b5950eu, 0x5071550eu, 0x5071950fu, 0xa0e32a1cu, 0xe39fdef3u}, + {0xa9f40a2du, 0x65b0ca2du, 0x65b00a2eu, 0x65b00a2du, 0x55576de3u}, + {0x9d441ac9u, 0x5900dac9u, 0x59001acau, 0xbd0085edu, 0xbb3e0d77u}, + {0x43fc8309u, 0xffb84309u, 0xffb8830au, 0xffb88309u, 0x61467fabu}, + {0x9f908012u, 0x5b4c4012u, 0x5b4c8013u, 0xc87e805au, 0x28ae799du}, + {0x442d76abu, 0xffe9b6abu, 0xffe976acu, 0xffbc6401u, 0x61e594efu}, + {0xe785738cu, 0xa341b38cu, 0xa341738du, 0xd388b548u, 0x1f289927u}, + {0x79461955u, 0x5724a955u, 0x57241956u, 0xae4832aau, 0xfec9ef9fu}, + {0xabe09c1cu, 0x89be2c1cu, 0x89be9c1du, 0x4df4e0e0u, 0x8d4a4181u}, + {0xb95f4c83u, 0x751b8c83u, 0x751b4c84u, 0x49887e8fu, 0x7634b8c7u}, + {0xd84449bbu, 0xa51189bbu, 0xa51149bcu, 0x395670a7u, 0xf20e21f7u}, + {0xd8160470u, 0x93d2c470u, 0x93d20471u, 0x93d20470u, 0xed119b0bu}, + {0x1d4d0b89u, 0xd909cb89u, 0xd9090b8au, 0xef3f50bfu, 0xc67da4efu}, + {0xa1551a9du, 0x5d11da9du, 0x5d111a9eu, 0x17334fd7u, 0xd61de0bfu}, + {0x5b7b1472u, 0x1737d472u, 0x17371473u, 0x2e6e28e4u, 0x633ccfbfu}, + {0xda359a51u, 0x95f15a51u, 0x95f19a52u, 0x83a99de6u, 0xb5d44d1bu}, + {0x224679e1u, 0xde02b9e1u, 0xde0279e2u, 0xde0279e1u, 0xda506d4fu}, + {0xa3c2ba05u, 0x81a00a05u, 0x81a0ba06u, 0x8b651623u, 0xd781bc07u}, + {0x2227a361u, 0xdde36361u, 0xdde3a362u, 0x99aaea23u, 0xf50597f7u}, + {0xc1f8304du, 0x7db4f04du, 0x7db4304eu, 0x6fed521bu, 0xf07ca537u}, + {0xca93a3f3u, 0x864f63f3u, 0x864fa3f4u, 0x193e8fccu, 0xbb670507u}, + {0xafc6e6c9u, 0x6b8226c9u, 0x6b82e6cau, 0xd705cd92u, 0x9311cd77u}, + {0x3fb99e0fu, 0xfb755e0fu, 0xfb759e10u, 0xe0375269u, 0x4f8ff2f7u}, + {0xc8decfb5u, 0x849a0fb5u, 0x849acfb6u, 0x24d67da8u, 0x62935ab7u}, + {0xd7990840u, 0x9355c840u, 0x93550841u, 0xe0a92940u, 0x1e6f25e7u}, + {0x654a119eu, 0x2106d19eu, 0x2106119fu, 0xc62469b4u, 0x3b5eb807u}, + {0xc575c554u, 0xb4646554u, 0xb464c555u, 0xd1931550u, 0x73ca9f9bu}, + {0xefe8c70eu, 0xaba4070eu, 0xaba4c70fu, 0x5a37e346u, 0xc97ef7a5u}, + {0xc171b02bu, 0x9f4f002bu, 0x9f4fb02cu, 0xfa7d8158u, 0xa0a819efu}, + {0x68b9263cu, 0x2475e63cu, 0x2475263du, 0xb649bf2cu, 0x338d123bu}, + {0x7edefa39u, 0x3a9a3a39u, 0x3a9afa3au, 0xafd0eeabu, 0xeb028dcbu}, + {0xd5838e1cu, 0xb3613e1cu, 0xb3618e1du, 0x66c31c38u, 0x41881ccfu}, + {0xbc6efe6bu, 0x782a3e6bu, 0x782afe6cu, 0xc157f358u, 0x42f27367u}, + {0x39edca5fu, 0xf5a90a5fu, 0xf5a9ca60u, 0xe0fd5f1du, 0x74fea3b7u}, + {0xd392cb4fu, 0x8f4e0b4fu, 0x8f4ecb50u, 0xeb278f29u, 0xa0f23657u}, + {0x43cf2a6au, 0xff8bea6au, 0xff8b2a6bu, 0xfc595350u, 0x5c752363u}, + {0x1103d2ecu, 0xccbf12ecu, 0xccbfd2edu, 0x65fe9760u, 0x90a6c17bu}, + {0xf7095523u, 0xb2c59523u, 0xb2c55524u, 0xb2c55523u, 0x076af3d7u}, + {0x26af524au, 0xe26b924au, 0xe26b524bu, 0x135a9250u, 0x5ae1dd2du}, + {0x781d1808u, 0x33d9d808u, 0x33d91809u, 0x33d91808u, 0x6d3deaebu}, + {0x89238a13u, 0x44df4a13u, 0x44df8a14u, 0xe21cc685u, 0xfc783cb7u}, + {0x376d28eau, 0x265c88eau, 0x265c28ebu, 0xe628f57cu, 0x3b292963u}, + {0xdd2ae5b7u, 0x98e625b7u, 0x98e6e5b8u, 0x98e6e5b7u, 0x1db34197u} +}; + +constexpr const char* Names[Checked] = {"jump_table", "jump_table_offset", "skip_forward", "loop_add_negative", "loop_sub"}; + +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(std::span code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + 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{{waveSize, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + target, + {0, 0, 0, 128} + }; + request.useCache = false; + return ShaderRecompiler::Recompile(request); +} + +void Run(AgcDriver::VulkanDevice& device, std::span code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + for (std::uint32_t tid = 0; tid < MaxThreads; ++tid) std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + const auto result = Compile(code, waveSize, target); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void CheckReversedSubtractRefused(AgcDriver::VulkanDevice& device) { + std::string refusal; + try { + static_cast(Compile(ReversedSubtractCode, 32, device.ComputeTarget(32))); + } catch (const std::exception& error) { + refusal = error.what(); + } + Require(refusal.find("unsupported dynamic s_setpc_b64") != std::string::npos, "s_setpc_b64 after s_sub_u32 with the immediate first was not refused"); +} + +template +void Check(const std::uint32_t (&expected)[Lanes][Checked]) { + for (std::uint32_t tid = 0; tid < Lanes; ++tid) { + for (std::uint32_t index = 0; index < Checked; ++index) { + const auto actual = Output[tid * Results + index]; + Require(actual == expected[tid][index], "setpc wave" + std::to_string(Lanes) + ": lane " + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device, Wave32Code, 32, device->ComputeTarget(32)); + Check(Expected32); + Run(*device, Wave64Code, 64, device->Target()); + Check(Expected64); + Run(*device, Wave64Code, 64, device->ComputeTarget(32)); + Check(Expected64); + CheckReversedSubtractRefused(*device); + std::puts("setpc tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp b/core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp index 9d9ef50cb..bfbd51cce 100644 --- a/core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp +++ b/core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp @@ -126,7 +126,7 @@ bool resolveSetpcTarget(const RdnaProgram& program, std::uint32_t setpcIndex, st if (getProgramCounter.op == RdnaOpcode::SGetpcB64 && getProgramCounter.destination.kind == RdnaOperandKind::ScalarRegister && getProgramCounter.destination.reg == pcRegister && arithmetic.destination.kind == RdnaOperandKind::ScalarRegister && arithmetic.destination.reg == pcRegister) { std::uint32_t immediate = 0; const bool adds = arithmetic.op == RdnaOpcode::SAddU32 || arithmetic.op == RdnaOpcode::SAddI32; - const bool subtracts = arithmetic.op == RdnaOpcode::SSubU32 || arithmetic.op == RdnaOpcode::SSubI32; + const bool subtracts = (arithmetic.op == RdnaOpcode::SSubU32 || arithmetic.op == RdnaOpcode::SSubI32) && isRegister(arithmetic.source0, RdnaOperandKind::ScalarRegister, pcRegister); if ((adds || subtracts) && (isRegister(arithmetic.source0, RdnaOperandKind::ScalarRegister, pcRegister) || isRegister(arithmetic.source1, RdnaOperandKind::ScalarRegister, pcRegister)) && (isImmediate(arithmetic.source0, immediate) || isImmediate(arithmetic.source1, immediate))) { const auto base = instructionEndProgramCounter(getProgramCounter); target = (adds ? base + immediate : base - immediate) & ~3u; From aff00b8cdedd3446cd7f069004e1521de21b3275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 04:56:34 +0200 Subject: [PATCH 074/164] fix(shader): wave-wide exec and vcc branch conditions s_cbranch_execz/execnz/vccz/vccnz test the whole wave mask on hardware, but the single-lane path branched on each lane's own bit, so inactive lanes took the other side and diverged in scalar state. Branch on a wave ballot instead, and execute the untested SOPP ops (cdbg branches, s_clause, s_inst_prefetch, s_setprio, s_waitcnt_depctr, s_ttracedata) against values measured on hardware. Co-Authored-By: Claude --- .../tests/execution/ScalarProgramFlow.cpp | 321 ++++++++++++++++++ .../include/SpirvBackend/SpirvAnalysis.hpp | 1 + .../SpirvBackend/src/SpirvAnalysis.cpp | 9 + .../SpirvBackend/src/SpirvFlowEmitter.cpp | 33 +- 4 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramFlow.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramFlow.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramFlow.cpp new file mode 100644 index 000000000..2ffaf0609 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramFlow.cpp @@ -0,0 +1,321 @@ +#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 MaxThreads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Checked = 14; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Wave32Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeadbeef, 0x7e1602ff, + 0xdeadbeef, 0x7e1802ff, 0xdeadbeef, 0x7e1a02ff, 0xdeadbeef, 0x7e1c02ff, 0xdeadbeef, 0x7e1e02ff, + 0xdeadbeef, 0x7e2002ff, 0xdeadbeef, 0x7e2202ff, 0xdeadbeef, 0x7e2402ff, 0xdeadbeef, 0x7e2602ff, + 0xdeadbeef, 0x7e2802ff, 0xdeadbeef, 0x7e2a02ff, 0xdeadbeef, 0x7e2c02ff, 0xdeadbeef, 0x7e2e02ff, + 0xdeadbeef, 0x7e180505, 0xd5480008, 0x02050104, 0x7d8a1080, 0xbe8e3c6a, 0xbf890003, 0x890cff0c, + 0x00005a5a, 0x7e120280, 0x800cff0c, 0x00000100, 0x887e0e7e, 0x7e14020c, 0x7e180505, 0xd5480008, + 0x02050304, 0x7d8a1080, 0xbe8e3c6a, 0xbf890003, 0x890cff0c, 0x00005a5a, 0x7e120280, 0x800cff0c, + 0x00000200, 0x887e0e7e, 0x7e16020c, 0x7e180505, 0xd5480008, 0x02050504, 0x7d8a1080, 0xbe8e3c6a, + 0xbf890003, 0x890cff0c, 0x00005a5a, 0x7e120280, 0x800cff0c, 0x00000300, 0x887e0e7e, 0x7e18020c, + 0x7e180506, 0xd5480008, 0x02050104, 0x7d8a1080, 0xbe8e3c6a, 0xbf880003, 0x8f0c810c, 0x800cff0c, + 0x00001000, 0x890cb30c, 0x887e0e7e, 0x7e1a020c, 0x7e180506, 0xd5480008, 0x02050304, 0x7d8a1080, + 0xbe8e3c6a, 0xbf880003, 0x8f0c810c, 0x800cff0c, 0x00002000, 0x890cb30c, 0x887e0e7e, 0x7e1c020c, + 0x7e180506, 0xd5480008, 0x02050504, 0x7d8a1080, 0xbe8e3c6a, 0xbf880003, 0x8f0c810c, 0x800cff0c, + 0x00003000, 0x890cb30c, 0x887e0e7e, 0x7e1e020c, 0x7e180506, 0xd5480008, 0x02050104, 0x7d8a1080, + 0xbf870002, 0x800cff0c, 0x00010000, 0x890cff0c, 0x00000055, 0x7e20020c, 0x7e180506, 0xd5480008, + 0x02050304, 0x7d8a1080, 0xbf870002, 0x800cff0c, 0x00020000, 0x890cff0c, 0x00000055, 0x7e22020c, + 0x7e180506, 0xd5480008, 0x02050504, 0x7d8a1080, 0xbf870002, 0x800cff0c, 0x00030000, 0x890cff0c, + 0x00000055, 0x7e24020c, 0x7e180506, 0xd5480008, 0x02050104, 0x7d8a1080, 0xbf860002, 0x800cff0c, + 0x00010000, 0x890cff0c, 0x00000077, 0x7e26020c, 0x7e180506, 0xd5480008, 0x02050304, 0x7d8a1080, + 0xbf860002, 0x800cff0c, 0x00020000, 0x890cff0c, 0x00000077, 0x7e28020c, 0x7e180506, 0xd5480008, + 0x02050504, 0x7d8a1080, 0xbf860002, 0x800cff0c, 0x00030000, 0x890cff0c, 0x00000077, 0x7e2a020c, + 0xbe8c03ff, 0x00001000, 0xbf970001, 0x800c810c, 0xbf980001, 0x800c820c, 0xbf990001, 0x800c840c, + 0xbf9a0001, 0x800c880c, 0x4a2c0a0c, 0xbf8f0002, 0xbfa00001, 0xbfa10001, 0x4a100d05, 0x3a120d05, + 0xbfa3ffe3, 0xbefc03ff, 0x00001234, 0xbf960000, 0xbf8f0000, 0x4c2e1308, 0xe0781000, 0x80010a03, + 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +alignas(256) constexpr std::array Wave64Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeadbeef, 0x7e1602ff, + 0xdeadbeef, 0x7e1802ff, 0xdeadbeef, 0x7e1a02ff, 0xdeadbeef, 0x7e1c02ff, 0xdeadbeef, 0x7e1e02ff, + 0xdeadbeef, 0x7e2002ff, 0xdeadbeef, 0x7e2202ff, 0xdeadbeef, 0x7e2402ff, 0xdeadbeef, 0x7e2602ff, + 0xdeadbeef, 0x7e2802ff, 0xdeadbeef, 0x7e2a02ff, 0xdeadbeef, 0x7e2c02ff, 0xdeadbeef, 0x7e2e02ff, + 0xdeadbeef, 0x7e180505, 0xd5480008, 0x02050104, 0x7d8a1080, 0xbe8e246a, 0xbf890003, 0x890cff0c, + 0x00005a5a, 0x7e120280, 0x800cff0c, 0x00000100, 0x88fe0e7e, 0x7e14020c, 0x7e180505, 0xd5480008, + 0x02050304, 0x7d8a1080, 0xbe8e246a, 0xbf890003, 0x890cff0c, 0x00005a5a, 0x7e120280, 0x800cff0c, + 0x00000200, 0x88fe0e7e, 0x7e16020c, 0x7e180505, 0xd5480008, 0x02050504, 0x7d8a1080, 0xbe8e246a, + 0xbf890003, 0x890cff0c, 0x00005a5a, 0x7e120280, 0x800cff0c, 0x00000300, 0x88fe0e7e, 0x7e18020c, + 0x7e180506, 0xd5480008, 0x02050104, 0x7d8a1080, 0xbe8e246a, 0xbf880003, 0x8f0c810c, 0x800cff0c, + 0x00001000, 0x890cb30c, 0x88fe0e7e, 0x7e1a020c, 0x7e180506, 0xd5480008, 0x02050304, 0x7d8a1080, + 0xbe8e246a, 0xbf880003, 0x8f0c810c, 0x800cff0c, 0x00002000, 0x890cb30c, 0x88fe0e7e, 0x7e1c020c, + 0x7e180506, 0xd5480008, 0x02050504, 0x7d8a1080, 0xbe8e246a, 0xbf880003, 0x8f0c810c, 0x800cff0c, + 0x00003000, 0x890cb30c, 0x88fe0e7e, 0x7e1e020c, 0x7e180506, 0xd5480008, 0x02050104, 0x7d8a1080, + 0xbf870002, 0x800cff0c, 0x00010000, 0x890cff0c, 0x00000055, 0x7e20020c, 0x7e180506, 0xd5480008, + 0x02050304, 0x7d8a1080, 0xbf870002, 0x800cff0c, 0x00020000, 0x890cff0c, 0x00000055, 0x7e22020c, + 0x7e180506, 0xd5480008, 0x02050504, 0x7d8a1080, 0xbf870002, 0x800cff0c, 0x00030000, 0x890cff0c, + 0x00000055, 0x7e24020c, 0x7e180506, 0xd5480008, 0x02050104, 0x7d8a1080, 0xbf860002, 0x800cff0c, + 0x00010000, 0x890cff0c, 0x00000077, 0x7e26020c, 0x7e180506, 0xd5480008, 0x02050304, 0x7d8a1080, + 0xbf860002, 0x800cff0c, 0x00020000, 0x890cff0c, 0x00000077, 0x7e28020c, 0x7e180506, 0xd5480008, + 0x02050504, 0x7d8a1080, 0xbf860002, 0x800cff0c, 0x00030000, 0x890cff0c, 0x00000077, 0x7e2a020c, + 0xbe8c03ff, 0x00001000, 0xbf970001, 0x800c810c, 0xbf980001, 0x800c820c, 0xbf990001, 0x800c840c, + 0xbf9a0001, 0x800c880c, 0x4a2c0a0c, 0xbf8f0002, 0xbfa00001, 0xbfa10001, 0x4a100d05, 0x3a120d05, + 0xbfa3ffe3, 0xbefc03ff, 0x00001234, 0xbf960000, 0xbf8f0000, 0x4c2e1308, 0xe0781000, 0x80010a03, + 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[64][4] = { + {0x00000000u, 0x51c9bc70u, 0x80a4df5au, 0x00000000u}, + {0x00000001u, 0x8306d03bu, 0xa5aec797u, 0x00000000u}, + {0x00000001u, 0xf3f49249u, 0x1a466884u, 0x00000000u}, + {0x00000001u, 0x39292d22u, 0xe5121482u, 0x00000000u}, + {0x00000001u, 0x9f199504u, 0x8e7aa6e9u, 0x00000000u}, + {0x00000004u, 0xc88b2875u, 0x9293de8fu, 0x00000000u}, + {0x00000001u, 0xd7a7a3ccu, 0xbb049a79u, 0x00000000u}, + {0x00000001u, 0xc4a334bfu, 0x7dabe929u, 0x00000000u}, + {0x00000001u, 0xc5e818fau, 0x96263ae6u, 0x00000000u}, + {0x00000000u, 0x3d6c51e3u, 0x00a61f93u, 0x00000000u}, + {0x00000001u, 0x14aa4e71u, 0x1c593af5u, 0x00000000u}, + {0x00000000u, 0xd11745adu, 0x1919e93au, 0x00000000u}, + {0x00000000u, 0x02f0ee99u, 0xd0944996u, 0x00000000u}, + {0x00000001u, 0xaf67d461u, 0x7d836e77u, 0x00000000u}, + {0x00000001u, 0x5071950eu, 0x35d14880u, 0x00000000u}, + {0x00000000u, 0x406329bcu, 0x59001ac9u, 0x00000000u}, + {0x00000001u, 0xffb88309u, 0xede7bd0cu, 0x00000000u}, + {0x00000000u, 0xce1d62e0u, 0x60581ccau, 0x00000000u}, + {0x00000001u, 0xbef7e268u, 0x837a7935u, 0x00000000u}, + {0x00000001u, 0x1389b372u, 0xb91dddd9u, 0x00000000u}, + {0x00000000u, 0x16f44881u, 0x8eb5140fu, 0x00000000u}, + {0x00000001u, 0x4ac87fc0u, 0x49a047dcu, 0x00000000u}, + {0x00000000u, 0x24114258u, 0xe060a724u, 0x00000000u}, + {0x00000001u, 0xb437bdb5u, 0xb766ff10u, 0x00000000u}, + {0x00000001u, 0x4f1a8a1du, 0x06555096u, 0x00000000u}, + {0x00000001u, 0xb5fb12e0u, 0x5e6c9992u, 0x00000000u}, + {0x00000000u, 0x7608d942u, 0x6c167229u, 0x00000000u}, + {0x00000000u, 0x66160227u, 0xe41686cdu, 0x00000000u}, + {0x00000001u, 0x8df05f25u, 0x7f9ac098u, 0x00000000u}, + {0x00000001u, 0x1dcf884cu, 0x6ce9da66u, 0x00000000u}, + {0x00000001u, 0xcd73de00u, 0xc0099ebau, 0x00000000u}, + {0x00000001u, 0x99a46df0u, 0xe01c683eu, 0x00000000u}, + {0x00000000u, 0x65ed0de4u, 0xeab06e9bu, 0x00000000u}, + {0x00000001u, 0x423674a6u, 0xd82fb0f1u, 0x00000000u}, + {0x00000000u, 0x90624fe3u, 0xed1d9d16u, 0x00000000u}, + {0x00000001u, 0x7aac319fu, 0x8350a73fu, 0x00000000u}, + {0x00000001u, 0xce1fd3d9u, 0x067f0cfcu, 0x00000000u}, + {0x00000001u, 0x397a7623u, 0xc40d31b5u, 0x00000000u}, + {0x00000000u, 0xd1711cbdu, 0x0c83e786u, 0x00000000u}, + {0x00000001u, 0xb464c554u, 0x85f184e0u, 0x00000000u}, + {0x00000003u, 0xaba4c70eu, 0x1a42721eu, 0x00000000u}, + {0x00000001u, 0x9f4fb02bu, 0x6dedc86au, 0x00000000u}, + {0x00000000u, 0x2475263cu, 0x7b6bc3c6u, 0x00000000u}, + {0x00000001u, 0x3a9afa39u, 0xd9814559u, 0x00000000u}, + {0x00000001u, 0x1f3c42b2u, 0xa06d7a69u, 0x00000000u}, + {0x00000001u, 0x74f09af5u, 0xac9f21dfu, 0x00000000u}, + {0x00000000u, 0xb6eafff5u, 0xedf165dau, 0x00000000u}, + {0x00000001u, 0xfcbad167u, 0xfa97002cu, 0x00000000u}, + {0x00000001u, 0x4041f5eeu, 0x8f4ecb4fu, 0x00000000u}, + {0x00000000u, 0xab74fe57u, 0xff8b2a6au, 0x00000000u}, + {0x00000000u, 0x8c53765fu, 0xccbfd2ecu, 0x00000000u}, + {0x00000001u, 0x807c7e30u, 0xb2c55523u, 0x00000000u}, + {0x00000000u, 0xce0d8d87u, 0xe26b524au, 0x00000000u}, + {0x00000000u, 0x6edd77d8u, 0x33d91808u, 0x00000000u}, + {0x00000000u, 0xd4f398eeu, 0x44df8a13u, 0x00000000u}, + {0x00000000u, 0x0879d955u, 0x265c28eau, 0x00000000u}, + {0x00000001u, 0xe1cd7be8u, 0x98e6e5b7u, 0x00000000u}, + {0x00000001u, 0x754e64f4u, 0xcdb54088u, 0x00000000u}, + {0x00000001u, 0xed4d19b8u, 0x7d510557u, 0x00000000u}, + {0x00000000u, 0xebe80fa9u, 0x33288e16u, 0x00000000u}, + {0x00000000u, 0xb1515fffu, 0x7c4e4248u, 0x00000000u}, + {0x00000000u, 0x82e26123u, 0xc446ab8cu, 0x00000000u}, + {0x00000001u, 0x7f45b096u, 0xced66b44u, 0x00000000u}, + {0x00000000u, 0x8ece78b0u, 0xa9f5307cu, 0x00000000u} +}; + +constexpr std::uint32_t Expected32[32][14] = { + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x51c9cc7fu, 0x010138a0u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x8306e04au, 0x020d8026u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xf3f4a258u, 0x24880000u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x39293d31u, 0x42000804u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x9f19a513u, 0x1c310800u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xc88b3884u, 0x0106100au}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xd7a7b3dbu, 0x26090490u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xc4a344ceu, 0x89464052u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xc5e82909u, 0x084031c4u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x3d6c61f2u, 0x00482306u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x14aa5e80u, 0x281014e2u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xd11755bcu, 0x22228250u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x02f0fea8u, 0x01209120u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xaf67e470u, 0x5a0688c2u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x5071a51du, 0x20a20000u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x406339cbu, 0x80001110u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xffb89318u, 0xdb410210u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xce1d72efu, 0x80300180u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xbef7f277u, 0x04e4c040u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x1389c381u, 0x221322a0u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x16f45890u, 0x0d680002u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x4ac88fcfu, 0x91008f80u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x24115267u, 0x40000400u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xb437cdc4u, 0x684d7a20u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x4f1a9a2cu, 0x0c200028u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xb5fb22efu, 0x28d02100u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x7608e951u, 0xc800a000u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x66161236u, 0xc82c040au}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x8df06f34u, 0x1b208000u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x1dcf985bu, 0x19931088u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xcd73ee0fu, 0x80033c00u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x99a47dffu, 0x0008d060u} +}; +constexpr std::uint32_t Expected64[64][14] = { + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x51c9cc7fu, 0x010138a0u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x8306e04au, 0x020d8026u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xf3f4a258u, 0x24880000u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x39293d31u, 0x42000804u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x9f19a513u, 0x1c310800u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xc88b3884u, 0x0106100au}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xd7a7b3dbu, 0x26090490u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xc4a344ceu, 0x89464052u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xc5e82909u, 0x084031c4u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x3d6c61f2u, 0x00482306u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x14aa5e80u, 0x281014e2u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xd11755bcu, 0x22228250u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x02f0fea8u, 0x01209120u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xaf67e470u, 0x5a0688c2u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x5071a51du, 0x20a20000u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x406339cbu, 0x80001110u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xffb89318u, 0xdb410210u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xce1d72efu, 0x80300180u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xbef7f277u, 0x04e4c040u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x1389c381u, 0x221322a0u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x16f45890u, 0x0d680002u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x4ac88fcfu, 0x91008f80u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x24115267u, 0x40000400u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xb437cdc4u, 0x684d7a20u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x4f1a9a2cu, 0x0c200028u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xb5fb22efu, 0x28d02100u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x7608e951u, 0xc800a000u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x66161236u, 0xc82c040au}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x8df06f34u, 0x1b208000u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x1dcf985bu, 0x19931088u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xcd73ee0fu, 0x80033c00u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x99a47dffu, 0x0008d060u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x65ed1df3u, 0xc1401900u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x423684b5u, 0x804c6140u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x90625ff2u, 0x00001a04u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x7aac41aeu, 0x0400423eu}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xce1fe3e8u, 0x0c3e01b0u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x397a8632u, 0x00106042u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xd1712cccu, 0x00020908u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xb464d563u, 0x08c10880u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xaba4d71du, 0x1400841cu}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x9f4fc03au, 0x1a9b0054u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x2475364bu, 0x40c20408u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x3a9b0a48u, 0x31008032u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x1f3c52c1u, 0x00588440u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x74f0ab04u, 0x492001aau}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xb6eb1004u, 0x49c0cba0u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xfcbae176u, 0xf1240048u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x404205fdu, 0x0081829cu}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xab750e66u, 0x56005484u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x8c53866eu, 0x1826a498u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x807c8e3fu, 0x0088a840u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xce0d9d96u, 0x84120004u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x6edd87e7u, 0x45b22010u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xd4f3a8fdu, 0x89a71004u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x0879e964u, 0x00b01080u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xe1cd8bf7u, 0x0188c340u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x754e7503u, 0x8a088100u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xed4d29c7u, 0xda820220u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xebe81fb8u, 0x46501c00u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xb151700eu, 0x60808490u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x82e27132u, 0x00844200u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x7f45c0a5u, 0x9c884008u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x8ece88bfu, 0x11886060u} +}; + +constexpr const char* Names[Checked] = {"execnz_partial", "execnz_upper_half", "execnz_lower_lane", "execz_partial", "execz_upper_half", "execz_lower_lane", "vccnz_partial", "vccnz_upper_half", "vccnz_lower_lane", "vccz_partial", "vccz_upper_half", "vccz_lower_lane", "cdbg_not_taken", "hints"}; +constexpr bool CrossLane[Checked] = {true, true, true, true, true, true, true, true, true, true, true, true, false, false}; + +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(std::span code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + 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{{waveSize, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + target, + {0, 0, 0, 128} + }; + request.useCache = false; + return ShaderRecompiler::Recompile(request); +} + +void Run(AgcDriver::VulkanDevice& device, std::span code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + for (std::uint32_t tid = 0; tid < MaxThreads; ++tid) std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + const auto result = Compile(code, waveSize, target); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +template +void Check(const std::uint32_t (&expected)[Lanes][Checked], bool wholeWave) { + for (std::uint32_t tid = 0; tid < Lanes; ++tid) { + for (std::uint32_t index = 0; index < Checked; ++index) { + if (CrossLane[index] && !wholeWave) continue; + const auto actual = Output[tid * Results + index]; + Require(actual == expected[tid][index], "program flow wave" + std::to_string(Lanes) + ": lane " + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const bool wholeWave = device->Target().subgroupSize >= 32u; + if (!wholeWave) std::printf("subgroup size %u cannot hold a wave32, checking only the columns without a wave-wide branch\n", device->Target().subgroupSize); + Run(*device, Wave32Code, 32, device->ComputeTarget(32)); + Check(Expected32, wholeWave); + Run(*device, Wave64Code, 64, device->Target()); + Check(Expected64, wholeWave); + Run(*device, Wave64Code, 64, device->ComputeTarget(32)); + Check(Expected64, wholeWave); + std::puts("scalar program flow tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp index 78ab61204..2a2437dc8 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp @@ -29,6 +29,7 @@ struct SpirvRequirements { [[nodiscard]] SpirvRequirements AnalyzeProgramRequirements(const IrProgram& program); [[nodiscard]] std::unordered_set WaveUniformValues(const IrProgram& program); +[[nodiscard]] bool IsWaveMaskBranch(BranchCondition condition); } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp index e417aee83..6f0d62aa0 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp @@ -194,9 +194,18 @@ SpirvRequirements AnalyzeProgramRequirements(const IrProgram& program) { } } } + for (const auto& info : program.Metadata().blockInfo) { + if (info.terminator.kind == TerminatorKind::ConditionalBranch && IsWaveMaskBranch(info.terminator.condition)) { + requirements.subgroupBallot = true; + } + } return requirements; } +bool IsWaveMaskBranch(BranchCondition condition) { + return condition == BranchCondition::ExecZero || condition == BranchCondition::ExecNonZero || condition == BranchCondition::VccZero || condition == BranchCondition::VccNonZero; +} + std::unordered_set WaveUniformValues(const IrProgram& program) { std::unordered_map varying; const auto isVarying = [&](const IrValue* value) { diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp index 28f7bd86b..0a1f11a06 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp @@ -1,6 +1,7 @@ #include "SpirvBackend/SpirvFlowEmitter.hpp" #include "SpirvBackend/SpirvBda.hpp" #include "SpirvBackend/SpirvEmitterInstructions.hpp" +#include "SpirvBackend/SpirvMemory/SpirvSubgroup.hpp" #include #include #include @@ -119,8 +120,38 @@ bool IsContinueTarget(const IrProgram& program, std::uint32_t block) { return false; } +std::uint32_t EmitWaveAny(SpirvEmitterState& state, std::uint32_t predicate) { + const auto ballot = state.module.AllocateId(); + state.module.AddFunction(spv::OpGroupNonUniformBallot, TypeU32Vector(state, 4u), ballot, ConstantU32(state, spv::ScopeSubgroup), predicate); + const auto wave = EmitWaveBallot(state, ballot); + const auto low = state.module.AllocateId(); + const auto high = state.module.AllocateId(); + const auto result = state.module.AllocateId(); + state.module.AddFunction(spv::OpCompositeExtract, TypeU32(state), low, wave, 0u); + state.module.AddFunction(spv::OpCompositeExtract, TypeU32(state), high, wave, 1u); + state.module.AddFunction(spv::OpINotEqual, TypeBool(state), result, EmitBinaryU32(state, spv::OpBitwiseOr, low, high), ConstantU32(state, 0u)); + return result; +} + +std::uint32_t EmitWaveMaskBranchCondition(SpirvValueEmitContext& ctx, const BlockInfo& info) { + auto& state = ctx.state; + const auto kind = info.terminator.condition; + if (kind == BranchCondition::ExecNonZero || kind == BranchCondition::VccNonZero) { + return EmitWaveAny(state, ctx.Def(info.condition)); + } + const auto clear = state.module.AllocateId(); + const auto result = state.module.AllocateId(); + state.module.AddFunction(spv::OpLogicalNot, TypeBool(state), clear, ctx.Def(info.condition)); + state.module.AddFunction(spv::OpLogicalNot, TypeBool(state), result, EmitWaveAny(state, clear)); + return result; +} + std::uint32_t EmitBranchCondition(SpirvValueEmitContext& ctx, const BlockInfo& info) { - if (ctx.otherHalf == nullptr || info.terminator.condition == BranchCondition::ScalarInstruction || info.terminator.condition == BranchCondition::GotoVariable || info.terminator.condition == BranchCondition::IndirectTarget) { + const auto condition = info.terminator.condition; + if (ctx.otherHalf == nullptr && IsWaveMaskBranch(condition)) { + return EmitWaveMaskBranchCondition(ctx, info); + } + if (ctx.otherHalf == nullptr || condition == BranchCondition::ScalarInstruction || condition == BranchCondition::GotoVariable || condition == BranchCondition::IndirectTarget) { return ctx.Def(info.condition); } auto& state = ctx.state; From a15a21c2c6967ad777c4b28217428e90d76cce54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 23:06:46 +0200 Subject: [PATCH 075/164] feat(shader): implement clamp on v_mad_u64_u32 and v_mad_i64_i32 Co-Authored-By: Claude --- .../tests/execution/Vop3MadClamp.cpp | 167 ++++++++++++++++++ .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 4 +- .../Translation/src/IntegerInstructions.cpp | 18 +- 3 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Vop3MadClamp.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3MadClamp.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3MadClamp.cpp new file mode 100644 index 000000000..53aef39dc --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3MadClamp.cpp @@ -0,0 +1,167 @@ +#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 +#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, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd577880a, 0x041a0b04, 0xd501000c, + 0x00210280, 0xd576880d, 0x041a0b04, 0xd501000f, 0x00210280, 0xe0701000, 0x80010a03, 0xe0701004, + 0x80010b03, 0xe0701008, 0x80010c03, 0xe070100c, 0x80010d03, 0xe0701010, 0x80010e03, 0xe0701014, + 0x80010f03, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000001u, 0x00000001u, 0xfffffffeu, 0xffffffffu}, + {0x00000001u, 0x00000001u, 0xffffffffu, 0xffffffffu}, + {0x00000002u, 0x00000001u, 0xfffffffeu, 0xffffffffu}, + {0x00000001u, 0x00000001u, 0xfffffffeu, 0x7fffffffu}, + {0x00000001u, 0x00000001u, 0xffffffffu, 0x7fffffffu}, + {0x00000001u, 0xffffffffu, 0x00000001u, 0x80000000u}, + {0x00000001u, 0xffffffffu, 0x00000000u, 0x80000000u}, + {0x00000002u, 0xffffffffu, 0x00000001u, 0x80000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0x00000000u, 0x40000000u}, + {0x80000000u, 0x80000000u, 0xffffffffu, 0x3fffffffu}, + {0x7fffffffu, 0x80000000u, 0x00000000u, 0xc0000000u}, + {0x7fffffffu, 0x80000000u, 0xffffffffu, 0xbfffffffu}, + {0x7fffffffu, 0x7fffffffu, 0xffffffffu, 0x7fffffffu}, + {0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu}, + {0x00000000u, 0x00000000u, 0xffffffffu, 0x7fffffffu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x00000001u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x00000001u, 0x80000000u, 0xffffffffu, 0xffffffffu}, + {0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0xffffffffu, 0xffffffffu}, + {0x80000000u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x00000001u, 0xffffffffu, 0xffffffffu}, + {0x80000000u, 0x00000001u, 0x00000000u, 0x80000000u}, + {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}, + {0x80000000u, 0x80000001u, 0x00000000u, 0xc0000000u}, + {0x80000001u, 0xffffffffu, 0xffffffffu, 0x7fffffffu}, + {0xfffffffeu, 0xfffffffeu, 0x00000000u, 0x40000000u}, + {0x3ceb3ffdu, 0x97b75092u, 0x8b529b4au, 0x21636369u}, + {0x5eb561a4u, 0xea7b5bf5u, 0x9a9a80fdu, 0x795b929eu} +}; +constexpr std::uint32_t Expected[32][6] = { + {0xffffffffu, 0xffffffffu, 0x00000001u, 0xffffffffu, 0xffffffffu, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x00000001u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x00000001u}, + {0xffffffffu, 0x7fffffffu, 0x00000000u, 0xffffffffu, 0x7fffffffu, 0x00000000u}, + {0xffffffffu, 0x7fffffffu, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x00000001u, 0x00000000u, 0x80000001u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x00000001u, 0xffffffffu, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x00000001u, 0xffffffffu, 0x80000001u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000001u, 0x00000000u, 0x00000000u, 0x00000001u, 0xfffffffeu, 0x00000000u}, + {0xffffffffu, 0x7fffffffu, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0xffffffffu, 0x7fffffffu, 0x00000000u, 0xffffffffu, 0x7fffffffu, 0x00000000u}, + {0x80000000u, 0x80000000u, 0x00000001u, 0x80000000u, 0xffffffffu, 0x00000000u}, + {0x7fffffffu, 0x80000000u, 0x00000001u, 0x7fffffffu, 0xffffffffu, 0x00000000u}, + {0xffffffffu, 0x7fffffffu, 0x00000000u, 0x00000000u, 0xbfffffffu, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x00000001u}, + {0xffffffffu, 0x7fffffffu, 0x00000000u, 0xffffffffu, 0x7fffffffu, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x00000001u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0xffffffffu, 0x00000001u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x7fffffffu, 0xffffffffu, 0x00000001u, 0xffffffffu, 0xffffffffu, 0x00000001u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xffffffffu, 0xffffffffu, 0x00000001u, 0xffffffffu, 0xffffffffu, 0x00000000u}, + {0x80000000u, 0xffffffffu, 0x00000001u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x7fffffffu, 0xffffffffu, 0x00000001u, 0xffffffffu, 0xffffffffu, 0x00000001u}, + {0x00000000u, 0x80000000u, 0x00000001u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0xc0000000u, 0x00000001u, 0x00000000u, 0xc0000000u, 0x00000000u}, + {0x80000000u, 0xffffffffu, 0x00000001u, 0xffffffffu, 0xffffffffu, 0x00000001u}, + {0xffffffffu, 0x7fffffffu, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x00000001u}, + {0x00000004u, 0x40000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x00000001u}, + {0xc6572994u, 0x08928581u, 0x00000000u, 0xc6572994u, 0x457dc57eu, 0x00000000u}, + {0x89b23ef1u, 0x71659f65u, 0x00000000u, 0x89b23ef1u, 0xd01b0109u, 0x00000000u} +}; +constexpr const char* Names[6] = {"i64 clamp lo", "i64 clamp hi", "i64 clamp carry", "u64 clamp lo", "u64 clamp hi", "u64 clamp carry"}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop3 mad clamp: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 6; ++i) Expect(tid, out[i], Expected[tid][i], Names[i]); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop3 mad clamp tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index 3cf02f268..544ed0fea 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -1930,7 +1930,7 @@ void checkNativeVop3Modifiers(RdnaOpcode opcode, bool permlane, bool carryInOut, } if (carryInOut || scalarDst) { const bool floatCompare = scalarDst && isVopcFloatCompareOpcode(opcode); - if (clamp != 0u || omod != 0u || (neg != 0u && !floatCompare && opcode != RdnaOpcode::VDivScaleF32 && opcode != RdnaOpcode::VDivScaleF64)) { + if ((clamp != 0u && !isVop3BMadU64Opcode(opcode)) || omod != 0u || (neg != 0u && !floatCompare && opcode != RdnaOpcode::VDivScaleF32 && opcode != RdnaOpcode::VDivScaleF64)) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); } return; @@ -2167,7 +2167,7 @@ RdnaInstruction DecodeRdnaVop3(std::uint32_t programCounter, std::span Date: Sun, 4 Oct 2026 11:45:59 +0200 Subject: [PATCH 076/164] feat(shader): accept float modifiers on v_cndmask_b32, v_mul_legacy_f32 and v_ldexp_f16 - v_cndmask_b32: neg/abs on DPP; neg/abs applied on the bits, as the hardware does - v_mul_legacy_f32: neg/abs on DPP and SDWA, clamp/omod on SDWA and VOP3 - v_ldexp_f16: neg/abs on src0 for VOP3, DPP and SDWA; NaN result chosen on the bits Co-Authored-By: Claude --- .../tests/execution/Vop2ModifierForms.cpp | 192 ++++++++++++++++++ .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 29 ++- .../Translation/src/FloatInstructions.cpp | 9 +- .../Translation/src/IntegerInstructions.cpp | 14 +- 4 files changed, 225 insertions(+), 19 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Vop2ModifierForms.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop2ModifierForms.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop2ModifierForms.cpp new file mode 100644 index 000000000..9b07c7f68 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop2ModifierForms.cpp @@ -0,0 +1,192 @@ +#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 +#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, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7d8a0c80, 0x7e1402ff, 0xdeadbeef, + 0x7e1602ff, 0xdeadbeef, 0x7e1802ff, 0xdeadbeef, 0x7e1a02ff, 0xdeadbeef, 0x7e1c02ff, 0xdeadbeef, + 0x7e1e02ff, 0xdeadbeef, 0x7e2002ff, 0xdeadbeef, 0x7e2202ff, 0xdeadbeef, 0x7e2402ff, 0xdeadbeef, + 0x7e2602ff, 0xdeadbeef, 0x7e2802ff, 0xdeadbeef, 0x7e2a02ff, 0xdeadbeef, 0x7e2c02ff, 0xdeadbeef, + 0x7e2e02ff, 0xdeadbeef, 0x7e3002ff, 0xdeadbeef, 0x7e3202ff, 0xdeadbeef, 0x02140afa, 0xff10e404, + 0x02160afa, 0xffe0e404, 0xd501020c, 0x21aa0b04, 0x021a0af9, 0x16360604, 0x0e1c0afa, 0xff90e404, + 0x0e1e0af9, 0x16360604, 0x0e200af9, 0x06062604, 0x0e220af9, 0x16064604, 0xd5078012, 0x1a020b04, + 0xd5070013, 0x32020b04, 0xd5078314, 0x42020b04, 0xd53b0015, 0x22020b04, 0x762c0afa, 0xff20e404, + 0x762e0af9, 0x06360604, 0xd53b0118, 0x02020b04, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, + 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u}, + {0x00000000u, 0x7f800000u, 0x00000001u, 0x00000000u}, + {0x3fc00000u, 0x3fc00000u, 0x00000001u, 0x00000000u}, + {0x3fc00000u, 0x7f800000u, 0x00000000u, 0x00000000u}, + {0xbf800000u, 0x3f800000u, 0x00000001u, 0x00000000u}, + {0x3fc00000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc0000000u, 0xabcdbc00u, 0x00000000u, 0x00000000u}, + {0x3fc00000u, 0xff800000u, 0x00000000u, 0x00000000u}, + {0xc0000000u, 0xff61b1e6u, 0x00000000u, 0x00000000u}, + {0x3e800000u, 0xc0000000u, 0x00000001u, 0x00000000u}, + {0x3e800000u, 0xbf800000u, 0x00000001u, 0x00000000u}, + {0x3f800000u, 0xbf800000u, 0x00000000u, 0x00000000u}, + {0xc0000000u, 0x7f800000u, 0x00000001u, 0x00000000u}, + {0x3e800000u, 0x3f800000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x3fc00000u, 0x00000001u, 0x00000000u}, + {0x807fffffu, 0x00000000u, 0x00000001u, 0x00000000u}, + {0x3f19999au, 0x00000001u, 0x00000001u, 0x00000000u}, + {0x3f19999au, 0x00003555u, 0x00000000u, 0x00000000u}, + {0x3f19999au, 0xffffffe2u, 0x00000001u, 0x00000000u}, + {0x00003c00u, 0x00000000u, 0x00000001u, 0x00000000u}, + {0x00003c00u, 0x00003c00u, 0x00000000u, 0x00000000u}, + {0x00007e01u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x807fffffu, 0x00000001u, 0x00000000u}, + {0x00000000u, 0x7fc00001u, 0x00000001u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0xc0000000u, 0x00000001u, 0x00000000u}, + {0x80000000u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x807fffffu, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x7fc00001u, 0x00000001u, 0x00000000u}, + {0x80000000u, 0xffc00000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x00007e01u, 0x00000001u, 0x00000000u} +}; +constexpr std::uint32_t Expected[32][15] = { + {0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x7f800000u, 0xff800000u, 0x7f800000u, 0xff800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x3fc00000u, 0xbfc00000u, 0x3fc00000u, 0xbfc00000u, 0xc0100000u, 0x40100000u, 0x3f800000u, 0xc0900000u, 0x3f800000u, 0xc1100000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0xbfc00000u, 0x3fc00000u, 0xbfc00000u, 0xbfc00000u, 0xff800000u, 0x7f800000u, 0x3f800000u, 0xff800000u, 0x3f800000u, 0xff800000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x3f800000u, 0xbf800000u, 0x3f800000u, 0xbf800000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x40800000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0xbfc00000u, 0x3fc00000u, 0xbfc00000u, 0xbfc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x40000000u, 0x40000000u, 0x40000000u, 0xc0000000u, 0x2c4dbc00u, 0xac4dbc00u, 0x2c4dbc00u, 0xaccdbc00u, 0x2bcdbc00u, 0xad4dbc00u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0xbfc00000u, 0x3fc00000u, 0xbfc00000u, 0xbfc00000u, 0xff800000u, 0xff800000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x40000000u, 0x40000000u, 0x40000000u, 0xc0000000u, 0x7f800000u, 0xff800000u, 0x3f800000u, 0xff800000u, 0x3f800000u, 0xff800000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0xc0000000u, 0xc0000000u, 0x40000000u, 0x40000000u, 0xbf000000u, 0xbf000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x40000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0xbf800000u, 0xbf800000u, 0x3f800000u, 0x3f800000u, 0xbe800000u, 0xbe800000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0xbf800000u, 0x3f800000u, 0xbf800000u, 0xbf800000u, 0xbf800000u, 0xbf800000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x40800000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x7f800000u, 0xff800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0xbe800000u, 0x3e800000u, 0xbe800000u, 0xbe800000u, 0xbe800000u, 0x3e800000u, 0x3e800000u, 0xbf000000u, 0x3e000000u, 0xbf800000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x3fc00000u, 0xbfc00000u, 0x3fc00000u, 0xbfc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead7fffu, 0xdead7fffu, 0x0000ffffu, 0xdead7fffu}, + {0x00000001u, 0x80000001u, 0x00000001u, 0x80000001u, 0x80000001u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead1d9au, 0xdead1d9au, 0x00009d9au, 0xdead1d9au}, + {0xbf19999au, 0x3f19999au, 0xbf19999au, 0xbf19999au, 0x80002000u, 0x00002000u, 0x00002000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead7c00u, 0xdead7c00u, 0x0000fc00u, 0xdead7c00u}, + {0xffffffe2u, 0xffffffe2u, 0x7fffffe2u, 0x7fffffe2u, 0x7fffffe2u, 0x7fffffe2u, 0x00000000u, 0x7fffffe2u, 0x00000000u, 0xffffffe2u, 0x00000000u, 0xdead0000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdeadbc00u, 0xdead3c00u, 0x0000bc00u, 0xdead3c00u}, + {0x80003c00u, 0x00003c00u, 0x80003c00u, 0x80003c00u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdeadfc00u, 0xdead7c00u, 0x0000fc00u, 0xdead7c00u}, + {0x80007e01u, 0x00007e01u, 0x80007e01u, 0x80007e01u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdeadfe01u, 0xdead7e01u, 0x0000fe01u, 0xdead7e01u}, + {0x807fffffu, 0x807fffffu, 0x007fffffu, 0x007fffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x7fc00001u, 0xffc00001u, 0x7fc00001u, 0xffc00001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0xc0000000u, 0xc0000000u, 0x40000000u, 0x40000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x7fc00001u, 0xffc00001u, 0x7fc00001u, 0xffc00001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x00007e01u, 0x80007e01u, 0x00007e01u, 0x80007e01u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u} +}; +constexpr std::uint32_t Checked[32] = {0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x780fu, 0x780fu, 0x780fu, 0x780fu, 0x780fu, 0x780fu, 0x780fu, 0x780fu, 0x780fu, 0x7fffu, 0x7fffu, 0x780fu, 0x780fu, 0x780fu, 0x780fu, 0x780fu}; +constexpr const char* Names[15] = { + "v_cndmask_b32_dpp v10, -v4, v5, vcc_lo", + "v_cndmask_b32_dpp v11, |v4|, -|v5|, vcc_lo", + "v_cndmask_b32_e64 v12, -v4, |v5|, vcc_lo", + "v_cndmask_b32_sdwa v13, -|v4|, -v5, vcc_lo", + "v_mul_legacy_f32_dpp v14, -v4, |v5|", + "v_mul_legacy_f32_sdwa v15, -|v4|, -v5", + "v_mul_legacy_f32_sdwa v16, v4, v5 clamp", + "v_mul_legacy_f32_sdwa v17, v4, -v5 mul:2", + "v_mul_legacy_f32_e64 v18, v4, v5 clamp div:2", + "v_mul_legacy_f32_e64 v19, -v4, v5 mul:4", + "v_mul_legacy_f32_e64 v20, |v4|, -|v5| clamp", + "v_ldexp_f16_e64 v21, -v4, v5", + "v_ldexp_f16_dpp v22, |v4|, v5", + "v_ldexp_f16_sdwa v23, -|v4|, v5", + "v_ldexp_f16_e64 v24, |v4|, v5", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop2 modifier forms: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 15; ++i) { + if ((Checked[tid] >> i) & 1u) Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop2 modifier forms tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index 3cf02f268..df2e1cc2f 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -1026,11 +1026,22 @@ bool isVop2FloatOpcode(RdnaOpcode opcode) { case RdnaOpcode::VFmamkF16: case RdnaOpcode::VFmaakF16: case RdnaOpcode::VMaxF16: - case RdnaOpcode::VMinF16: return true; + case RdnaOpcode::VMinF16: + case RdnaOpcode::VMulLegacyF32: return true; default: return false; } } +bool hasUnsupportedVop2SourceModifiers(RdnaOpcode opcode, bool src0Modified, bool src1Modified) { + if (isVop2FloatOpcode(opcode) || opcode == RdnaOpcode::VCndmaskB32) { + return false; + } + if (opcode == RdnaOpcode::VLdexpF16) { + return src1Modified; + } + return src0Modified || src1Modified; +} + bool usesInexactClampControl(RdnaOpcode opcode) { return opcode == RdnaOpcode::VCvtU32F32 || opcode == RdnaOpcode::VCvtI32F32 || opcode == RdnaOpcode::VCvtU16F16 || opcode == RdnaOpcode::VCvtI16F16; @@ -1329,8 +1340,7 @@ void validateVop2Sdwa(const RdnaInstruction& instruction, std::uint32_t opcode, throw std::invalid_argument("VOP2 SDWA output modifiers are not supported"); } if (isFullWidthVop2Sdwa(fields)) { - if (!isVop2FloatOpcode(instruction.op) && instruction.op != RdnaOpcode::VCndmaskB32 && - (fields.src0Neg != 0u || fields.src0Abs != 0u || fields.src1Neg != 0u || fields.src1Abs != 0u)) { + if (hasUnsupportedVop2SourceModifiers(instruction.op, fields.src0Neg != 0u || fields.src0Abs != 0u, fields.src1Neg != 0u || fields.src1Abs != 0u)) { throw std::invalid_argument("VOP2 SDWA source modifiers are not supported"); } return; @@ -1440,8 +1450,7 @@ void decodeVop2Dpp(std::uint32_t programCounter, std::span instruction.source1.negateHi = instruction.source1.negate; } - if (!isVop2FloatOpcode(instruction.op) && !packedFmac && - (instruction.source0.negate || instruction.source0.absolute || instruction.source1.negate || instruction.source1.absolute)) { + if (!packedFmac && hasUnsupportedVop2SourceModifiers(instruction.op, instruction.source0.negate || instruction.source0.absolute, instruction.source1.negate || instruction.source1.absolute)) { throw std::invalid_argument("VOP2 DPP integer source modifiers are not supported"); } finalizeVop2Instruction(code, wordIndex, instruction); @@ -1863,7 +1872,8 @@ bool supportsNativeVop3SourceModifiers(RdnaOpcode opcode) { case RdnaOpcode::VMinF64: case RdnaOpcode::VMaxF64: case RdnaOpcode::VLdexpF64: - case RdnaOpcode::VTrigPreopF64: return true; + case RdnaOpcode::VTrigPreopF64: + case RdnaOpcode::VLdexpF16: return true; default: return false; } } @@ -1877,6 +1887,7 @@ bool supportsNativeVop3ResultModifiers(RdnaOpcode opcode) { case RdnaOpcode::VSubF32: case RdnaOpcode::VSubrevF32: case RdnaOpcode::VMulF32: + case RdnaOpcode::VMulLegacyF32: case RdnaOpcode::VMinF32: case RdnaOpcode::VMaxF32: case RdnaOpcode::VMacF32: @@ -1947,6 +1958,12 @@ void checkNativeVop3Modifiers(RdnaOpcode opcode, bool permlane, bool carryInOut, } return; } + if (opcode == RdnaOpcode::VLdexpF16) { + if ((abs & ~1u) != 0u || opSel != 0u || clamp != 0u || omod != 0u || (neg & ~1u) != 0u) { + throw std::invalid_argument("VOP3 source modifiers are not implemented"); + } + return; + } if (opcode == RdnaOpcode::VCndmaskB32) { if ((abs & ~0x3u) != 0u || opSel != 0u || clamp != 0u || omod != 0u || (neg & ~0x3u) != 0u) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); diff --git a/core/shader/recompiler/Translation/src/FloatInstructions.cpp b/core/shader/recompiler/Translation/src/FloatInstructions.cpp index 079497ea8..5513e8198 100644 --- a/core/shader/recompiler/Translation/src/FloatInstructions.cpp +++ b/core/shader/recompiler/Translation/src/FloatInstructions.cpp @@ -173,11 +173,16 @@ IrU32 TranslationContext::normF16(IrU32 bits, bool signedValue) { } bool TranslationContext::vLdexpF16(const RdnaInstruction& inst) { - const IrF32 value = readF16AsF32(sourceAt(inst, 0u)); + const IrU32 bits = readF16Bits(sourceAt(inst, 0u)); + const IrF16 half(ir.Emit(IrOpcode::BitCastF16U16, IrType::F16, {&ir.Emit(IrOpcode::ConvertU16U32, IrType::U16, {&bits.Value()})})); + const IrF32 value(ir.Emit(IrOpcode::ConvertF32F16, IrType::F32, {&half.Value()})); const IrU32 exponent(ir.Emit(IrOpcode::BitFieldSExtract, IrType::U32, {&readU32(sourceAt(inst, 1u)).Value(), &ir.Constant(0u), &ir.Constant(16u)})); const IrU32 clamped(ir.Emit(IrOpcode::SMax32, IrType::U32, {&ir.Emit(IrOpcode::SMin32, IrType::U32, {&exponent.Value(), &ir.Constant(64u)}), &ir.Constant(static_cast(-64))})); IrValue& power = ir.BitCastF32(ir.ShiftLeftLogical(ir.IAdd(clamped.Value(), ir.Constant(127u)), ir.Constant(23u))); - writeF16(inst.destination, IrF32(ir.Emit(IrOpcode::FPMul32, IrType::F32, {&value.Value(), &power}))); + const IrF16 scaled(ir.Emit(IrOpcode::ConvertF16F32, IrType::F16, {&ir.Emit(IrOpcode::FPMul32, IrType::F32, {&value.Value(), &power})})); + const IrU32 result(ir.Emit(IrOpcode::ConvertU32U16, IrType::U32, {&ir.Emit(IrOpcode::BitCastU16F16, IrType::U16, {&scaled.Value()})})); + const IrU1 nan(ir.UGreaterThan(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffu)), ir.Constant(0x7c00u))); + write16Bits(inst.destination, IrU32(ir.Select(nan.Value(), ir.BitwiseOr(bits.Value(), ir.Constant(0x200u)), result.Value()))); return true; } diff --git a/core/shader/recompiler/Translation/src/IntegerInstructions.cpp b/core/shader/recompiler/Translation/src/IntegerInstructions.cpp index 81974fcbe..447f28c23 100644 --- a/core/shader/recompiler/Translation/src/IntegerInstructions.cpp +++ b/core/shader/recompiler/Translation/src/IntegerInstructions.cpp @@ -802,17 +802,9 @@ bool TranslationContext::vCndmaskB32(const RdnaInstruction& inst) { const IrU1 condition = readMask(maskOperand); const RdnaOperand& falseOperand = sourceAt(inst, 0u); const RdnaOperand& trueOperand = sourceAt(inst, 1u); - IrValue* result = nullptr; - if (falseOperand.negate || falseOperand.absolute || trueOperand.negate || trueOperand.absolute) { - IrValue* falseValue = readOperand(falseOperand, IrType::F32); - IrValue* trueValue = readOperand(trueOperand, IrType::F32); - result = &ir.Emit(IrOpcode::SelectF32, IrType::F32, {&condition.Value(), trueValue, falseValue}); - } else { - const IrU32 falseValue = readU32(falseOperand); - const IrU32 trueValue = readU32(trueOperand); - result = &ir.Select(condition.Value(), trueValue.Value(), falseValue.Value()); - } - writeOperand(inst.destination, result); + const IrU32 falseValue = readU32(falseOperand); + const IrU32 trueValue = readU32(trueOperand); + writeOperand(inst.destination, &ir.Select(condition.Value(), trueValue.Value(), falseValue.Value())); return true; } From b5da7d00e490c7c962e9bb2f7db34ccd881cb35b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 23:53:38 +0200 Subject: [PATCH 077/164] fix(shader): apply the f32 output modifier on the bits like the hardware Co-Authored-By: Claude --- .../tests/execution/F32OutputModifier.cpp | 191 ++++++++++++++++++ .../src/TranslationContext/OperandAccess.cpp | 11 +- docs/dev/TechnicalDebt.md | 1 + 3 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/F32OutputModifier.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F32OutputModifier.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F32OutputModifier.cpp new file mode 100644 index 000000000..fe668ec80 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F32OutputModifier.cpp @@ -0,0 +1,191 @@ +#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 +#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, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd508000a, 0x0a020b04, 0xd508000b, + 0x1a020b04, 0xd508000c, 0x12020b04, 0xd503000d, 0x0a020b04, 0xd503000e, 0x1a020b04, 0xd504000f, + 0x32020b04, 0xd54b0010, 0x0c1a0b04, 0xd54b0011, 0x1c1a0b04, 0xd50f0012, 0x1a020b04, 0xd5100013, + 0x0a020d04, 0xd5a40014, 0x1a010104, 0xd5850015, 0x12010104, 0xd5088016, 0x0a020b04, 0xd5088017, + 0x1a020b04, 0xd5038018, 0x12020d04, 0xd54b0019, 0x341a0b04, 0xe0781000, 0x80010a03, 0xe0781010, + 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x80000000u, 0x3f800000u, 0x80000000u, 0x00000000u}, + {0x80800000u, 0x3f800000u, 0x80800000u, 0x00000000u}, + {0x00ffffffu, 0x3f800000u, 0x80ffffffu, 0x00000000u}, + {0x7f7fffffu, 0x40000000u, 0x7f7fffffu, 0x00000000u}, + {0x7fc12345u, 0xffc00000u, 0xff800001u, 0x00000000u}, + {0x7fc12345u, 0x3f800000u, 0x7fc12345u, 0x00000000u}, + {0x7f800001u, 0x40000000u, 0x7f800001u, 0x00000000u}, + {0xff812345u, 0xbf800000u, 0xff812345u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xff800000u, 0x00000000u}, + {0x3f800000u, 0xff000000u, 0x00000000u, 0x00000000u}, + {0x3f000000u, 0xbf800000u, 0x7f800000u, 0x00000000u}, + {0xff800000u, 0x3f000000u, 0x7fc00000u, 0x00000000u}, + {0xff000000u, 0xbf800000u, 0xbf800000u, 0x00000000u}, + {0x3f800000u, 0x01000000u, 0x3f000000u, 0x00000000u}, + {0x00000000u, 0x00800000u, 0x00800000u, 0x00000000u}, + {0x00000000u, 0x80800000u, 0x80800000u, 0x00000000u}, + {0x7f7fffffu, 0x3f000000u, 0x00000000u, 0x00000000u}, + {0x7f800000u, 0x7f800000u, 0x00000000u, 0x00000000u}, + {0x3f000000u, 0xff000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0xff000000u, 0x7f7fffffu, 0x7fc00000u, 0x00000000u}, + {0x00000000u, 0x01000000u, 0x01000000u, 0x00000000u}, + {0x7fc00000u, 0x7f7fffffu, 0x00000000u, 0x00000000u}, + {0xbf800000u, 0x01000000u, 0x00000000u, 0x00000000u}, + {0x80800000u, 0x80800000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7fc00000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xffc00000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7f800001u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7fc12345u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7f800000u, 0x00000000u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0x3f000000u, 0xc0800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xd0000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x81000000u, 0x80000000u, 0x81800000u, 0x40000000u, 0x3f000000u, 0xc0800000u, 0x81800000u, 0x80800000u, 0x80000000u, 0x81000000u, 0xbf000000u, 0xcfff0000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x017fffffu, 0x00000000u, 0x01ffffffu, 0x40000000u, 0x3f000000u, 0xc0800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x017fffffu, 0x00000000u, 0x4c7fffffu, 0x017fffffu, 0x00000000u, 0x00000000u, 0x827fffffu}, + {0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7effffffu, 0xff800000u, 0x7f800000u, 0x7f800000u, 0x3f800000u, 0x7f800000u, 0x7effffffu, 0x4fff0000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0xff800000u}, + {0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0xffc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x4fff8247u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffc12345u}, + {0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0xffc12345u, 0x7fc12345u, 0x7fc12345u, 0x3f000000u, 0x7fc12345u, 0x7fc12345u, 0x4fff8247u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffc12345u}, + {0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0xff800001u, 0x7f800001u, 0x7f800001u, 0x3f800000u, 0x7f800001u, 0x7f800001u, 0x4fff0000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff800001u}, + {0xff812345u, 0xff812345u, 0xff812345u, 0xff812345u, 0xff812345u, 0x7f812345u, 0xff812345u, 0xff812345u, 0xbf000000u, 0xff812345u, 0xff812345u, 0xcbfdb976u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7f812345u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff800000u, 0xff800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff800000u}, + {0xff800000u, 0xfe800000u, 0xff800000u, 0xff800000u, 0xfe800000u, 0x7f800000u, 0xff800000u, 0xfe800000u, 0xfe800000u, 0x40000000u, 0x3f000000u, 0x4f7e0000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x7f800000u}, + {0xbf800000u, 0xbe800000u, 0xc0000000u, 0xbf800000u, 0xbe800000u, 0x40000000u, 0x7f800000u, 0x7f800000u, 0xbf000000u, 0x7f800000u, 0x00000000u, 0x4f7c0000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x7f800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0x7f800000u, 0x7fc00000u, 0x7fc00000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xcc000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc00000u}, + {0x7f800000u, 0x7e800000u, 0x7f800000u, 0xff800000u, 0xfe800000u, 0x7f800000u, 0x7f800000u, 0x7e800000u, 0xfe800000u, 0xc0000000u, 0xfe800000u, 0xcc800000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0xff800000u}, + {0x01800000u, 0x00800000u, 0x02000000u, 0x40000000u, 0x3f000000u, 0xc0800000u, 0x3f800000u, 0x3e800000u, 0x00800000u, 0x40000000u, 0x3f000000u, 0x4f7e0000u, 0x01800000u, 0x00800000u, 0x3f800000u, 0x40000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x01000000u, 0x00000000u, 0x81800000u, 0x01000000u, 0x00000000u, 0x00000000u, 0x01000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x01800000u, 0x01800000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x81000000u, 0x80000000u, 0x01800000u, 0x81000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x81800000u}, + {0x7f7fffffu, 0x7e7fffffu, 0x7f800000u, 0x7f800000u, 0x7effffffu, 0xff800000u, 0x7f7fffffu, 0x7e7fffffu, 0x3e800000u, 0x7f800000u, 0x7effffffu, 0x4fff0000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0xff800000u}, + {0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x4fff0000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0xff800000u}, + {0xff000000u, 0xfe000000u, 0xff800000u, 0xff800000u, 0xfe800000u, 0x7f800000u, 0xff000000u, 0xfe000000u, 0xfe800000u, 0x3f800000u, 0x00000000u, 0x4f7c0000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x7f800000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xd0000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0x7f7ffffeu, 0x7e7ffffeu, 0xff800000u, 0x7fc00000u, 0x7fc00000u, 0xfe800000u, 0xff800000u, 0xfe800000u, 0xcc800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x01800000u, 0x00800000u, 0x82000000u, 0x01800000u, 0x00800000u, 0x00000000u, 0x01800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x02000000u, 0x02000000u}, + {0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0xffc00000u, 0x7fc00000u, 0x7fc00000u, 0x7effffffu, 0x00000000u, 0x7fc00000u, 0x4fff8000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffc00000u}, + {0x81800000u, 0x80800000u, 0x82000000u, 0xc0000000u, 0xbf000000u, 0x40800000u, 0x81800000u, 0x80800000u, 0xbf000000u, 0x00000000u, 0xbf000000u, 0xcf810000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x02000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x81800000u, 0x80800000u, 0x02000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xbf000000u, 0xcfff0000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc00000u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffc00000u, 0xffc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffc00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7f800001u, 0x7f800001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7f800001u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc12345u, 0x7fc12345u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc12345u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7f800000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x7f800000u} +}; +constexpr std::uint32_t Checked[32] = {0xffffu, 0xffffu, 0xffffu, 0x7f3fu, 0x7800u, 0x7b00u, 0x7b00u, 0x7b00u, 0xffffu, 0xffffu, 0xffffu, 0x7f3fu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0x7f3fu, 0xffffu, 0xffffu, 0x7f3fu, 0xffffu, 0x7b00u, 0xffffu, 0x7f3fu, 0xffffu, 0xffffu, 0x7f3fu, 0x7f3fu, 0x7f3fu, 0x7f3fu, 0xffffu}; +constexpr std::uint32_t AnyNan[32] = {0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x84ffu, 0x84ffu, 0x84ffu, 0x84ffu, 0x0000u, 0x0000u, 0x0000u, 0x80c0u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x80c0u, 0x0000u, 0x84ffu, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x80c0u, 0x80c0u, 0x80c0u, 0x80c0u, 0x0000u}; +constexpr const char* Names[16] = { + "v_mul_f32_e64 v10, v4, v5 mul:2", + "v_mul_f32_e64 v11, v4, v5 div:2", + "v_mul_f32_e64 v12, v4, v5 mul:4", + "v_add_f32_e64 v13, v4, v5 mul:2", + "v_add_f32_e64 v14, v4, v5 div:2", + "v_sub_f32_e64 v15, -v4, v5 mul:4", + "v_fma_f32 v16, v4, v5, v6 mul:2", + "v_fma_f32 v17, v4, v5, v6 div:2", + "v_min_f32_e64 v18, v4, v5 div:2", + "v_max_f32_e64 v19, v4, v6 mul:2", + "v_floor_f32_e64 v20, v4 div:2", + "v_cvt_f32_i32_e64 v21, v4 mul:4", + "v_mul_f32_e64 v22, v4, v5 clamp mul:2", + "v_mul_f32_e64 v23, v4, v5 clamp div:2", + "v_add_f32_e64 v24, v4, v6 clamp mul:4", + "v_fma_f32 v25, -v4, v5, v6 mul:4", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("f32 output modifier: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + if ((Checked[tid] >> i) & 1u) Expect(tid, out[i], Expected[tid][i], Names[i]); + if ((AnyNan[tid] >> i) & 1u) Require((out[i] & 0x7fffffffu) > 0x7f800000u, std::string("f32 output modifier: lane ") + std::to_string(tid) + " " + Names[i] + " is " + Hex(out[i]) + ", expected a NaN"); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("f32 output modifier tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp b/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp index d27ea5491..d1c146873 100644 --- a/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp +++ b/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp @@ -252,7 +252,16 @@ IrF32 TranslationContext::applyF32ResultModifiers(const RdnaOperand& operand, Ir case 2u: multiplier = 4.0f; break; default: break; } - value = IrF32(ir.Emit(IrOpcode::FPMul32, IrType::F32, {&value.Value(), &ir.ConstantF32(multiplier)})); + const IrF32 scaled(ir.Emit(IrOpcode::FPMul32, IrType::F32, {&value.Value(), &ir.ConstantF32(multiplier)})); + IrValue& bits = ir.BitCastU32(value.Value()); + IrValue& magnitude = ir.BitwiseAnd(bits, ir.Constant(0x7fffffffu)); + IrValue* result = &ir.BitCastU32(scaled.Value()); + if (operand.omod == 3u) { + result = &ir.Select(ir.ULessThan(magnitude, ir.Constant(0x01000000u)), ir.BitwiseAnd(bits, ir.Constant(0x80000000u)), *result); + } + result = &ir.Select(ir.ULessThan(magnitude, ir.Constant(0x00800000u)), ir.Constant(0u), *result); + result = &ir.Select(ir.UGreaterThan(magnitude, ir.Constant(0x7f800000u)), bits, *result); + value = IrF32(ir.BitCastF32(*result)); } if (operand.clamp) { value = IrF32(ir.Emit(IrOpcode::FPSaturate32, IrType::F32, {&value.Value()})); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 5e7471b7c..b38809ea9 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -87,5 +87,6 @@ Throughout the project, every function at every stage either **does exactly what - [sceKeyboardGetKey2Char](../../core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp) (libSceKeyboard) translates only the 101-key (US) arrangement and throws for the 106-key (Japanese) one; Ctrl and Alt do not change the character. - [libSceAudiodec](../../core/libs/prx/libSceAudiodec/Export.cpp) throws for the 24-bit PCM word size (`iBwPcm` 0): its sample layout is unknown. ATRAC9 decoding is covered only by configuration and error tests, as no ATRAC9 encoder is available for a fixture. - [sceAjmDecMp3ParseFrame](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) throws when the original file length is requested (`parse_ofl` non-zero): reading it from the frame's Xing/Info, VBRI or Fraunhofer data is not implemented. +- The [output modifier](../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) (`mul:2`, `mul:4`, `div:2`) follows the hardware with the IEEE mode off and f32 denormals flushed, the LLVM graphics default; the shader's MODE register is not read, and the hardware ignores the modifier in the other modes. On f16 results it is ignored, as on the hardware while f16 denormals are enabled; with them flushed the hardware applies it. - [libScePlayerInvitationDialog](../../core/libs/prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.cpp) simulates dialog completion without displaying UI or sending invitations; its parameter ABI remains unverified. - [64-bit LDS atomics](../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) (`ds_*_u64`, `ds_*_b64`, `ds_*_f64`) read and write the two dwords under a workgroup lock stored after the guest LDS, as LDS is a 32-bit array without 64-bit atomics. They are atomic with respect to each other only: 32-bit accesses to the same dwords issued concurrently are not ordered by the lock. 64-bit GDS atomics throw. `ds_min/max(_rtn)_f64` and `ds_cmpst(_rtn)_f64` follow the NaN and signed-zero rules measured for the f32 forms; the f64 forms were not measured on hardware. From be866e4214602619f35d3a460502deca40a21608 Mon Sep 17 00:00:00 2001 From: DotDebian Date: Fri, 2 Oct 2026 19:09:23 +0200 Subject: [PATCH 078/164] feat(shader): store through flat and global addresses flat_store_* and global_store_* decoded and translated, but a store whose segment is not scratch was refused when the SPIR-V was emitted ("writable FLAT/GLOBAL addresses require GPU ownership tracking"). Such a store now goes through the BDA page table like a store through a GPU-selected V#: the write lookup needs Write permission, which only a writable registered range imported in place has, and the written page is noted for the host to mark GPU-written. Byte and short stores, and dword stores to an address that is not dword aligned, are stored byte by byte. Co-Authored-By: Claude Opus 5.5 --- .../Graphics/src/BdaResources.cpp | 4 +- .../tests/execution/FlatStore.cpp | 143 ++++++++++++++++++ .../Optimization/src/ResourceTracker.cpp | 1 + .../include/SpirvBackend/SpirvBda.hpp | 1 + .../SpirvBackend/src/SpirvAnalysis.cpp | 2 - .../SpirvBackend/src/SpirvBdaRead.cpp | 52 +++++-- .../SpirvMemory/SpirvMemoryInstructions.cpp | 23 ++- 7 files changed, 206 insertions(+), 20 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/FlatStore.cpp diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp index 22bc67a98..605809a50 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp @@ -262,7 +262,7 @@ void BdaResources::CheckFault() const { ShaderRecompiler::BdaAbi::Range range{}; std::memcpy(&range, bytes.data() + sizeof(header) + index * sizeof(range), sizeof(range)); if (report.address < range.begin || report.address >= range.end) continue; - message << std::hex << "; the store hit 0x" << range.begin << "+0x" << range.end - range.begin << ", read-only in the BDA table: stores through GPU-selected descriptors reach only writable ranges imported in place, not ones served by a mirror or a copy (past APS5_HOST_IMPORT_MIB, or refused by the driver)"; + message << std::hex << "; the store hit 0x" << range.begin << "+0x" << range.end - range.begin << ", read-only in the BDA table: stores through GPU-selected descriptors and FLAT/GLOBAL addresses reach only writable ranges imported in place, not ones served by a mirror or a copy (past APS5_HOST_IMPORT_MIB, or refused by the driver)"; break; } } @@ -276,7 +276,7 @@ namespace AgcDriver::Graphics { void BdaResources::markWrittenPages() const { namespace Abi = ShaderRecompiler::BdaAbi; auto* words = reinterpret_cast(fault->Bytes().data()); - Require(words[Abi::WrittenOverflowWord] == 0, "more than " + std::to_string(Abi::WrittenPageSlots) + " pages stored to through GPU-selected buffer descriptors in one use are not implemented"); + Require(words[Abi::WrittenOverflowWord] == 0, "more than " + std::to_string(Abi::WrittenPageSlots) + " pages stored to through the BDA table in one use are not implemented"); bool any = false; for (std::uint32_t slot = 0; slot < Abi::WrittenPageSlots; ++slot) { const auto page = words[Abi::WrittenSlotsWord + slot]; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatStore.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatStore.cpp new file mode 100644 index 000000000..25bcf9396 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatStore.cpp @@ -0,0 +1,143 @@ +#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 + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t MaskedThreads = 16; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint32_t CheckedBytes = 0x2000; +constexpr std::uint8_t Fill = 0xcd; + +alignas(256) constexpr std::array FlatStoreCode{ + 0x34020082, 0x34040084, 0x340c0081, 0x340e0083, 0x4a060200, 0x7e080201, 0x4a1806ff, 0x00000204, + 0x7e1a0201, 0x4a1c04ff, 0x00001000, 0x4a1000ff, 0xa1b2c300, 0x4a1202ff, 0x51000000, 0x4a1404ff, + 0x00c0ffee, 0x4a160eff, 0x7e570000, 0xdc708000, 0x00000801, 0xdc700100, 0x007d0903, 0xdc708ffc, + 0x007d0a0c, 0xdc608400, 0x00000800, 0xdc688480, 0x00000906, 0xdc708601, 0x00000b07, 0xdc788000, + 0x0000080e, 0xdc748400, 0x0000080e, 0xdc7c8800, 0x0000090e, 0xdc308000, 0x0f000001, 0xdc708500, + 0x00000f01, 0x7da80090, 0xdc708300, 0x00000a01, 0xbf810000, +}; + +class GuestBlock { +public: + explicit GuestBlock(bool writable) { +#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, "flat store: cannot allocate the guest block"); + Clear(); + GuestAllocations::Mutation().Add(block, BlockBytes, true, writable); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + void Clear() { std::memset(block, Fill, BlockBytes); } + const std::uint8_t* Data() const { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +std::vector Expected() { + std::vector image(CheckedBytes, Fill); + const auto put = [&](std::uint32_t offset, std::uint32_t value, std::uint32_t bytes) { + for (std::uint32_t byte = 0; byte < bytes; ++byte) image.at(offset + byte) = static_cast(value >> (byte * 8u)); + }; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::array data{0xa1b2c300u + tid, 0x51000000u + tid * 4u, 0x00c0ffeeu + tid * 16u, 0x7e570000u + tid * 8u}; + put(0x000u + tid * 4u, data[0], 4); + put(0x100u + tid * 4u, data[1], 4); + put(0x200u + tid * 4u, data[2], 4); + if (tid < MaskedThreads) put(0x300u + tid * 4u, data[2], 4); + put(0x400u + tid, data[0], 1); + put(0x480u + tid * 2u, data[1], 2); + put(0x500u + tid * 4u, data[0], 4); + put(0x601u + tid * 8u, data[3], 4); + for (std::uint32_t dword = 0; dword < 3; ++dword) put(0x800u + tid * 16u + dword * 4u, data[dword + 1u], 4); + for (std::uint32_t dword = 0; dword < 4; ++dword) put(0x1000u + tid * 16u + dword * 4u, data[dword], 4); + for (std::uint32_t dword = 0; dword < 2; ++dword) put(0x1400u + tid * 16u + dword * 4u, data[dword], 4); + } + return image; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(8, 0u); + userData[0] = static_cast(address); + userData[1] = static_cast(address >> 32u); + const std::span code(FlatStoreCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 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, memory}, + device.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(); +} + +void RunStores(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize) { + guest.Clear(); + Dispatch(device, waveSize, guest.Data()); + const auto expected = Expected(); + for (std::uint32_t offset = 0; offset < CheckedBytes; ++offset) { + const auto actual = guest.Data()[offset]; + Require(actual == expected[offset], "flat store: wave" + std::to_string(waveSize) + " byte " + std::to_string(offset) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[offset])); + } +} + +void RunReadOnly(AgcDriver::VulkanDevice& device, const GuestBlock& guest) { + Dispatch(device, 32, guest.Data()); + for (std::uint32_t offset = 0; offset < CheckedBytes; ++offset) { + Require(guest.Data()[offset] == Fill, "flat store: a store into a read-only range changed byte " + std::to_string(offset)); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock writable(true); + const GuestBlock readOnly(false); + RunStores(*device, writable, 32); + RunStores(*device, writable, 64); + RunReadOnly(*device, readOnly); + std::puts("flat store tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/Optimization/src/ResourceTracker.cpp b/core/shader/recompiler/Optimization/src/ResourceTracker.cpp index 450a60b45..d57416cdb 100644 --- a/core/shader/recompiler/Optimization/src/ResourceTracker.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceTracker.cpp @@ -1016,6 +1016,7 @@ private: std::fprintf(stderr, "[bda] %s at pc 0x%08x: %s access, offset %s%s\n", std::string(IrOpcodeName(op)).c_str(), flags.pc, kind, immediateOffset ? "immediate" : "dynamic", memory.kind == ResourceKind::ScalarAddress && !immediateOffset ? " (a register offset is not planned by the SRT walker)" : ""); } m_info.usesDma = true; + m_info.bdaWrites = m_info.bdaWrites || addressInfo.access == AddressAccess::Write; return; } diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvBda.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvBda.hpp index f7c278915..3ce87d8fa 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvBda.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvBda.hpp @@ -21,6 +21,7 @@ bool BdaInvocationsMayStop(const IrProgram& program); void StopBdaInvocationIf(SpirvEmitterState& state, std::uint32_t condition); std::uint32_t EmitBdaRead(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t address, std::uint32_t bits); void EmitBdaWrite(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t address, std::uint32_t value); +void EmitBdaStore(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t address, std::uint32_t value, std::uint32_t bits); // Reads the dwords of a 1-4 dword load at address + offset that the program extracts, with one // table lookup for the whole span; the per-byte lookups of EmitBdaRead remain the fallback (and // the only path under APS5_BDA_BYTE_READS=1). Dwords the program never extracts are neither read diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp index e417aee83..8740e59ce 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp @@ -84,8 +84,6 @@ SpirvRequirements AnalyzeProgramRequirements(const IrProgram& program) { throw std::runtime_error("scratch operation has no per-thread storage"); } requirements.functionScratch = true; - } else if (addressAccess == AddressAccess::Write) { - throw std::runtime_error("writable FLAT/GLOBAL addresses require GPU ownership tracking"); } } if (BufferAccessOf(inst->Opcode()) != BufferAccess::None) { diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp index 3787acf5c..981c26e92 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp @@ -83,6 +83,21 @@ std::uint32_t UsedBdaDwords(const IrValue& inst, std::uint32_t dwords) { return used; } +void EmitBdaStoreAt(SpirvEmitterState& state, std::uint32_t address, std::uint32_t bytes, std::uint32_t pointerType, std::uint32_t value, std::uint32_t instruction, std::uint32_t accessMask) { + const auto physical = state.module.AllocateId(); + state.module.AddFunction(spv::OpFunctionCall, TypeScalarU64(state), physical, state.bdaWritePointerFunction, address, ConstantU32(state, bytes), instruction); + EmitIfCondition(state, Binary(state, spv::OpINotEqual, TypeBool(state), physical, BdaConstant(state, 0u)), [&] { + const auto pointer = state.module.AllocateId(); + state.module.AddFunction(spv::OpConvertUToPtr, pointerType, pointer, physical); + state.module.AddFunction(spv::OpStore, pointer, value, accessMask, bytes); + state.module.AddFunction(spv::OpFunctionCall, state.module.Type(spv::OpTypeVoid), state.module.AllocateId(), state.bdaNoteWriteFunction, address); + }); +} + +std::uint32_t BdaAddressUnaligned(SpirvEmitterState& state, std::uint32_t address) { + return Binary(state, spv::OpINotEqual, TypeBool(state), Binary(state, spv::OpBitwiseAnd, TypeScalarU64(state), address, BdaConstant(state, 3u)), BdaConstant(state, 0u)); +} + } // APS5_BDA_BYTE_READS=1 restores one lookup and one byte load per byte for every read. @@ -143,17 +158,36 @@ void EmitBdaWrite(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t auto& state = ctx.state; if (state.bdaWritePointerFunction == 0 || state.bdaNoteWriteFunction == 0) ctx.Fail(inst, "BDA write functions are missing"); const auto instruction = ConstantU32(state, inst.Flags().pc); - const auto unaligned = Binary(state, spv::OpINotEqual, TypeBool(state), Binary(state, spv::OpBitwiseAnd, TypeScalarU64(state), address, BdaConstant(state, 3u)), BdaConstant(state, 0u)); + const auto unaligned = BdaAddressUnaligned(state, address); EmitIfCondition(state, unaligned, [&] { RecordBdaFault(state, address, ConstantU32(state, 4u), instruction, BdaAbi::FaultReason::Unaligned); }); EmitIfCondition(state, Unary(state, spv::OpLogicalNot, TypeBool(state), unaligned), [&] { - const auto physical = state.module.AllocateId(); - state.module.AddFunction(spv::OpFunctionCall, TypeScalarU64(state), physical, state.bdaWritePointerFunction, address, ConstantU32(state, 4u), instruction); - EmitIfCondition(state, Binary(state, spv::OpINotEqual, TypeBool(state), physical, BdaConstant(state, 0u)), [&] { - const auto pointer = state.module.AllocateId(); - state.module.AddFunction(spv::OpConvertUToPtr, TypePhysicalU32Pointer(state), pointer, physical); - state.module.AddFunction(spv::OpStore, pointer, value, BdaAccessMask(state, inst), 4u); - state.module.AddFunction(spv::OpFunctionCall, state.module.Type(spv::OpTypeVoid), state.module.AllocateId(), state.bdaNoteWriteFunction, address); - }); + EmitBdaStoreAt(state, address, 4u, TypePhysicalU32Pointer(state), value, instruction, BdaAccessMask(state, inst)); + }); +} + +void EmitBdaStore(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t address, std::uint32_t value, std::uint32_t bits) { + auto& state = ctx.state; + if (bits != 8u && bits != 16u && bits != 32u) ctx.Fail(inst, "unsupported BDA store width"); + if (state.bdaWritePointerFunction == 0 || state.bdaNoteWriteFunction == 0) ctx.Fail(inst, "BDA write functions are missing"); + const auto instruction = ConstantU32(state, inst.Flags().pc); + const auto accessMask = BdaAccessMask(state, inst); + const auto storeBytes = [&] { + const auto byteType = state.module.Type(spv::OpTypeInt, 8u, 0u); + const auto bytePointer = TypePointer(state, spv::StorageClassPhysicalStorageBuffer, byteType); + for (std::uint32_t byte = 0; byte < bits / 8u; ++byte) { + const auto guest = byte == 0u ? address : Binary(state, spv::OpIAdd, TypeScalarU64(state), address, BdaConstant(state, byte)); + const auto shifted = byte == 0u ? value : Binary(state, spv::OpShiftRightLogical, TypeU32(state), value, ConstantU32(state, byte * 8u)); + EmitBdaStoreAt(state, guest, 1u, bytePointer, Unary(state, spv::OpUConvert, byteType, shifted), instruction, accessMask); + } + }; + if (bits != 32u) { + storeBytes(); + return; + } + const auto unaligned = BdaAddressUnaligned(state, address); + EmitIfCondition(state, unaligned, storeBytes); + EmitIfCondition(state, Unary(state, spv::OpLogicalNot, TypeBool(state), unaligned), [&] { + EmitBdaStoreAt(state, address, 4u, TypePhysicalU32Pointer(state), value, instruction, accessMask); }); } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp index 090c4df4c..fdb90178e 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp @@ -733,13 +733,22 @@ void LoadAddressWide(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint3 void StoreAddress(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t bits) { const auto& mem = ctx.Memory(inst); - if (mem.kind != ResourceKind::Scratch) { - ctx.Fail(inst, "must write a scratch resource because physical address stores have no emitter"); - } - if (bits == 32u) { - StoreWord(ctx, inst, mem); - } else { - StoreSubword(ctx, inst, mem, bits); + switch (mem.kind) { + case ResourceKind::Scratch: + if (bits == 32u) { + StoreWord(ctx, inst, mem); + } else { + StoreSubword(ctx, inst, mem, bits); + } + return; + case ResourceKind::Flat: + case ResourceKind::Global: + EmitIfCondition(ctx.state, ActiveArgument(ctx, inst), [&]() { + EmitBdaStore(ctx, inst, GuestAddress(ctx, inst, mem), ctx.Arg(inst, inst.ArgumentCount() - 2u), bits); + }); + return; + default: + ctx.Fail(inst, "must write a scratch or physical address resource"); } } From 038ded6382496f4071ee66bf5011b313cac5db70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 10:26:55 +0200 Subject: [PATCH 079/164] feat(shader): implement the integer flat and global atomics Co-Authored-By: Claude --- .../tests/execution/GlobalAtomics.cpp | 674 ++++++++++++++++++ .../IntermediateRepresentation/IrOpcode.hpp | 26 +- .../src/IrOpcode.cpp | 53 +- .../Optimization/src/ResourceTracker.cpp | 2 +- .../include/RdnaDecoder/RdnaOpcode.hpp | 24 + .../RdnaDecoder/src/RdnaMemoryOpDecoder.cpp | 68 +- .../include/SpirvBackend/SpirvBda.hpp | 2 + .../SpirvBackend/SpirvEmitterInstructions.hpp | 1 + .../SpirvBackend/src/SpirvAnalysis.cpp | 4 +- .../SpirvBackend/src/SpirvBdaRead.cpp | 22 + .../SpirvBackend/src/SpirvFlowEmitter.cpp | 24 + .../SpirvMemory/SpirvMemoryInstructions.cpp | 80 +++ .../Translation/TranslationContext.hpp | 1 + .../Translation/src/InstructionTranslator.cpp | 4 + .../MemoryInstructions/FlatInstructions.cpp | 30 + .../MemoryInstructionDispatch.cpp | 48 ++ 16 files changed, 1056 insertions(+), 7 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics.cpp new file mode 100644 index 000000000..736dd9cdc --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics.cpp @@ -0,0 +1,674 @@ +#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::uint32_t Threads = 64; +constexpr std::uint32_t RowsPerOp = 16; +constexpr std::uint32_t OpCount = 24; +constexpr std::uint32_t NarrowOps = 13; +constexpr std::uint32_t OpStride = 0x200; +constexpr std::uint32_t Special = OpCount * OpStride; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint8_t Fill = 0xcd; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array GlobalAtomicsCode{ + 0x34020083, 0x34040084, 0x340a0083, 0x7e2802ff, 0x05e471e1, 0x7e2a02ff, 0x05e471e1, 0x4a060280, + 0x4a080480, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0a80, 0xdcc18000, 0x14080a03, 0xbf8c3f70, + 0xe0701000, 0x80011406, 0x4a0602ff, 0x00000200, 0x4a0804ff, 0x00000400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00000200, 0xdcc58000, 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, + 0x4a0602ff, 0x00000400, 0x4a0804ff, 0x00000800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00000400, 0xdcc98000, 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, 0x4a0602ff, 0x00000600, + 0x4a0804ff, 0x00000c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000600, 0xdccd8000, + 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, 0x4a0602ff, 0x00000800, 0x4a0804ff, 0x00001000, + 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000800, 0xdcd58000, 0x14080a03, 0xbf8c3f70, + 0xe0701000, 0x80011406, 0x4a0602ff, 0x00000a00, 0x4a0804ff, 0x00001400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00000a00, 0xdcd98000, 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, + 0x4a0602ff, 0x00000c00, 0x4a0804ff, 0x00001800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00000c00, 0xdcdd8000, 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, 0x4a0602ff, 0x00000e00, + 0x4a0804ff, 0x00001c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000e00, 0xdce18000, + 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, 0x4a0602ff, 0x00001000, 0x4a0804ff, 0x00002000, + 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00001000, 0xdce58000, 0x14080a03, 0xbf8c3f70, + 0xe0701000, 0x80011406, 0x4a0602ff, 0x00001200, 0x4a0804ff, 0x00002400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00001200, 0xdce98000, 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, + 0x4a0602ff, 0x00001400, 0x4a0804ff, 0x00002800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00001400, 0xdced8000, 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, 0x4a0602ff, 0x00001600, + 0x4a0804ff, 0x00002c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00001600, 0xdcf18000, + 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, 0x4a0602ff, 0x00001800, 0x4a0804ff, 0x00003000, + 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00001800, 0xdcf58000, 0x14080a03, 0xbf8c3f70, + 0xe0701000, 0x80011406, 0x4a0602ff, 0x00001a00, 0x4a0804ff, 0x00003400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00001a00, 0xdd418000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, + 0x4a0602ff, 0x00001c00, 0x4a0804ff, 0x00003800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00001c00, 0xdd458000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00001e00, + 0x4a0804ff, 0x00003c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00001e00, 0xdd498000, + 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002000, 0x4a0804ff, 0x00004000, + 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00002000, 0xdd4d8000, 0x14080a03, 0xbf8c3f70, + 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002200, 0x4a0804ff, 0x00004400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00002200, 0xdd558000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, + 0x4a0602ff, 0x00002400, 0x4a0804ff, 0x00004800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00002400, 0xdd598000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002600, + 0x4a0804ff, 0x00004c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00002600, 0xdd5d8000, + 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002800, 0x4a0804ff, 0x00005000, + 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00002800, 0xdd618000, 0x14080a03, 0xbf8c3f70, + 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002a00, 0x4a0804ff, 0x00005400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00002a00, 0xdd658000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, + 0x4a0602ff, 0x00002c00, 0x4a0804ff, 0x00005800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00002c00, 0xdd698000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002e00, + 0x4a0804ff, 0x00005c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00002e00, 0xdd6d8000, + 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00003000, 0x7e2802ff, 0x05e471e1, + 0x4a140081, 0xdcc88000, 0x00080a03, 0xbf8c3f70, 0xd70f6a1e, 0x02020608, 0x7e3e0209, 0x503e3e80, + 0xd70f6a1e, 0x02023cff, 0x00000200, 0x503e3e80, 0x34140088, 0xdced0000, 0x147d0a1e, 0xbf8c0070, + 0x4a0c0aff, 0x00003000, 0xe0701000, 0x80011406, 0x7e0602ff, 0x00003400, 0x4a1414ff, 0x00000100, + 0xdcc98004, 0x14080a03, 0xbf8c3f70, 0x4a0c0aff, 0x00003200, 0xe0701000, 0x80011406, 0x7da80090, + 0x4a0602ff, 0x00003600, 0x7e1402ff, 0x000000f0, 0xdce88000, 0x00080a03, 0xbf8c3f70, 0xbf810000 +}; + +struct Row { + std::uint32_t op; + std::uint64_t memory; + std::uint64_t data; + std::uint64_t comparator; + std::uint64_t returned; + std::uint64_t final; +}; + +constexpr std::array Rows{{ + {0, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {0, 0x1ull, 0x80000000ull, 0xf9677975ull, 0x1ull, 0x80000000ull}, + {0, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0xffffffffull}, + {0, 0x7fffffffull, 0x0ull, 0xfc5039f7ull, 0x7fffffffull, 0x0ull}, + {0, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0x7fffffffull}, + {0, 0x80000001ull, 0xfffffffeull, 0x1098ad65ull, 0x80000001ull, 0xfffffffeull}, + {0, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0xdeadbeefull}, + {0, 0xffffffffull, 0x2ull, 0x19211eull, 0xffffffffull, 0x2ull}, + {0, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x80000001ull}, + {0, 0xdeadbeefull, 0x12345678ull, 0xcdbc2be9ull, 0xdeadbeefull, 0x12345678ull}, + {0, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {0, 0x1ull, 0x80000000ull, 0xa3639922ull, 0x1ull, 0x80000000ull}, + {0, 0x46e9a894ull, 0xd107eea9ull, 0x46e9a894ull, 0x46e9a894ull, 0xd107eea9ull}, + {0, 0xbc50bcc8ull, 0x58ffc3ecull, 0x4cd661ull, 0xbc50bcc8ull, 0x58ffc3ecull}, + {0, 0xb9093c7eull, 0x665932a4ull, 0xb9093c7eull, 0xb9093c7eull, 0x665932a4ull}, + {0, 0xd88449d9ull, 0xfe358db5ull, 0xba80659full, 0xd88449d9ull, 0xfe358db5ull}, + {1, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {1, 0x1ull, 0x80000000ull, 0x109949f9ull, 0x1ull, 0x1ull}, + {1, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0xffffffffull}, + {1, 0x7fffffffull, 0x0ull, 0x22d348c2ull, 0x7fffffffull, 0x7fffffffull}, + {1, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0x7fffffffull}, + {1, 0x80000001ull, 0xfffffffeull, 0x42872e08ull, 0x80000001ull, 0x80000001ull}, + {1, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0xdeadbeefull}, + {1, 0xffffffffull, 0x2ull, 0x675c2152ull, 0xffffffffull, 0xffffffffull}, + {1, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x80000001ull}, + {1, 0xdeadbeefull, 0x12345678ull, 0x2f1ed38ull, 0xdeadbeefull, 0xdeadbeefull}, + {1, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {1, 0x1ull, 0x80000000ull, 0xd6be8bc1ull, 0x1ull, 0x1ull}, + {1, 0x283d5502ull, 0xfc565b67ull, 0x283d5502ull, 0x283d5502ull, 0xfc565b67ull}, + {1, 0xe503313aull, 0x1722ca72ull, 0xdd44c959ull, 0xe503313aull, 0xe503313aull}, + {1, 0x2a841e2ull, 0x52a450e2ull, 0x2a841e2ull, 0x2a841e2ull, 0x52a450e2ull}, + {1, 0xcc440ac9ull, 0x46313f70ull, 0xee4a57bbull, 0xcc440ac9ull, 0xcc440ac9ull}, + {2, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {2, 0x1ull, 0x80000000ull, 0x452f2ffcull, 0x1ull, 0x80000001ull}, + {2, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0x1ull}, + {2, 0x7fffffffull, 0x0ull, 0xf5091963ull, 0x7fffffffull, 0x7fffffffull}, + {2, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0xffffffffull}, + {2, 0x80000001ull, 0xfffffffeull, 0xeb165c4ull, 0x80000001ull, 0x7fffffffull}, + {2, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0xdeadbeedull}, + {2, 0xffffffffull, 0x2ull, 0x1a438e52ull, 0xffffffffull, 0x1ull}, + {2, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x92345679ull}, + {2, 0xdeadbeefull, 0x12345678ull, 0x64638c03ull, 0xdeadbeefull, 0xf0e21567ull}, + {2, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {2, 0x1ull, 0x80000000ull, 0xe6d2996dull, 0x1ull, 0x80000001ull}, + {2, 0xa9bdd75aull, 0x70734273ull, 0xa9bdd75aull, 0xa9bdd75aull, 0x1a3119cdull}, + {2, 0x8aa692d0ull, 0x74d3a033ull, 0x5a7fd942ull, 0x8aa692d0ull, 0xff7a3303ull}, + {2, 0x201fe536ull, 0xf8ddb196ull, 0x201fe536ull, 0x201fe536ull, 0x18fd96ccull}, + {2, 0x15f79627ull, 0xb2346d27ull, 0x8a6b642dull, 0x15f79627ull, 0xc82c034eull}, + {3, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0xffffffffull}, + {3, 0x1ull, 0x80000000ull, 0x69639719ull, 0x1ull, 0x80000001ull}, + {3, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0x3ull}, + {3, 0x7fffffffull, 0x0ull, 0x89e34c92ull, 0x7fffffffull, 0x7fffffffull}, + {3, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0x1ull}, + {3, 0x80000001ull, 0xfffffffeull, 0x73229920ull, 0x80000001ull, 0x80000003ull}, + {3, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0x2152410full}, + {3, 0xffffffffull, 0x2ull, 0x66310986ull, 0xffffffffull, 0xfffffffdull}, + {3, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x92345677ull}, + {3, 0xdeadbeefull, 0x12345678ull, 0x8fa4a357ull, 0xdeadbeefull, 0xcc796877ull}, + {3, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0xffffffffull}, + {3, 0x1ull, 0x80000000ull, 0x64bdad88ull, 0x1ull, 0x80000001ull}, + {3, 0xd5b148e7ull, 0x91d4c68ull, 0xd5b148e7ull, 0xd5b148e7ull, 0xcc93fc7full}, + {3, 0xb6cd96e4ull, 0xa145e36full, 0x6efd9be0ull, 0xb6cd96e4ull, 0x1587b375ull}, + {3, 0xf123997cull, 0x12d3d18aull, 0xf123997cull, 0xf123997cull, 0xde4fc7f2ull}, + {3, 0x62bfff89ull, 0x2106fea7ull, 0x22db0452ull, 0x62bfff89ull, 0x41b900e2ull}, + {4, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x0ull}, + {4, 0x1ull, 0x80000000ull, 0xdbbb69abull, 0x1ull, 0x80000000ull}, + {4, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0xffffffffull}, + {4, 0x7fffffffull, 0x0ull, 0xdffce4e3ull, 0x7fffffffull, 0x0ull}, + {4, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0x80000000ull}, + {4, 0x80000001ull, 0xfffffffeull, 0x77ac152aull, 0x80000001ull, 0x80000001ull}, + {4, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0xdeadbeefull}, + {4, 0xffffffffull, 0x2ull, 0x24880f60ull, 0xffffffffull, 0xffffffffull}, + {4, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x80000001ull}, + {4, 0xdeadbeefull, 0x12345678ull, 0x6adc5e6full, 0xdeadbeefull, 0xdeadbeefull}, + {4, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x0ull}, + {4, 0x1ull, 0x80000000ull, 0x5c5e447eull, 0x1ull, 0x80000000ull}, + {4, 0xbc193f27ull, 0x5b2c838cull, 0xbc193f27ull, 0xbc193f27ull, 0xbc193f27ull}, + {4, 0xa69c5ce5ull, 0xbf61575eull, 0x341a640bull, 0xa69c5ce5ull, 0xa69c5ce5ull}, + {4, 0x1836851cull, 0x12fef585ull, 0x1836851cull, 0x1836851cull, 0x12fef585ull}, + {4, 0xaa5c538dull, 0x618e48b9ull, 0x56002b6cull, 0xaa5c538dull, 0xaa5c538dull}, + {5, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x0ull}, + {5, 0x1ull, 0x80000000ull, 0x21d3075eull, 0x1ull, 0x1ull}, + {5, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0x2ull}, + {5, 0x7fffffffull, 0x0ull, 0x16b60422ull, 0x7fffffffull, 0x0ull}, + {5, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0x7fffffffull}, + {5, 0x80000001ull, 0xfffffffeull, 0xfd31cfd7ull, 0x80000001ull, 0x80000001ull}, + {5, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0xdeadbeefull}, + {5, 0xffffffffull, 0x2ull, 0x6c5db104ull, 0xffffffffull, 0x2ull}, + {5, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x12345678ull}, + {5, 0xdeadbeefull, 0x12345678ull, 0xca2b6cbeull, 0xdeadbeefull, 0x12345678ull}, + {5, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x0ull}, + {5, 0x1ull, 0x80000000ull, 0xdc32d1aaull, 0x1ull, 0x1ull}, + {5, 0xfd11be0eull, 0x30eaba72ull, 0xfd11be0eull, 0xfd11be0eull, 0x30eaba72ull}, + {5, 0x4f696046ull, 0xed71a68aull, 0x59f1d18dull, 0x4f696046ull, 0x4f696046ull}, + {5, 0x2e192541ull, 0x1f6c7f26ull, 0x2e192541ull, 0x2e192541ull, 0x1f6c7f26ull}, + {5, 0x7a1ff8aeull, 0xdae2a240ull, 0xd604dd68ull, 0x7a1ff8aeull, 0x7a1ff8aeull}, + {6, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {6, 0x1ull, 0x80000000ull, 0xadf8324cull, 0x1ull, 0x1ull}, + {6, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0x2ull}, + {6, 0x7fffffffull, 0x0ull, 0x9f75da08ull, 0x7fffffffull, 0x7fffffffull}, + {6, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0x7fffffffull}, + {6, 0x80000001ull, 0xfffffffeull, 0xcd48b9bdull, 0x80000001ull, 0xfffffffeull}, + {6, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0xfffffffeull}, + {6, 0xffffffffull, 0x2ull, 0x40591809ull, 0xffffffffull, 0x2ull}, + {6, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x12345678ull}, + {6, 0xdeadbeefull, 0x12345678ull, 0x951a2694ull, 0xdeadbeefull, 0x12345678ull}, + {6, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {6, 0x1ull, 0x80000000ull, 0x55ff16b5ull, 0x1ull, 0x1ull}, + {6, 0xadb9fc15ull, 0x9263cee2ull, 0xadb9fc15ull, 0xadb9fc15ull, 0xadb9fc15ull}, + {6, 0xa055a0b2ull, 0xe06373c3ull, 0x406ffc43ull, 0xa055a0b2ull, 0xe06373c3ull}, + {6, 0xe442a859ull, 0xbcdeb639ull, 0xe442a859ull, 0xe442a859ull, 0xe442a859ull}, + {6, 0x238c6bf4ull, 0xfe8fedc6ull, 0x771aaf91ull, 0x238c6bf4ull, 0x238c6bf4ull}, + {7, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {7, 0x1ull, 0x80000000ull, 0x237244c9ull, 0x1ull, 0x80000000ull}, + {7, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0xffffffffull}, + {7, 0x7fffffffull, 0x0ull, 0xf62e5e4full, 0x7fffffffull, 0x7fffffffull}, + {7, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0x80000000ull}, + {7, 0x80000001ull, 0xfffffffeull, 0x90bfe553ull, 0x80000001ull, 0xfffffffeull}, + {7, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0xfffffffeull}, + {7, 0xffffffffull, 0x2ull, 0x44b4cc99ull, 0xffffffffull, 0xffffffffull}, + {7, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x80000001ull}, + {7, 0xdeadbeefull, 0x12345678ull, 0x99faa521ull, 0xdeadbeefull, 0xdeadbeefull}, + {7, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {7, 0x1ull, 0x80000000ull, 0x5136e276ull, 0x1ull, 0x80000000ull}, + {7, 0x64a7abb4ull, 0x146d75bcull, 0x64a7abb4ull, 0x64a7abb4ull, 0x64a7abb4ull}, + {7, 0x2ae5498eull, 0x63a37ae1ull, 0xde40e42bull, 0x2ae5498eull, 0x63a37ae1ull}, + {7, 0x99206560ull, 0x2a0494fdull, 0x99206560ull, 0x99206560ull, 0x99206560ull}, + {7, 0x634e77acull, 0xbfe9ef2bull, 0x70824c54ull, 0x634e77acull, 0xbfe9ef2bull}, + {8, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x0ull}, + {8, 0x1ull, 0x80000000ull, 0x5b03f923ull, 0x1ull, 0x0ull}, + {8, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0x2ull}, + {8, 0x7fffffffull, 0x0ull, 0x32c25127ull, 0x7fffffffull, 0x0ull}, + {8, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0x0ull}, + {8, 0x80000001ull, 0xfffffffeull, 0x1197fd88ull, 0x80000001ull, 0x80000000ull}, + {8, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0xdeadbeeeull}, + {8, 0xffffffffull, 0x2ull, 0x7aa29c55ull, 0xffffffffull, 0x2ull}, + {8, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x0ull}, + {8, 0xdeadbeefull, 0x12345678ull, 0x4dc23c90ull, 0xdeadbeefull, 0x12241668ull}, + {8, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x0ull}, + {8, 0x1ull, 0x80000000ull, 0xfa62c0f8ull, 0x1ull, 0x0ull}, + {8, 0xfd7537d1ull, 0x72643b5aull, 0xfd7537d1ull, 0xfd7537d1ull, 0x70643350ull}, + {8, 0xd8dd1ff5ull, 0x8007f94cull, 0x1c7c7b29ull, 0xd8dd1ff5ull, 0x80051944ull}, + {8, 0xadaa9f69ull, 0x542db28dull, 0xadaa9f69ull, 0xadaa9f69ull, 0x4289209ull}, + {8, 0xeba8ad6dull, 0xaf3f49e4ull, 0xbf7eef4eull, 0xeba8ad6dull, 0xab280964ull}, + {9, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {9, 0x1ull, 0x80000000ull, 0xd2dc3da8ull, 0x1ull, 0x80000001ull}, + {9, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0xffffffffull}, + {9, 0x7fffffffull, 0x0ull, 0xcbcf2e9cull, 0x7fffffffull, 0x7fffffffull}, + {9, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0xffffffffull}, + {9, 0x80000001ull, 0xfffffffeull, 0xf1616b9dull, 0x80000001ull, 0xffffffffull}, + {9, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0xffffffffull}, + {9, 0xffffffffull, 0x2ull, 0xaf3ff23eull, 0xffffffffull, 0xffffffffull}, + {9, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x92345679ull}, + {9, 0xdeadbeefull, 0x12345678ull, 0xd2b04560ull, 0xdeadbeefull, 0xdebdfeffull}, + {9, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {9, 0x1ull, 0x80000000ull, 0xef07f743ull, 0x1ull, 0x80000001ull}, + {9, 0x24c74e5ull, 0x5084dec1ull, 0x24c74e5ull, 0x24c74e5ull, 0x52ccfee5ull}, + {9, 0x8c6705a2ull, 0x1e525d48ull, 0xab05ab28ull, 0x8c6705a2ull, 0x9e775deaull}, + {9, 0xa36a05e5ull, 0x3a793efcull, 0xa36a05e5ull, 0xa36a05e5ull, 0xbb7b3ffdull}, + {9, 0xd5f31646ull, 0xa296c3e7ull, 0x213632ddull, 0xd5f31646ull, 0xf7f7d7e7ull}, + {10, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {10, 0x1ull, 0x80000000ull, 0x11e1dbaaull, 0x1ull, 0x80000001ull}, + {10, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0xfffffffdull}, + {10, 0x7fffffffull, 0x0ull, 0x2cd60e1aull, 0x7fffffffull, 0x7fffffffull}, + {10, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0xffffffffull}, + {10, 0x80000001ull, 0xfffffffeull, 0x1f025ad0ull, 0x80000001ull, 0x7fffffffull}, + {10, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0x21524111ull}, + {10, 0xffffffffull, 0x2ull, 0x5d9eb5b7ull, 0xffffffffull, 0xfffffffdull}, + {10, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x92345679ull}, + {10, 0xdeadbeefull, 0x12345678ull, 0xf167a808ull, 0xdeadbeefull, 0xcc99e897ull}, + {10, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {10, 0x1ull, 0x80000000ull, 0x9f4e6575ull, 0x1ull, 0x80000001ull}, + {10, 0x8bd3aa1cull, 0x44b01dcdull, 0x8bd3aa1cull, 0x8bd3aa1cull, 0xcf63b7d1ull}, + {10, 0xc2c143f7ull, 0x51bc9443ull, 0xd65e2124ull, 0xc2c143f7ull, 0x937dd7b4ull}, + {10, 0xb0cb286eull, 0xec5194a4ull, 0xb0cb286eull, 0xb0cb286eull, 0x5c9abccaull}, + {10, 0xe17a51ecull, 0xe0bfc82aull, 0x44dde368ull, 0xe17a51ecull, 0x1c599c6ull}, + {11, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {11, 0x1ull, 0x80000000ull, 0x20d071afull, 0x1ull, 0x2ull}, + {11, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0x3ull}, + {11, 0x7fffffffull, 0x0ull, 0x7a347eebull, 0x7fffffffull, 0x0ull}, + {11, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0x0ull}, + {11, 0x80000001ull, 0xfffffffeull, 0x56e21801ull, 0x80000001ull, 0x80000002ull}, + {11, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0x0ull}, + {11, 0xffffffffull, 0x2ull, 0x7a390ac1ull, 0xffffffffull, 0x0ull}, + {11, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x12345679ull}, + {11, 0xdeadbeefull, 0x12345678ull, 0xe457cd1full, 0xdeadbeefull, 0x0ull}, + {11, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {11, 0x1ull, 0x80000000ull, 0x460a51e2ull, 0x1ull, 0x2ull}, + {11, 0xe3202a2aull, 0xe3202a2bull, 0xe3202a2aull, 0xe3202a2aull, 0xe3202a2bull}, + {11, 0x9ec2949ull, 0x9ec2949ull, 0x947613e8ull, 0x9ec2949ull, 0x0ull}, + {11, 0x8f8118afull, 0x8f8118b0ull, 0x8f8118afull, 0x8f8118afull, 0x8f8118b0ull}, + {11, 0x761757b6ull, 0x761757b6ull, 0x8be15c75ull, 0x761757b6ull, 0x0ull}, + {12, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {12, 0x1ull, 0x80000000ull, 0x7c5a8c15ull, 0x1ull, 0x0ull}, + {12, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0x1ull}, + {12, 0x7fffffffull, 0x0ull, 0x29769614ull, 0x7fffffffull, 0x0ull}, + {12, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0x7fffffffull}, + {12, 0x80000001ull, 0xfffffffeull, 0x537a54d3ull, 0x80000001ull, 0x80000000ull}, + {12, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0xdeadbeefull}, + {12, 0xffffffffull, 0x2ull, 0x6add45d9ull, 0xffffffffull, 0x2ull}, + {12, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x12345677ull}, + {12, 0xdeadbeefull, 0x12345678ull, 0x978c0812ull, 0xdeadbeefull, 0x12345678ull}, + {12, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {12, 0x1ull, 0x80000000ull, 0xf67e5e26ull, 0x1ull, 0x0ull}, + {12, 0xebea6cf1ull, 0xebea6cf2ull, 0xebea6cf1ull, 0xebea6cf1ull, 0xebea6cf0ull}, + {12, 0xf9a9360ull, 0xf9a9360ull, 0xf85b81cdull, 0xf9a9360ull, 0xf9a935full}, + {12, 0x6e723d5cull, 0x6e723d5dull, 0x6e723d5cull, 0x6e723d5cull, 0x6e723d5bull}, + {12, 0x1f609b4full, 0x1f609b4full, 0x69d177f8ull, 0x1f609b4full, 0x1f609b4eull}, + {13, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {13, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, + {13, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {13, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xb210ee8d643f54e6ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, + {13, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {13, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, + {13, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, + {13, 0xfffffffeffffffffull, 0x1ull, 0xed5fb21be38948d7ull, 0xfffffffeffffffffull, 0x1ull}, + {13, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, + {13, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {13, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {13, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xb346daefa27ab170ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {13, 0x53bb9629497dfb3full, 0xa4abfed10ad2a242ull, 0x53bb9629497dfb3full, 0x53bb9629497dfb3full, 0xa4abfed10ad2a242ull}, + {13, 0x6c12816965391baaull, 0xce7cd7b44b45eaf3ull, 0x6c12816967391baaull, 0x6c12816965391baaull, 0xce7cd7b44b45eaf3ull}, + {13, 0xdba996ccffffffffull, 0x10bbe64100000001ull, 0xdba996ccffffffffull, 0xdba996ccffffffffull, 0x10bbe64100000001ull}, + {13, 0x5a7c138cffffffffull, 0x9c15fed00000001ull, 0x5a7c138cdfffffffull, 0x5a7c138cffffffffull, 0x9c15fed00000001ull}, + {14, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {14, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, + {14, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {14, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x166c48c6e9c1b01cull, 0x8000000000000000ull, 0x8000000000000000ull}, + {14, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {14, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {14, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, + {14, 0xfffffffeffffffffull, 0x1ull, 0x866b08e67ea9d02cull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, + {14, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, + {14, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, + {14, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {14, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xa54604e5b9b1311full, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {14, 0xb9a56d03ead2890full, 0x242f2f3b525af861ull, 0xb9a56d03ead2890full, 0xb9a56d03ead2890full, 0x242f2f3b525af861ull}, + {14, 0x65c783e7e26c0130ull, 0x19a0106761c2f186ull, 0x65c783e7e06c0130ull, 0x65c783e7e26c0130ull, 0x65c783e7e26c0130ull}, + {14, 0x7bebc240ffffffffull, 0x4da1ba4b00000001ull, 0x7bebc240ffffffffull, 0x7bebc240ffffffffull, 0x4da1ba4b00000001ull}, + {14, 0x57e3536bffffffffull, 0xd8d033bd00000001ull, 0x57e3536bdfffffffull, 0x57e3536bffffffffull, 0x57e3536bffffffffull}, + {15, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {15, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xffffffff00000000ull}, + {15, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {15, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd4db9193123f6ebaull, 0x8000000000000000ull, 0x923456789abcdef0ull}, + {15, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xfffffffffffffffeull}, + {15, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {15, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, + {15, 0xfffffffeffffffffull, 0x1ull, 0x25fd502c82facbc0ull, 0xfffffffeffffffffull, 0xffffffff00000000ull}, + {15, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, + {15, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {15, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xffffffff00000000ull}, + {15, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf9124f0e4aae24c5ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {15, 0xc3e86469baffc1dull, 0xc22f0e9d6d69338dull, 0xc3e86469baffc1dull, 0xc3e86469baffc1dull, 0xce6d94e409192faaull}, + {15, 0xe499e6544c91e53ull, 0x4647904bf55b8053ull, 0xe499e6546c91e53ull, 0xe499e6544c91e53ull, 0x54912eb13a249ea6ull}, + {15, 0x7a4a48e2ffffffffull, 0x7ccc4b5300000001ull, 0x7a4a48e2ffffffffull, 0x7a4a48e2ffffffffull, 0xf716943600000000ull}, + {15, 0xa925179dffffffffull, 0xb2b1ac1800000001ull, 0xa925179ddfffffffull, 0xa925179dffffffffull, 0x5bd6c3b600000000ull}, + {16, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x8000000000000001ull}, + {16, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x100000002ull}, + {16, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {16, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x6835bf86a066a97eull, 0x8000000000000000ull, 0x6dcba98765432110ull}, + {16, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0x0ull}, + {16, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {16, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ull}, + {16, 0xfffffffeffffffffull, 0x1ull, 0xbcbca7d9810b83c1ull, 0xfffffffeffffffffull, 0xfffffffefffffffeull}, + {16, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456779abcdef0ull}, + {16, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x8000000000000001ull}, + {16, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x100000002ull}, + {16, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x663c553a9a16e35ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {16, 0x63fbf4e8e453a4c5ull, 0xb7a15652045c43f6ull, 0x63fbf4e8e453a4c5ull, 0x63fbf4e8e453a4c5ull, 0xac5a9e96dff760cfull}, + {16, 0x1603b25645bdd8aaull, 0x1f87b96dd545e1f1ull, 0x1603b25647bdd8aaull, 0x1603b25645bdd8aaull, 0xf67bf8e87077f6b9ull}, + {16, 0x757fd168ffffffffull, 0x2356488500000001ull, 0x757fd168ffffffffull, 0x757fd168ffffffffull, 0x522988e3fffffffeull}, + {16, 0x752b2a35ffffffffull, 0x869e617c00000001ull, 0x752b2a35dfffffffull, 0x752b2a35ffffffffull, 0xee8cc8b9fffffffeull}, + {17, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, + {17, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, + {17, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {17, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd286c038892fd9d4ull, 0x8000000000000000ull, 0x8000000000000000ull}, + {17, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {17, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, + {17, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, + {17, 0xfffffffeffffffffull, 0x1ull, 0x80b34c5cb5907e40ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, + {17, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, + {17, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, + {17, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {17, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xe7cd1f2260293767ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {17, 0x8372abbda96ba8eeull, 0x5e71be928ba29363ull, 0x8372abbda96ba8eeull, 0x8372abbda96ba8eeull, 0x8372abbda96ba8eeull}, + {17, 0xce77c3cdcc677910ull, 0xdf44f6f934647f0eull, 0xce77c3cdce677910ull, 0xce77c3cdcc677910ull, 0xce77c3cdcc677910ull}, + {17, 0x9b3edbb1ffffffffull, 0xf4f16cf100000001ull, 0x9b3edbb1ffffffffull, 0x9b3edbb1ffffffffull, 0x9b3edbb1ffffffffull}, + {17, 0xadf3fbf0ffffffffull, 0x8b29592900000001ull, 0xadf3fbf0dfffffffull, 0xadf3fbf0ffffffffull, 0x8b29592900000001ull}, + {18, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, + {18, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, + {18, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {18, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xff63cf82d01938e9ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, + {18, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {18, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, + {18, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, + {18, 0xfffffffeffffffffull, 0x1ull, 0x2a88358605f1254ull, 0xfffffffeffffffffull, 0x1ull}, + {18, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, + {18, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, + {18, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, + {18, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf078418740b4f6d3ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {18, 0x8d4a240ceafb92f2ull, 0xd789c424b5282b1dull, 0x8d4a240ceafb92f2ull, 0x8d4a240ceafb92f2ull, 0x8d4a240ceafb92f2ull}, + {18, 0x64b3620ed7a9434full, 0x930b9894c3741be0ull, 0x64b3620ed5a9434full, 0x64b3620ed7a9434full, 0x64b3620ed7a9434full}, + {18, 0x62a12f63ffffffffull, 0x4a1df58400000001ull, 0x62a12f63ffffffffull, 0x62a12f63ffffffffull, 0x4a1df58400000001ull}, + {18, 0xd0826a0cffffffffull, 0x14fe08aa00000001ull, 0xd0826a0cdfffffffull, 0xd0826a0cffffffffull, 0x14fe08aa00000001ull}, + {19, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {19, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, + {19, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {19, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x35f6b71611838017ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, + {19, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {19, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {19, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x100000000ull}, + {19, 0xfffffffeffffffffull, 0x1ull, 0x884dcfec36f43da6ull, 0xfffffffeffffffffull, 0x1ull}, + {19, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull}, + {19, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {19, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, + {19, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x97e21514d87b299dull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {19, 0x7ae9f54bc19e56ddull, 0x4a6ac7aeb830ed76ull, 0x7ae9f54bc19e56ddull, 0x7ae9f54bc19e56ddull, 0x7ae9f54bc19e56ddull}, + {19, 0x5b1c8aa1eb9a258cull, 0xf4a0ea8b3badac72ull, 0x5b1c8aa1e99a258cull, 0x5b1c8aa1eb9a258cull, 0x5b1c8aa1eb9a258cull}, + {19, 0x869f35deffffffffull, 0xdeffa68100000001ull, 0x869f35deffffffffull, 0x869f35deffffffffull, 0xdeffa68100000001ull}, + {19, 0x5752b4dfffffffffull, 0x2a51ee7e00000001ull, 0x5752b4dfdfffffffull, 0x5752b4dfffffffffull, 0x5752b4dfffffffffull}, + {20, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {20, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, + {20, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {20, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd316f470e2d8993bull, 0x8000000000000000ull, 0x8000000000000000ull}, + {20, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {20, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {20, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x100000000ull}, + {20, 0xfffffffeffffffffull, 0x1ull, 0xfc2b7ace1072e7b9ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, + {20, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull}, + {20, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {20, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {20, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x142c1e683d47a123ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {20, 0x5ea0e74f0d30cb76ull, 0xecd62560f1407a13ull, 0x5ea0e74f0d30cb76ull, 0x5ea0e74f0d30cb76ull, 0xecd62560f1407a13ull}, + {20, 0x6f8664b99db31c8bull, 0x77417f9d1c387d00ull, 0x6f8664b99fb31c8bull, 0x6f8664b99db31c8bull, 0x77417f9d1c387d00ull}, + {20, 0x1e0ce078ffffffffull, 0xc130cf3c00000001ull, 0x1e0ce078ffffffffull, 0x1e0ce078ffffffffull, 0xc130cf3c00000001ull}, + {20, 0xd160d889ffffffffull, 0x2b456f6800000001ull, 0xd160d889dfffffffull, 0xd160d889ffffffffull, 0xd160d889ffffffffull}, + {21, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, + {21, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, + {21, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x0ull}, + {21, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x691beca72ac31615ull, 0x8000000000000000ull, 0x0ull}, + {21, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {21, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, + {21, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x0ull}, + {21, 0xfffffffeffffffffull, 0x1ull, 0xc89594109911783aull, 0xfffffffeffffffffull, 0x1ull}, + {21, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x0ull}, + {21, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, + {21, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, + {21, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xa69bfe93d6efb4ddull, 0x7fffffffffffffffull, 0x0ull}, + {21, 0xd5e7cc579d21963aull, 0x93826a1ee8ab2ed1ull, 0xd5e7cc579d21963aull, 0xd5e7cc579d21963aull, 0x9182481688210610ull}, + {21, 0x657c382c4e1cfcddull, 0x837a0dc63ac35f32ull, 0x657c382c4c1cfcddull, 0x657c382c4e1cfcddull, 0x17808040a005c10ull}, + {21, 0x7552c284ffffffffull, 0x995c2cf200000001ull, 0x7552c284ffffffffull, 0x7552c284ffffffffull, 0x1150008000000001ull}, + {21, 0xe20d4f8dffffffffull, 0x4864cdbc00000001ull, 0xe20d4f8ddfffffffull, 0xe20d4f8dffffffffull, 0x40044d8c00000001ull}, + {22, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {22, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, + {22, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {22, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xbad3dfdbb007d4ecull, 0x8000000000000000ull, 0x923456789abcdef0ull}, + {22, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {22, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {22, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, + {22, 0xfffffffeffffffffull, 0x1ull, 0xedecea3c6fa0d23full, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, + {22, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, + {22, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {22, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {22, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf7926675f45d1971ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {22, 0x360443a82569e20cull, 0x3800d9ff899231faull, 0x360443a82569e20cull, 0x360443a82569e20cull, 0x3e04dbffadfbf3feull}, + {22, 0xc265df77a6c71a0dull, 0x5e94d6ccc50557f8ull, 0xc265df77a4c71a0dull, 0xc265df77a6c71a0dull, 0xdef5dfffe7c75ffdull}, + {22, 0x41a90d6bffffffffull, 0x4bb3092400000001ull, 0x41a90d6bffffffffull, 0x41a90d6bffffffffull, 0x4bbb0d6fffffffffull}, + {22, 0xc54e9269ffffffffull, 0xf536f34000000001ull, 0xc54e9269dfffffffull, 0xc54e9269ffffffffull, 0xf57ef369ffffffffull}, + {23, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {23, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffefffffffeull}, + {23, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {23, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xe3b411de678ee5c0ull, 0x8000000000000000ull, 0x923456789abcdef0ull}, + {23, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0x0ull}, + {23, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {23, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, + {23, 0xfffffffeffffffffull, 0x1ull, 0xe4e171d83c299defull, 0xfffffffeffffffffull, 0xfffffffefffffffeull}, + {23, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, + {23, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {23, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffefffffffeull}, + {23, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xaa23249b400d9ebdull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {23, 0xc1f06114a97ad060ull, 0x679f7fe64c950a15ull, 0xc1f06114a97ad060ull, 0xc1f06114a97ad060ull, 0xa66f1ef2e5efda75ull}, + {23, 0x59bdb13042bcfe48ull, 0x1669010b3ea903bbull, 0x59bdb13040bcfe48ull, 0x59bdb13042bcfe48ull, 0x4fd4b03b7c15fdf3ull}, + {23, 0x701d03a9ffffffffull, 0xf96a081600000001ull, 0x701d03a9ffffffffull, 0x701d03a9ffffffffull, 0x89770bbffffffffeull}, + {23, 0x9b817862ffffffffull, 0x3c3e50fe00000001ull, 0x9b817862dfffffffull, 0x9b817862ffffffffull, 0xa7bf289cfffffffeull}, +}}; + +class GuestBlock { +public: + explicit GuestBlock(bool writable) { +#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 atomics: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, writable); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + const std::uint8_t* Data() const { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +void Put(std::vector& image, std::uint32_t offset, std::uint64_t value, std::uint32_t bytes) { + for (std::uint32_t byte = 0; byte < bytes; ++byte) image.at(offset + byte) = static_cast(value >> (byte * 8u)); +} + +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::uint64_t value) { + char text[24]; + std::snprintf(text, sizeof(text), "0x%llx", static_cast(value)); + return text; +} + +const Row& RowOf(std::uint32_t op, std::uint32_t tid) { + return Rows.at(op * RowsPerOp + tid % RowsPerOp); +} + +std::uint32_t OpBytes(std::uint32_t op) { + return op < NarrowOps ? 4u : 8u; +} + +std::vector Initial() { + std::vector image(BlockBytes, Fill); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).memory, OpBytes(op)); + } + } + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, Special + tid * 8u, 0x1000u * tid, 4); + Put(image, Special + Threads * 8u + tid * 8u, 0xa5a5a5a5u ^ tid, 4); + Put(image, Special + Threads * 24u + tid * 8u, 0x0f0f0f00u + tid, 4); + } + Put(image, Special + Threads * 16u + 4u, 0x11u, 4); + return image; +} + +std::vector Expected() { + auto image = Initial(); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).final, OpBytes(op)); + } + } + std::uint32_t total = 0x11u; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, Special + tid * 8u, 0x1000u * tid + tid + 1u, 4); + Put(image, Special + Threads * 8u + tid * 8u, (0xa5a5a5a5u ^ tid) ^ (tid << 8u), 4); + if (tid < 16u) Put(image, Special + Threads * 24u + tid * 8u, (0x0f0f0f00u + tid) | 0xf0u, 4); + total += (tid + 1u) << 8u; + } + Put(image, Special + Threads * 16u + 4u, total, 4); + return image; +} + +void FillInput() { + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + auto* words = &Input[(op * Threads + tid) * 4u]; + if (OpBytes(op) == 4u) { + words[0] = static_cast(row.data); + words[1] = static_cast(row.comparator); + words[2] = 0u; + words[3] = 0u; + } else { + words[0] = static_cast(row.data); + words[1] = static_cast(row.data >> 32u); + words[2] = static_cast(row.comparator); + words[3] = static_cast(row.comparator >> 32u); + } + } + } +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 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); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::span code(GlobalAtomicsCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 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, memory}, + device.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(); +} + +void RunAtomics(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + FillInput(); + Output.fill(Sentinel); + Dispatch(device, waveSize, guest.Data()); + const auto wave = "global atomics: wave" + std::to_string(waveSize) + " "; + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + const auto* words = &Output[(op * Threads + tid) * 2u]; + const auto returned = OpBytes(op) == 4u ? static_cast(words[0]) : (static_cast(words[1]) << 32u) | words[0]; + Require(returned == row.returned, wave + "op " + std::to_string(op) + " lane " + std::to_string(tid) + " returned " + Hex(returned) + ", expected " + Hex(row.returned)); + } + } + std::vector> order; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto flat = Output[(OpCount * Threads + tid) * 2u]; + Require(flat == (0xa5a5a5a5u ^ tid), wave + "flat xor returned " + Hex(flat) + " to lane " + std::to_string(tid)); + order.emplace_back(Output[((OpCount + 1u) * Threads + tid) * 2u], tid); + } + std::sort(order.begin(), order.end()); + std::uint32_t running = 0x11u; + for (const auto& [returned, tid] : order) { + Require(returned == running, wave + "contended add returned " + Hex(returned) + " to lane " + std::to_string(tid) + ", expected " + Hex(running)); + running += (tid + 1u) << 8u; + } + const auto expected = Expected(); + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + const auto actual = guest.Data()[offset]; + Require(actual == expected[offset], wave + "byte " + std::to_string(offset) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[offset])); + } +} + +void RunReadOnly(AgcDriver::VulkanDevice& device, GuestBlock& guest) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + FillInput(); + Dispatch(device, 32, guest.Data()); + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + Require(guest.Data()[offset] == initial[offset], "global atomics: an atomic into a read-only range changed byte " + std::to_string(offset)); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock writable(true); + GuestBlock readOnly(false); + RunAtomics(*device, writable, 32); + RunAtomics(*device, writable, 64); + RunReadOnly(*device, readOnly); + std::puts("global atomics tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp index 8bd56b1f4..7ef446397 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp @@ -293,6 +293,30 @@ enum class IrOpcode : std::uint16_t { StoreAddressU8, StoreAddressU16, StoreAddressU32, + AddressAtomicSwap32, + AddressAtomicCmpSwap32, + AddressAtomicIAdd32, + AddressAtomicISub32, + AddressAtomicSMin32, + AddressAtomicUMin32, + AddressAtomicSMax32, + AddressAtomicUMax32, + AddressAtomicAnd32, + AddressAtomicOr32, + AddressAtomicXor32, + AddressAtomicInc32, + AddressAtomicDec32, + AddressAtomicSwap64, + AddressAtomicCmpSwap64, + AddressAtomicIAdd64, + AddressAtomicISub64, + AddressAtomicSMin64, + AddressAtomicUMin64, + AddressAtomicSMax64, + AddressAtomicUMax64, + AddressAtomicAnd64, + AddressAtomicOr64, + AddressAtomicXor64, LoadBufferU8, LoadBufferU16, LoadBufferU32, @@ -428,7 +452,7 @@ enum class IrOpcode : std::uint16_t { enum class BufferAccess { None, Read, Write, Atomic }; enum class SharedAccess { None, Read, Write, Atomic, Append, Consume }; -enum class AddressAccess { None, Read, Write }; +enum class AddressAccess { None, Read, Write, Atomic }; enum class ImageAccess { None, Read, Write, Atomic }; enum class ImageResourceClass { None, Sampled, Storage }; diff --git a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp index 158279a36..497292518 100644 --- a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp +++ b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp @@ -334,6 +334,30 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("StoreAddressU8", Void, AddressResource, U32, U32, U8, U1), makeMeta("StoreAddressU16", Void, AddressResource, U32, U32, U16, U1), makeMeta("StoreAddressU32", Void, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicSwap32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicCmpSwap32", U32, AddressResource, U32, U32, U32, U32, U1), + makeMeta("AddressAtomicIAdd32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicISub32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicSMin32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicUMin32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicSMax32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicUMax32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicAnd32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicOr32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicXor32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicInc32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicDec32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicSwap64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicCmpSwap64", U64, AddressResource, U32, U32, U64, U64, U1), + makeMeta("AddressAtomicIAdd64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicISub64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicSMin64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicUMin64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicSMax64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicUMax64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicAnd64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicOr64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicXor64", U64, AddressResource, U32, U32, U64, U1), makeMeta("LoadBufferU8", U8, BufferResource, U32, U32, U32, U1), makeMeta("LoadBufferU16", U16, BufferResource, U32, U32, U32, U1), makeMeta("LoadBufferU32", U32, BufferResource, U32, U32, U32, U1), @@ -702,6 +726,32 @@ AddressOpcodeInfo AddressOpcodeInfoOf(IrOpcode opcode) { return {AddressAccess::Write, 16u}; case IrOpcode::StoreAddressU32: return {AddressAccess::Write, 32u}; + case IrOpcode::AddressAtomicSwap32: + case IrOpcode::AddressAtomicCmpSwap32: + case IrOpcode::AddressAtomicIAdd32: + case IrOpcode::AddressAtomicISub32: + case IrOpcode::AddressAtomicSMin32: + case IrOpcode::AddressAtomicUMin32: + case IrOpcode::AddressAtomicSMax32: + case IrOpcode::AddressAtomicUMax32: + case IrOpcode::AddressAtomicAnd32: + case IrOpcode::AddressAtomicOr32: + case IrOpcode::AddressAtomicXor32: + case IrOpcode::AddressAtomicInc32: + case IrOpcode::AddressAtomicDec32: + return {AddressAccess::Atomic, 32u}; + case IrOpcode::AddressAtomicSwap64: + case IrOpcode::AddressAtomicCmpSwap64: + case IrOpcode::AddressAtomicIAdd64: + case IrOpcode::AddressAtomicISub64: + case IrOpcode::AddressAtomicSMin64: + case IrOpcode::AddressAtomicUMin64: + case IrOpcode::AddressAtomicSMax64: + case IrOpcode::AddressAtomicUMax64: + case IrOpcode::AddressAtomicAnd64: + case IrOpcode::AddressAtomicOr64: + case IrOpcode::AddressAtomicXor64: + return {AddressAccess::Atomic, 32u, 2u}; default: return {}; } @@ -757,7 +807,8 @@ bool IrOpcodeHasSideEffects(IrOpcode opcode) { if (sharedAccess == SharedAccess::Write || sharedAccess == SharedAccess::Atomic || sharedAccess == SharedAccess::Append || sharedAccess == SharedAccess::Consume) { return true; } - if (AddressOpcodeInfoOf(opcode).access == AddressAccess::Write) { + const AddressAccess addressAccess = AddressOpcodeInfoOf(opcode).access; + if (addressAccess == AddressAccess::Write || addressAccess == AddressAccess::Atomic) { return true; } const ImageAccess imageAccess = ImageOpcodeInfoOf(opcode).access; diff --git a/core/shader/recompiler/Optimization/src/ResourceTracker.cpp b/core/shader/recompiler/Optimization/src/ResourceTracker.cpp index d57416cdb..bcd33a91c 100644 --- a/core/shader/recompiler/Optimization/src/ResourceTracker.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceTracker.cpp @@ -1016,7 +1016,7 @@ private: std::fprintf(stderr, "[bda] %s at pc 0x%08x: %s access, offset %s%s\n", std::string(IrOpcodeName(op)).c_str(), flags.pc, kind, immediateOffset ? "immediate" : "dynamic", memory.kind == ResourceKind::ScalarAddress && !immediateOffset ? " (a register offset is not planned by the SRT walker)" : ""); } m_info.usesDma = true; - m_info.bdaWrites = m_info.bdaWrites || addressInfo.access == AddressAccess::Write; + m_info.bdaWrites = m_info.bdaWrites || addressInfo.access == AddressAccess::Write || addressInfo.access == AddressAccess::Atomic; return; } diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp index 0c0d6b6de..18932387b 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -776,6 +776,30 @@ enum class RdnaOpcode : std::uint16_t { FlatStoreDwordx2, FlatStoreDwordx3, FlatStoreDwordx4, + FlatAtomicSwap, + FlatAtomicCmpswap, + FlatAtomicAdd, + FlatAtomicSub, + FlatAtomicSmin, + FlatAtomicUmin, + FlatAtomicSmax, + FlatAtomicUmax, + FlatAtomicAnd, + FlatAtomicOr, + FlatAtomicXor, + FlatAtomicInc, + FlatAtomicDec, + FlatAtomicSwapX2, + FlatAtomicCmpswapX2, + FlatAtomicAddX2, + FlatAtomicSubX2, + FlatAtomicSminX2, + FlatAtomicUminX2, + FlatAtomicSmaxX2, + FlatAtomicUmaxX2, + FlatAtomicAndX2, + FlatAtomicOrX2, + FlatAtomicXorX2, DsAddU32, DsAddRtnU32, DsSubU32, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp index 52a0b30ba..cb034b22f 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp @@ -184,6 +184,30 @@ constexpr MemoryOpcodeInfo flatOpcodes[] = { {0x25u, RdnaOpcode::FlatLoadShortD16Hi, 1, 16, false, false, false}, {0x19u, RdnaOpcode::FlatStoreByteD16Hi, 1, 8, false, false, false}, {0x1bu, RdnaOpcode::FlatStoreShortD16Hi, 1, 16, false, false, false}, + {0x30u, RdnaOpcode::FlatAtomicSwap, 1, 32, false, false, false}, + {0x31u, RdnaOpcode::FlatAtomicCmpswap, 1, 32, false, false, false}, + {0x32u, RdnaOpcode::FlatAtomicAdd, 1, 32, false, false, false}, + {0x33u, RdnaOpcode::FlatAtomicSub, 1, 32, false, false, false}, + {0x35u, RdnaOpcode::FlatAtomicSmin, 1, 32, false, false, false}, + {0x36u, RdnaOpcode::FlatAtomicUmin, 1, 32, false, false, false}, + {0x37u, RdnaOpcode::FlatAtomicSmax, 1, 32, false, false, false}, + {0x38u, RdnaOpcode::FlatAtomicUmax, 1, 32, false, false, false}, + {0x39u, RdnaOpcode::FlatAtomicAnd, 1, 32, false, false, false}, + {0x3au, RdnaOpcode::FlatAtomicOr, 1, 32, false, false, false}, + {0x3bu, RdnaOpcode::FlatAtomicXor, 1, 32, false, false, false}, + {0x3cu, RdnaOpcode::FlatAtomicInc, 1, 32, false, false, false}, + {0x3du, RdnaOpcode::FlatAtomicDec, 1, 32, false, false, false}, + {0x50u, RdnaOpcode::FlatAtomicSwapX2, 2, 32, false, false, false}, + {0x51u, RdnaOpcode::FlatAtomicCmpswapX2, 2, 32, false, false, false}, + {0x52u, RdnaOpcode::FlatAtomicAddX2, 2, 32, false, false, false}, + {0x53u, RdnaOpcode::FlatAtomicSubX2, 2, 32, false, false, false}, + {0x55u, RdnaOpcode::FlatAtomicSminX2, 2, 32, false, false, false}, + {0x56u, RdnaOpcode::FlatAtomicUminX2, 2, 32, false, false, false}, + {0x57u, RdnaOpcode::FlatAtomicSmaxX2, 2, 32, false, false, false}, + {0x58u, RdnaOpcode::FlatAtomicUmaxX2, 2, 32, false, false, false}, + {0x59u, RdnaOpcode::FlatAtomicAndX2, 2, 32, false, false, false}, + {0x5au, RdnaOpcode::FlatAtomicOrX2, 2, 32, false, false, false}, + {0x5bu, RdnaOpcode::FlatAtomicXorX2, 2, 32, false, false, false}, }; constexpr MemoryOpcodeInfo dsOpcodes[] = { @@ -533,6 +557,37 @@ bool isFlatStoreOpcode(RdnaOpcode opcode) { } } +bool isFlatAtomicOpcode(RdnaOpcode opcode) { + switch (opcode) { + case RdnaOpcode::FlatAtomicSwap: + case RdnaOpcode::FlatAtomicCmpswap: + case RdnaOpcode::FlatAtomicAdd: + case RdnaOpcode::FlatAtomicSub: + case RdnaOpcode::FlatAtomicSmin: + case RdnaOpcode::FlatAtomicUmin: + case RdnaOpcode::FlatAtomicSmax: + case RdnaOpcode::FlatAtomicUmax: + case RdnaOpcode::FlatAtomicAnd: + case RdnaOpcode::FlatAtomicOr: + case RdnaOpcode::FlatAtomicXor: + case RdnaOpcode::FlatAtomicInc: + case RdnaOpcode::FlatAtomicDec: + case RdnaOpcode::FlatAtomicSwapX2: + case RdnaOpcode::FlatAtomicCmpswapX2: + case RdnaOpcode::FlatAtomicAddX2: + case RdnaOpcode::FlatAtomicSubX2: + case RdnaOpcode::FlatAtomicSminX2: + case RdnaOpcode::FlatAtomicUminX2: + case RdnaOpcode::FlatAtomicSmaxX2: + case RdnaOpcode::FlatAtomicUmaxX2: + case RdnaOpcode::FlatAtomicAndX2: + case RdnaOpcode::FlatAtomicOrX2: + case RdnaOpcode::FlatAtomicXorX2: + return true; + default: return false; + } +} + void setRawWords(RdnaInstruction& instruction, std::span code, std::uint32_t wordIndex, std::uint32_t wordCount) { instruction.wordCount = wordCount; for (std::uint32_t i = 0; i < wordCount; ++i) { @@ -741,10 +796,11 @@ RdnaInstruction DecodeRdnaFlat(std::uint32_t programCounter, std::span> 16u) & 0x7Fu; const auto data = (word1 >> 8u) & 0xFFu; const auto addr = word1 & 0xFFu; - if (dlc != 0u || lds != 0u || glc || slc || seg == 3u) { + const auto& info = lookupOpcode(flatOpcodes, opcode, "FLAT opcode is not supported"); + const bool atomic = isFlatAtomicOpcode(info.opcode); + if (dlc != 0u || lds != 0u || (glc && !atomic) || (atomic && seg == 1u) || slc || seg == 3u) { throw std::runtime_error("unsupported FLAT modifiers or segment"); } - const auto& info = lookupOpcode(flatOpcodes, opcode, "FLAT opcode is not supported"); RdnaInstruction instruction{}; instruction.programCounter = programCounter; @@ -776,6 +832,14 @@ RdnaInstruction DecodeRdnaFlat(std::uint32_t programCounter, std::span +#include namespace ShaderRecompiler { @@ -22,6 +23,7 @@ void StopBdaInvocationIf(SpirvEmitterState& state, std::uint32_t condition); std::uint32_t EmitBdaRead(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t address, std::uint32_t bits); void EmitBdaWrite(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t address, std::uint32_t value); void EmitBdaStore(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t address, std::uint32_t value, std::uint32_t bits); +std::uint32_t EmitBdaAtomic(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t address, std::uint32_t bytes, const std::function& operation); // Reads the dwords of a 1-4 dword load at address + offset that the program extracts, with one // table lookup for the whole span; the per-byte lookups of EmitBdaRead remain the fallback (and // the only path under APS5_BDA_BYTE_READS=1). Dwords the program never extracts are neither read diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp index 9926fdf1d..0859c0ebd 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp @@ -242,6 +242,7 @@ void EmitLoadAddressU32x4(SpirvValueEmitContext& ctx, const IrValue& inst); void EmitStoreAddressU8(SpirvValueEmitContext& ctx, const IrValue& inst); void EmitStoreAddressU16(SpirvValueEmitContext& ctx, const IrValue& inst); void EmitStoreAddressU32(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitAddressAtomic(SpirvValueEmitContext& ctx, const IrValue& inst); void EmitLoadBufferU8(SpirvValueEmitContext& ctx, const IrValue& inst); void EmitLoadBufferU16(SpirvValueEmitContext& ctx, const IrValue& inst); void EmitLoadBufferU32(SpirvValueEmitContext& ctx, const IrValue& inst); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp index 8740e59ce..4b510a5de 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp @@ -67,13 +67,13 @@ SpirvRequirements AnalyzeProgramRequirements(const IrProgram& program) { SpirvRequirements requirements {}; for (const IrBlock* block : program.BlockOrder()) { for (const IrValue* inst : block->Instructions()) { - if (BufferAccessOf(inst->Opcode()) == BufferAccess::Atomic && inst->Type() == IrType::U64) { + const auto addressAccess = AddressOpcodeInfoOf(inst->Opcode()).access; + if ((BufferAccessOf(inst->Opcode()) == BufferAccess::Atomic || addressAccess == AddressAccess::Atomic) && inst->Type() == IrType::U64) { requirements.bufferInt64Atomics = true; } if (IsFloat64Opcode(inst->Opcode())) { requirements.float64 = true; } - const auto addressAccess = AddressOpcodeInfoOf(inst->Opcode()).access; if (addressAccess != AddressAccess::None) { const auto memoryIndex = inst->Flags().index; if (memoryIndex >= program.Resources().memoryInfo.size()) { diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp index 981c26e92..df12bfd78 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp @@ -191,6 +191,28 @@ void EmitBdaStore(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t }); } +std::uint32_t EmitBdaAtomic(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t address, std::uint32_t bytes, const std::function& operation) { + auto& state = ctx.state; + if (bytes != 4u && bytes != 8u) ctx.Fail(inst, "unsupported BDA atomic width"); + if (state.bdaWritePointerFunction == 0 || state.bdaNoteWriteFunction == 0) ctx.Fail(inst, "BDA write functions are missing"); + const auto instruction = ConstantU32(state, inst.Flags().pc); + const auto type = bytes == 8u ? TypeScalarU64(state) : TypeU32(state); + const auto zero = bytes == 8u ? BdaConstant(state, 0u) : ConstantU32(state, 0u); + const auto unaligned = Binary(state, spv::OpINotEqual, TypeBool(state), Binary(state, spv::OpBitwiseAnd, TypeScalarU64(state), address, BdaConstant(state, bytes - 1u)), BdaConstant(state, 0u)); + EmitIfCondition(state, unaligned, [&] { RecordBdaFault(state, address, ConstantU32(state, bytes), instruction, BdaAbi::FaultReason::Unaligned); }); + return EmitValueOrDefaultIfCondition(state, Unary(state, spv::OpLogicalNot, TypeBool(state), unaligned), type, zero, [&] { + const auto physical = state.module.AllocateId(); + state.module.AddFunction(spv::OpFunctionCall, TypeScalarU64(state), physical, state.bdaWritePointerFunction, address, ConstantU32(state, bytes), instruction); + return EmitValueOrDefaultIfCondition(state, Binary(state, spv::OpINotEqual, TypeBool(state), physical, BdaConstant(state, 0u)), type, zero, [&] { + const auto pointer = state.module.AllocateId(); + state.module.AddFunction(spv::OpConvertUToPtr, TypePointer(state, spv::StorageClassPhysicalStorageBuffer, type), pointer, physical); + const auto old = operation(pointer); + state.module.AddFunction(spv::OpFunctionCall, state.module.Type(spv::OpTypeVoid), state.module.AllocateId(), state.bdaNoteWriteFunction, address); + return old; + }); + }); +} + // One probe of the span from the first to the last extracted dword replaces 4 lookups per dword (a // Bink DC pass spent ~450 dependent table levels per iteration on them). The span is loaded // dword-wise from the returned device address when it lies in one range and that address is diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp index 28f7bd86b..26570c011 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp @@ -467,6 +467,30 @@ void EmitDirectInstruction(SpirvValueEmitContext& ctx, const IrValue& inst) { case IrOpcode::StoreAddressU8: return Invoke(EmitStoreAddressU8, ctx, inst); case IrOpcode::StoreAddressU16: return Invoke(EmitStoreAddressU16, ctx, inst); case IrOpcode::StoreAddressU32: return Invoke(EmitStoreAddressU32, ctx, inst); + case IrOpcode::AddressAtomicSwap32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicCmpSwap32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicIAdd32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicISub32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicSMin32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicUMin32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicSMax32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicUMax32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicAnd32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicOr32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicXor32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicInc32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicDec32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicSwap64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicCmpSwap64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicIAdd64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicISub64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicSMin64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicUMin64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicSMax64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicUMax64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicAnd64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicOr64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicXor64: return Invoke(EmitAddressAtomic, ctx, inst); case IrOpcode::LoadBufferU8: return Invoke(EmitLoadBufferU8, ctx, inst); case IrOpcode::LoadBufferU16: return Invoke(EmitLoadBufferU16, ctx, inst); case IrOpcode::LoadBufferU32: return Invoke(EmitLoadBufferU32, ctx, inst); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp index fdb90178e..55e17c84d 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp @@ -1055,6 +1055,82 @@ std::uint32_t SharedFloatMinMax(SpirvValueEmitContext& ctx, const IrValue& inst, }); } +std::uint32_t AddressAtomicOpcode(IrOpcode opcode) { + switch (opcode) { + case IrOpcode::AddressAtomicSwap32: + case IrOpcode::AddressAtomicSwap64: + return spv::OpAtomicExchange; + case IrOpcode::AddressAtomicCmpSwap32: + case IrOpcode::AddressAtomicCmpSwap64: + return spv::OpAtomicCompareExchange; + case IrOpcode::AddressAtomicIAdd32: + case IrOpcode::AddressAtomicIAdd64: + return spv::OpAtomicIAdd; + case IrOpcode::AddressAtomicISub32: + case IrOpcode::AddressAtomicISub64: + return spv::OpAtomicISub; + case IrOpcode::AddressAtomicSMin32: + case IrOpcode::AddressAtomicSMin64: + return spv::OpAtomicSMin; + case IrOpcode::AddressAtomicUMin32: + case IrOpcode::AddressAtomicUMin64: + return spv::OpAtomicUMin; + case IrOpcode::AddressAtomicSMax32: + case IrOpcode::AddressAtomicSMax64: + return spv::OpAtomicSMax; + case IrOpcode::AddressAtomicUMax32: + case IrOpcode::AddressAtomicUMax64: + return spv::OpAtomicUMax; + case IrOpcode::AddressAtomicAnd32: + case IrOpcode::AddressAtomicAnd64: + return spv::OpAtomicAnd; + case IrOpcode::AddressAtomicOr32: + case IrOpcode::AddressAtomicOr64: + return spv::OpAtomicOr; + case IrOpcode::AddressAtomicXor32: + case IrOpcode::AddressAtomicXor64: + return spv::OpAtomicXor; + default: + throw std::runtime_error("AddressAtomicOpcode: opcode has no SPIR-V atomic instruction"); + } +} + +std::uint32_t AddressAtomic(SpirvValueEmitContext& ctx, const IrValue& inst) { + auto& state = ctx.state; + const auto& mem = ctx.Memory(inst); + if (mem.kind != ResourceKind::Flat && mem.kind != ResourceKind::Global) { + ctx.Fail(inst, "must access a physical address resource"); + } + const bool wide = inst.Type() == IrType::U64; + const auto type = wide ? TypeU64(state) : TypeU32(state); + const auto zero = wide ? ConstantU64(state, 0u) : ConstantU32(state, 0u); + return EmitValueOrDefaultIfCondition(state, ActiveArgument(ctx, inst), type, zero, [&]() { + const auto scalarType = wide ? TypeScalarU64(state) : TypeU32(state); + const auto scalar = [&](std::uint32_t value) { return wide ? Unary(state, spv::OpBitcast, scalarType, value) : value; }; + const auto value = scalar(ctx.Arg(inst, 3)); + const auto old = EmitBdaAtomic(ctx, inst, GuestAddress(ctx, inst, mem), wide ? 8u : 4u, [&](std::uint32_t pointer) { + if (inst.Opcode() == IrOpcode::AddressAtomicInc32 || inst.Opcode() == IrOpcode::AddressAtomicDec32) { + const bool increment = inst.Opcode() == IrOpcode::AddressAtomicInc32; + return AtomicUpdate(state, pointer, mem.kind, [&](std::uint32_t current) { + return increment ? AtomicIncrement(state, current, value) : AtomicDecrement(state, current, value); + }); + } + const auto scope = ConstantU32(state, spv::ScopeDevice); + const auto semantics = ConstantU32(state, spv::MemorySemanticsMaskNone); + const auto opcode = AddressAtomicOpcode(inst.Opcode()); + const auto result = state.module.AllocateId(); + if (opcode == spv::OpAtomicCompareExchange) { + state.module.AddFunction(opcode, scalarType, result, pointer, scope, semantics, semantics, value, scalar(ctx.Arg(inst, 4))); + } else { + state.module.AddFunction(opcode, scalarType, result, pointer, scope, semantics, value); + } + EmitDeviceAtomicMemoryBarrier(state); + return result; + }); + return wide ? Unary(state, spv::OpBitcast, type, old) : old; + }); +} + std::uint32_t AppendConsume(SpirvValueEmitContext& ctx, const IrValue& inst, bool append) { auto& state = ctx.state; if (ctx.half == 1u) { @@ -1323,6 +1399,10 @@ void EmitStoreAddressU32(SpirvValueEmitContext& ctx, const IrValue& inst) { StoreAddress(ctx, inst, 32u); } +std::uint32_t EmitAddressAtomic(SpirvValueEmitContext& ctx, const IrValue& inst) { + return AddressAtomic(ctx, inst); +} + void EmitLoadBufferU8(SpirvValueEmitContext& ctx, const IrValue& inst) { ctx.Define(inst, LoadSubword(ctx, inst, BufferMemory(ctx, inst), 8u)); } diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index e6abfdcce..579a99074 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -93,6 +93,7 @@ private: bool dsAtomic(const RdnaInstruction& inst, IrOpcode opcode, bool returnsValue); bool flatLoad(const RdnaInstruction& inst); bool flatStore(const RdnaInstruction& inst); + bool flatAtomic(const RdnaInstruction& inst, IrOpcode opcode); bool imageGetResinfo(const RdnaInstruction& inst); bool imageGetLod(const RdnaInstruction& inst); bool imageLoad(const RdnaInstruction& inst); diff --git a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp index b02afbaa4..08768d213 100644 --- a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp +++ b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp @@ -153,6 +153,10 @@ void includeInstructionVectorRegisters(const RdnaInstruction& instruction, std:: includeVector(instruction.source1); includeVector(instruction.source2); includeVector(instruction.source3); + if (instruction.family == RdnaInstructionFamily::FLAT) { + const bool compare = instruction.op == RdnaOpcode::FlatAtomicCmpswap || instruction.op == RdnaOpcode::FlatAtomicCmpswapX2; + includeVector(instruction.source2, std::max(instruction.dataDwordCount, 1u) * (compare ? 2u : 1u)); + } if (instruction.family == RdnaInstructionFamily::DS) { switch (instruction.op) { case RdnaOpcode::DsWriteB64: diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/FlatInstructions.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/FlatInstructions.cpp index 13de61432..55754aac4 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/FlatInstructions.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/FlatInstructions.cpp @@ -141,4 +141,34 @@ bool TranslationContext::flatStore(const RdnaInstruction& inst) { return true; } +bool TranslationContext::flatAtomic(const RdnaInstruction& inst, IrOpcode opcode) { + const MemoryInfo memory = flatMemoryInfoFromInstruction(inst); + const AddressOperands address = readAddressOperands(inst, 0u); + const MemoryFlags flags = addMemoryInfo(memory, inst.programCounter); + IrValue& active = ir.GetExec(); + const bool compare = opcode == IrOpcode::AddressAtomicCmpSwap32 || opcode == IrOpcode::AddressAtomicCmpSwap64; + IrValue* result; + if (IrOpcodeType(opcode) == IrType::U64) { + const IrU64 value = readU64(inst.source2); + if (compare) { + const IrU64 comparator = readU64(offsetOperand(inst.source2, 2u)); + result = &ir.Emit(opcode, IrOpcodeType(opcode), {address.resource, address.low, address.high, &value.Value(), &comparator.Value(), &active}, flags); + } else { + result = &ir.Emit(opcode, IrOpcodeType(opcode), {address.resource, address.low, address.high, &value.Value(), &active}, flags); + } + } else { + const IrU32 value = readU32(inst.source2); + if (compare) { + const IrU32 comparator = readU32(offsetOperand(inst.source2, 1u)); + result = &ir.Emit(opcode, IrOpcodeType(opcode), {address.resource, address.low, address.high, &value.Value(), &comparator.Value(), &active}, flags); + } else { + result = &ir.Emit(opcode, IrOpcodeType(opcode), {address.resource, address.low, address.high, &value.Value(), &active}, flags); + } + } + if (inst.glc) { + writeOperand(inst.destination, result); + } + return true; +} + } diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp index 947df3fa4..fbaa93398 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp @@ -207,6 +207,54 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) { case RdnaOpcode::FlatStoreDwordx3: case RdnaOpcode::FlatStoreDwordx4: return flatStore(inst); + case RdnaOpcode::FlatAtomicSwap: + return flatAtomic(inst, IrOpcode::AddressAtomicSwap32); + case RdnaOpcode::FlatAtomicCmpswap: + return flatAtomic(inst, IrOpcode::AddressAtomicCmpSwap32); + case RdnaOpcode::FlatAtomicAdd: + return flatAtomic(inst, IrOpcode::AddressAtomicIAdd32); + case RdnaOpcode::FlatAtomicSub: + return flatAtomic(inst, IrOpcode::AddressAtomicISub32); + case RdnaOpcode::FlatAtomicSmin: + return flatAtomic(inst, IrOpcode::AddressAtomicSMin32); + case RdnaOpcode::FlatAtomicUmin: + return flatAtomic(inst, IrOpcode::AddressAtomicUMin32); + case RdnaOpcode::FlatAtomicSmax: + return flatAtomic(inst, IrOpcode::AddressAtomicSMax32); + case RdnaOpcode::FlatAtomicUmax: + return flatAtomic(inst, IrOpcode::AddressAtomicUMax32); + case RdnaOpcode::FlatAtomicAnd: + return flatAtomic(inst, IrOpcode::AddressAtomicAnd32); + case RdnaOpcode::FlatAtomicOr: + return flatAtomic(inst, IrOpcode::AddressAtomicOr32); + case RdnaOpcode::FlatAtomicXor: + return flatAtomic(inst, IrOpcode::AddressAtomicXor32); + case RdnaOpcode::FlatAtomicInc: + return flatAtomic(inst, IrOpcode::AddressAtomicInc32); + case RdnaOpcode::FlatAtomicDec: + return flatAtomic(inst, IrOpcode::AddressAtomicDec32); + case RdnaOpcode::FlatAtomicSwapX2: + return flatAtomic(inst, IrOpcode::AddressAtomicSwap64); + case RdnaOpcode::FlatAtomicCmpswapX2: + return flatAtomic(inst, IrOpcode::AddressAtomicCmpSwap64); + case RdnaOpcode::FlatAtomicAddX2: + return flatAtomic(inst, IrOpcode::AddressAtomicIAdd64); + case RdnaOpcode::FlatAtomicSubX2: + return flatAtomic(inst, IrOpcode::AddressAtomicISub64); + case RdnaOpcode::FlatAtomicSminX2: + return flatAtomic(inst, IrOpcode::AddressAtomicSMin64); + case RdnaOpcode::FlatAtomicUminX2: + return flatAtomic(inst, IrOpcode::AddressAtomicUMin64); + case RdnaOpcode::FlatAtomicSmaxX2: + return flatAtomic(inst, IrOpcode::AddressAtomicSMax64); + case RdnaOpcode::FlatAtomicUmaxX2: + return flatAtomic(inst, IrOpcode::AddressAtomicUMax64); + case RdnaOpcode::FlatAtomicAndX2: + return flatAtomic(inst, IrOpcode::AddressAtomicAnd64); + case RdnaOpcode::FlatAtomicOrX2: + return flatAtomic(inst, IrOpcode::AddressAtomicOr64); + case RdnaOpcode::FlatAtomicXorX2: + return flatAtomic(inst, IrOpcode::AddressAtomicXor64); case RdnaOpcode::DsAddU32: return dsAtomic(inst, IrOpcode::SharedAtomicIAdd32, false); From 183a0f09b4bfa698b89c8dd073ed3dbb51f50522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 11:13:30 +0200 Subject: [PATCH 080/164] feat(shader): accept the glc, slc and dlc cache bits on flat, global and scratch Loads and stores with glc or dlc are coherent, as MUBUF already does; slc is a cache hint and changes no result. Atomics keep glc as the return bit. lds, segment 3 and scratch atomics still throw. Co-Authored-By: Claude --- .../tests/execution/FlatCacheBits.cpp | 188 ++++++++++++++++++ .../RdnaDecoder/src/RdnaMemoryOpDecoder.cpp | 5 +- .../MemoryInstructions/FlatInstructions.cpp | 6 +- 3 files changed, 195 insertions(+), 4 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/FlatCacheBits.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatCacheBits.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatCacheBits.cpp new file mode 100644 index 000000000..9b1650f3f --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatCacheBits.cpp @@ -0,0 +1,188 @@ +#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 + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint8_t Fill = 0xcd; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array FlatCacheBitsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0xd70f6a1e, 0x02020208, 0x7e3e0209, + 0x503e3e80, 0xdc318000, 0x0a080001, 0xdc328004, 0x0b080001, 0xdc309008, 0x0c080001, 0xdc33900c, + 0x0d080001, 0xdc359004, 0x0e080001, 0xdc228001, 0x10080001, 0xdc259007, 0x11080001, 0xbf8c3f70, + 0xdc739000, 0x00080501, 0xdc628006, 0x00080601, 0xbf8c3f70, 0xdc359000, 0x12080001, 0xdccb9008, + 0x14080701, 0xdcce900c, 0x00080401, 0xbf8c3f70, 0xdc358008, 0x15080001, 0xdc311000, 0x177d001e, + 0xdc730000, 0x007d071e, 0xbf8c0070, 0xdcef1000, 0x187d061e, 0xbf8c0070, 0xdc320000, 0x197d001e, + 0xbf8c0070, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, 0x80011202, 0xe0781030, + 0x80011602, 0xbf810000 +}; + +struct Row { + std::array memory; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x0000007f, 0x00000080, 0x7fffffff, 0x0000007f, 0x00000080, 0x00000000, 0x00000000, 0x0000007f, 0x0080007f, 0x00000080, 0x8000007f, 0x7ffffffe, 0x0000007f, 0x7fffffff, 0x7fffff7f}}, + {{0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01, 0x000000ff, 0x80000000, 0x00000000, 0x00000000, 0x000000ff, 0x000000ff, 0x80000000, 0x00ff7f01, 0x80ff7e82, 0x000000ff, 0x80ff7f01, 0x00ff7f01}}, + {{0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff, 0x80000000, 0xdeadbeef, 0x00000000, 0xffffff80, 0x80000000, 0x80ef0000, 0xdeadbeef, 0xdeadbfee, 0x0000007f, 0x80000000, 0x000000ff, 0xdeadbe10}}, + {{0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef, 0x12345678, 0x00000001, 0x00000000, 0x00000012, 0x12345678, 0x12015678, 0x00000001, 0xdeadbef0, 0xdeadbdf0, 0x12345678, 0xdeadbeef, 0xdeadbeee}}, + {{0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080, 0x80ff7f01, 0x000000ff, 0x000000ff, 0xffffff80, 0x80ff7f01, 0x80ff7f01, 0x000000ff, 0x0000017f, 0x80000081, 0x80ff7f01, 0x00000080, 0x0000007f}}, + {{0x80000000, 0x00000001, 0xffffffff, 0x12345678}, {0x80000000, 0x00000001, 0xffffffff, 0x12345678, 0x00000001, 0xffffffff, 0x00000000, 0x00000000, 0x00000001, 0x00ff0001, 0xffffffff, 0x12345677, 0x92345678, 0x00000001, 0x12345678, 0xedcba987}}, + {{0xffffffff, 0x00000080, 0x80ff7f01, 0x0000007f}, {0xffffffff, 0x00000080, 0x80ff7f01, 0x0000007f, 0x00000080, 0x80ff7f01, 0x000000ff, 0x00000000, 0x00000080, 0x00010080, 0x80ff7f01, 0x80ff7f80, 0x00000080, 0x00000080, 0x0000007f, 0x80ff7f7e}}, + {{0x12345678, 0x7fffffff, 0x0000007f, 0xffffffff}, {0x12345678, 0x7fffffff, 0x0000007f, 0xffffffff, 0x7fffffff, 0x0000007f, 0x00000056, 0x0000007f, 0x7fffffff, 0x7f7fffff, 0x0000007f, 0x0000007e, 0xedcba987, 0x7fffffff, 0xffffffff, 0xffffff80}}, + {{0xdeadbeef, 0xffffffff, 0x7fffffff, 0x00000001}, {0xdeadbeef, 0xffffffff, 0x7fffffff, 0x00000001, 0xffffffff, 0x7fffffff, 0x000000be, 0xffffffff, 0xffffffff, 0xffffffff, 0x7fffffff, 0x80000000, 0x21524112, 0xffffffff, 0x00000001, 0x7ffffffe}}, + {{0x80ff7f01, 0xdeadbeef, 0x12345678, 0x80000000}, {0x80ff7f01, 0xdeadbeef, 0x12345678, 0x80000000, 0xdeadbeef, 0x12345678, 0x0000007f, 0xffffffde, 0xdeadbeef, 0xde78beef, 0x12345678, 0x92345678, 0xff0080ff, 0xdeadbeef, 0x80000000, 0x92345678}}, + {{0x00000000, 0x00000000, 0x00000000, 0x00000000}, {0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}}, + {{0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x0000007f, 0x00000080, 0x7fffffff, 0x0000007f, 0x00000080, 0x00000000, 0x00000000, 0x0000007f, 0x0080007f, 0x00000080, 0x8000007f, 0x7ffffffe, 0x0000007f, 0x7fffffff, 0x7fffff7f}}, + {{0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01, 0x000000ff, 0x80000000, 0x00000000, 0x00000000, 0x000000ff, 0x000000ff, 0x80000000, 0x00ff7f01, 0x80ff7e82, 0x000000ff, 0x80ff7f01, 0x00ff7f01}}, + {{0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff, 0x80000000, 0xdeadbeef, 0x00000000, 0xffffff80, 0x80000000, 0x80ef0000, 0xdeadbeef, 0xdeadbfee, 0x0000007f, 0x80000000, 0x000000ff, 0xdeadbe10}}, + {{0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef, 0x12345678, 0x00000001, 0x00000000, 0x00000012, 0x12345678, 0x12015678, 0x00000001, 0xdeadbef0, 0xdeadbdf0, 0x12345678, 0xdeadbeef, 0xdeadbeee}}, + {{0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080, 0x80ff7f01, 0x000000ff, 0x000000ff, 0xffffff80, 0x80ff7f01, 0x80ff7f01, 0x000000ff, 0x0000017f, 0x80000081, 0x80ff7f01, 0x00000080, 0x0000007f}}, + {{0x2265b1f5, 0x91b7584a, 0xd8f16adf, 0xcd613e30}, {0x2265b1f5, 0x91b7584a, 0xd8f16adf, 0xcd613e30, 0x91b7584a, 0xd8f16adf, 0x000000b1, 0xffffff91, 0x91b7584a, 0x91df584a, 0xd8f16adf, 0xa652a90f, 0xaafb8c3b, 0x91b7584a, 0xcd613e30, 0x159054ef}}, + {{0xc386bbc4, 0x1027c4d1, 0x414c343c, 0x1e2feb89}, {0xc386bbc4, 0x1027c4d1, 0x414c343c, 0x1e2feb89, 0x1027c4d1, 0x414c343c, 0x000000bb, 0x00000010, 0x1027c4d1, 0x103cc4d1, 0x414c343c, 0x5f7c1fc5, 0x5aa92fc5, 0x1027c4d1, 0x1e2feb89, 0x5f63dfb5}}, + {{0x7ed4d57b, 0xc2ce6f44, 0x7311d8a3, 0x78e51061}, {0x7ed4d57b, 0xc2ce6f44, 0x7311d8a3, 0x78e51061, 0xc2ce6f44, 0x7311d8a3, 0x000000d5, 0xffffffc2, 0xc2ce6f44, 0xc2a36f44, 0x7311d8a3, 0xebf6e904, 0xfa103ae6, 0xc2ce6f44, 0x78e51061, 0x0bf4c8c2}}, + {{0xa6cecc1b, 0x612e7696, 0xc9e9c616, 0x35bf992d}, {0xa6cecc1b, 0x612e7696, 0xc9e9c616, 0x35bf992d, 0x612e7696, 0xc9e9c616, 0x000000cc, 0x00000061, 0x612e7696, 0x61167696, 0xc9e9c616, 0xffa95f43, 0x8ef0cd12, 0x612e7696, 0x35bf992d, 0xfc565f3b}}, + {{0x18072e8c, 0x7ce42c82, 0x0741c7a8, 0xe4b06ce6}, {0x18072e8c, 0x7ce42c82, 0x0741c7a8, 0xe4b06ce6, 0x7ce42c82, 0x0741c7a8, 0x0000002e, 0x0000007c, 0x7ce42c82, 0x7ca82c82, 0x0741c7a8, 0xebf2348e, 0xcca93e5a, 0x7ce42c82, 0xe4b06ce6, 0xe3f1ab4e}}, + {{0xd5f4b3b2, 0x63ca828d, 0x6ec9d286, 0x9b810e76}, {0xd5f4b3b2, 0x63ca828d, 0x6ec9d286, 0x9b810e76, 0x63ca828d, 0x6ec9d286, 0x000000b3, 0x00000063, 0x63ca828d, 0x6386828d, 0x6ec9d286, 0x0a4ae0fc, 0xc58c5ac4, 0x63ca828d, 0x9b810e76, 0xf548dcf0}}, + {{0xc324c985, 0xc4647159, 0x008a05a6, 0xb2221a58}, {0xc324c985, 0xc4647159, 0x008a05a6, 0xb2221a58, 0xc4647159, 0x008a05a6, 0x000000c9, 0xffffffc4, 0xc4647159, 0xc4a67159, 0x008a05a6, 0xb2ac1ffe, 0xeefd50d3, 0xc4647159, 0xb2221a58, 0xb2a81ffe}}, + {{0x7204e52d, 0x442e3d43, 0xb8b6d8fe, 0xcd447e35}, {0x7204e52d, 0x442e3d43, 0xb8b6d8fe, 0xcd447e35, 0x442e3d43, 0xb8b6d8fe, 0x000000e5, 0x00000044, 0x442e3d43, 0x44fe3d43, 0xb8b6d8fe, 0x85fb5733, 0x5b3f9908, 0x442e3d43, 0xcd447e35, 0x75f2a6cb}}, + {{0x3a902931, 0x9755d4c1, 0xf1fd42a2, 0x1a2b8f1f}, {0x3a902931, 0x9755d4c1, 0xf1fd42a2, 0x1a2b8f1f, 0x9755d4c1, 0xf1fd42a2, 0x00000029, 0xffffff97, 0x9755d4c1, 0x97a2d4c1, 0xf1fd42a2, 0x0c28d1c1, 0xdf9b65ee, 0x9755d4c1, 0x1a2b8f1f, 0xebd6cdbd}}, + {{0xe6c3f339, 0x51431193, 0x07d4bedc, 0x05b6e6e3}, {0xe6c3f339, 0x51431193, 0x07d4bedc, 0x05b6e6e3, 0x51431193, 0x07d4bedc, 0x000000f3, 0x00000051, 0x51431193, 0x51dc1193, 0x07d4bedc, 0x0d8ba5bf, 0x1ef2f3aa, 0x51431193, 0x05b6e6e3, 0x0262583f}}, + {{0x06839eb9, 0xa648a7dd, 0x8a9a021e, 0x025b413f}, {0x06839eb9, 0xa648a7dd, 0x8a9a021e, 0x025b413f, 0xa648a7dd, 0x8a9a021e, 0x0000009e, 0xffffffa6, 0xa648a7dd, 0xa61ea7dd, 0x8a9a021e, 0x8cf5435d, 0xfbd7a286, 0xa648a7dd, 0x025b413f, 0x88c14321}}, + {{0xf06c144a, 0xe1988ad9, 0x619699cf, 0xafbd67f9}, {0xf06c144a, 0xe1988ad9, 0x619699cf, 0xafbd67f9, 0xe1988ad9, 0x619699cf, 0x00000014, 0xffffffe1, 0xe1988ad9, 0xe1cf8ad9, 0x619699cf, 0x115401c8, 0xbf5153af, 0xe1988ad9, 0xafbd67f9, 0xce2bfe36}}, + {{0x37730edf, 0xf8130c42, 0x6c0fd4f5, 0xb9d179e0}, {0x37730edf, 0xf8130c42, 0x6c0fd4f5, 0xb9d179e0, 0xf8130c42, 0x6c0fd4f5, 0x0000000e, 0xfffffff8, 0xf8130c42, 0xf8f50c42, 0x6c0fd4f5, 0x25e14ed5, 0x825e6b01, 0xf8130c42, 0xb9d179e0, 0xd5dead15}}, + {{0x076f3787, 0x8712b8bc, 0x38c0c8fd, 0xc381e88f}, {0x076f3787, 0x8712b8bc, 0x38c0c8fd, 0xc381e88f, 0x8712b8bc, 0x38c0c8fd, 0x00000037, 0xffffff87, 0x8712b8bc, 0x87fdb8bc, 0x38c0c8fd, 0xfc42b18c, 0xbc12b108, 0x8712b8bc, 0xc381e88f, 0xfb412072}}, + {{0x701966a0, 0xf06d3fef, 0x7eed8d14, 0x8d88348a}, {0x701966a0, 0xf06d3fef, 0x7eed8d14, 0x8d88348a, 0xf06d3fef, 0x7eed8d14, 0x00000066, 0xfffffff0, 0xf06d3fef, 0xf0143fef, 0x7eed8d14, 0x0c75c19e, 0x1d6ecdea, 0xf06d3fef, 0x8d88348a, 0xf365b99e}}, + {{0x3bab6c39, 0x587fd280, 0x3b1a11df, 0xad45f23d}, {0x3bab6c39, 0x587fd280, 0x3b1a11df, 0xad45f23d, 0x587fd280, 0x3b1a11df, 0x0000006c, 0x00000058, 0x587fd280, 0x58dfd280, 0x3b1a11df, 0xe860041c, 0x719a8604, 0x587fd280, 0xad45f23d, 0x965fe3e2}} +}}; + +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, "flat cache bits: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, true); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +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%x", value); + return text; +} + +std::array FinalMemory(const Row& row) { + const auto [a, b, c, d] = row.memory; + return {d ^ c, (b & ~0xff0000u) | ((c & 0xffu) << 16u), c + d, d - a}; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::span code(FlatCacheBitsCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 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, memory}, + device.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(); +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize) { + std::memset(guest.Data(), Fill, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid % Rows.size()].memory.data(), 16u); + } + Output.fill(Sentinel); + Dispatch(device, waveSize, guest.Data()); + const auto wave = "flat cache bits: wave" + std::to_string(waveSize) + " lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = Rows[tid % Rows.size()]; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == row.results[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(row.results[result])); + } + std::array memory{}; + std::memcpy(memory.data(), guest.Data() + tid * 16u, 16u); + const auto expected = FinalMemory(row); + for (std::uint32_t dword = 0; dword < 4u; ++dword) { + Require(memory[dword] == expected[dword], wave + std::to_string(tid) + " memory dword " + std::to_string(dword) + " is " + Hex(memory[dword]) + ", expected " + Hex(expected[dword])); + } + } + for (std::size_t offset = Threads * 16u; offset < BlockBytes; ++offset) { + Require(guest.Data()[offset] == Fill, wave + "store outside the rows changed byte " + std::to_string(offset)); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + Run(*device, guest, 32); + Run(*device, guest, 64); + std::puts("flat cache bits tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp index cb034b22f..508b441ab 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp @@ -786,7 +786,7 @@ RdnaInstruction DecodeRdnaFlat(std::uint32_t programCounter, std::span> 12u) & 1u; + const auto dlc = ((word0 >> 12u) & 1u) != 0u; const auto lds = (word0 >> 13u) & 1u; const auto seg = (word0 >> 14u) & 0x3u; const auto glc = ((word0 >> 16u) & 1u) != 0u; @@ -798,7 +798,7 @@ RdnaInstruction DecodeRdnaFlat(std::uint32_t programCounter, std::span Date: Sun, 4 Oct 2026 12:51:19 +0200 Subject: [PATCH 081/164] feat(shader): implement the float and 64-bit inc/dec flat and global atomics Co-Authored-By: Claude --- .../tests/execution/GlobalAtomicsFloat.cpp | 750 ++++++++++++++++++ .../IntermediateRepresentation/IrOpcode.hpp | 8 + .../src/IrOpcode.cpp | 16 + .../include/RdnaDecoder/RdnaOpcode.hpp | 8 + .../RdnaDecoder/src/RdnaMemoryOpDecoder.cpp | 16 + .../SpirvBackend/src/SpirvFlowEmitter.cpp | 8 + .../SpirvMemory/SpirvMemoryInstructions.cpp | 52 +- .../Translation/src/InstructionTranslator.cpp | 2 +- .../MemoryInstructions/FlatInstructions.cpp | 2 +- .../MemoryInstructionDispatch.cpp | 16 + 10 files changed, 859 insertions(+), 19 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsFloat.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsFloat.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsFloat.cpp new file mode 100644 index 000000000..cf7b06501 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsFloat.cpp @@ -0,0 +1,750 @@ +#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 + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t OpCount = 8; +constexpr std::uint32_t NarrowOps = 3; +constexpr std::uint32_t OpStride = 0x200; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint8_t Fill = 0xcd; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array GlobalAtomicsFloatCode{ + 0x34020083, 0x34040084, 0x340a0083, 0x7e2802ff, 0x05e471e1, 0x7e2a02ff, 0x05e471e1, 0x4a060280, + 0x4a080480, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0a80, 0xdcf98000, 0x14080a03, 0xbf8c0070, + 0xe0701000, 0x80011406, 0x4a0602ff, 0x00000200, 0x4a0804ff, 0x00000400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00000200, 0xd70f6a1e, 0x02020608, 0x7e3e0209, 0x503e3e80, 0xdcfd0000, + 0x147d0a1e, 0xbf8c0070, 0xe0701000, 0x80011406, 0x4a0602ff, 0x00000400, 0x4a0804ff, 0x00000800, + 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000400, 0xdd018000, 0x14080a03, 0xbf8c0070, + 0xe0701000, 0x80011406, 0x4a0602ff, 0x00000600, 0x4a0804ff, 0x00000c00, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00000600, 0xdd798000, 0x14080a03, 0xbf8c0070, 0xe0741000, 0x80011406, + 0x4a0602ff, 0x00000800, 0x4a0804ff, 0x00001000, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00000800, 0xdd7d8000, 0x14080a03, 0xbf8c0070, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000a00, + 0x4a0804ff, 0x00001400, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000a00, 0xd70f6a1e, + 0x02020608, 0x7e3e0209, 0x503e3e80, 0xdd810000, 0x147d0a1e, 0xbf8c0070, 0xe0741000, 0x80011406, + 0x4a0602ff, 0x00000c00, 0x4a0804ff, 0x00001800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00000c00, 0xdd718000, 0x14080a03, 0xbf8c0070, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000e00, + 0x4a0804ff, 0x00001c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000e00, 0xd70f6a1e, + 0x02020608, 0x7e3e0209, 0x503e3e80, 0xdd750000, 0x147d0a1e, 0xbf8c0070, 0xe0741000, 0x80011406, + 0xbf810000 +}; + +struct Row { + std::uint32_t op; + std::uint64_t memory; + std::uint64_t data; + std::uint64_t comparator; + std::uint64_t final; +}; + +constexpr std::array Rows{{ + {0, 0x0ull, 0x0ull, 0x0ull, 0x0ull}, + {0, 0x0ull, 0x80000000ull, 0x80000000ull, 0x80000000ull}, + {0, 0x0ull, 0x3f800000ull, 0xc0200000ull, 0x0ull}, + {0, 0x0ull, 0xbf800000ull, 0xbf800000ull, 0x0ull}, + {0, 0x0ull, 0x3fc00000ull, 0x0ull, 0x3fc00000ull}, + {0, 0x0ull, 0x7f800000ull, 0xffc12345ull, 0x0ull}, + {0, 0x0ull, 0xff800000ull, 0xff800000ull, 0x0ull}, + {0, 0x0ull, 0x7fa00000ull, 0x807fffffull, 0x0ull}, + {0, 0x0ull, 0xff800001ull, 0xff800001ull, 0x0ull}, + {0, 0x0ull, 0x800000ull, 0x800000ull, 0x0ull}, + {0, 0x80000000ull, 0x0ull, 0x80000000ull, 0x0ull}, + {0, 0x80000000ull, 0x80000000ull, 0x0ull, 0x80000000ull}, + {0, 0x80000000ull, 0x3f800000ull, 0x7f800000ull, 0x80000000ull}, + {0, 0x80000000ull, 0xbf800000ull, 0xbf800000ull, 0x80000000ull}, + {0, 0x80000000ull, 0xc0200000ull, 0x0ull, 0xc0200000ull}, + {0, 0x80000000ull, 0x7f800000ull, 0x7fa00000ull, 0x80000000ull}, + {0, 0x80000000ull, 0xff800000ull, 0xff800000ull, 0x80000000ull}, + {0, 0x80000000ull, 0x7fa00000ull, 0x7f7fffffull, 0x80000000ull}, + {0, 0x80000000ull, 0xff800001ull, 0xff800001ull, 0x80000000ull}, + {0, 0x80000000ull, 0x7f7fffffull, 0x3f800000ull, 0x80000000ull}, + {0, 0x80000000ull, 0x800000ull, 0x800000ull, 0x80000000ull}, + {0, 0x3f800000ull, 0x0ull, 0x3f800000ull, 0x0ull}, + {0, 0x3f800000ull, 0x80000000ull, 0xbf800000ull, 0x3f800000ull}, + {0, 0x3f800000ull, 0x3f800000ull, 0xff800000ull, 0x3f800000ull}, + {0, 0x3f800000ull, 0x7f800000ull, 0xff800001ull, 0x3f800000ull}, + {0, 0x3f800000ull, 0x7fa00000ull, 0x800000ull, 0x3f800000ull}, + {0, 0xbf800000ull, 0x0ull, 0xbf800000ull, 0x0ull}, + {0, 0xbf800000ull, 0x80000000ull, 0x3f800000ull, 0xbf800000ull}, + {0, 0xbf800000ull, 0x3f800000ull, 0x7fc00000ull, 0xbf800000ull}, + {0, 0xbf800000ull, 0xbf800000ull, 0xbf800000ull, 0xbf800000ull}, + {0, 0xbf800000ull, 0x7f800000ull, 0x1ull, 0xbf800000ull}, + {0, 0xbf800000ull, 0xff800000ull, 0xff800000ull, 0xbf800000ull}, + {0, 0xbf800000ull, 0x7fa00000ull, 0x0ull, 0xbf800000ull}, + {0, 0xbf800000ull, 0xff800001ull, 0xff800001ull, 0xbf800000ull}, + {0, 0xbf800000ull, 0x7f7fffffull, 0x3fc00000ull, 0xbf800000ull}, + {0, 0xbf800000ull, 0x800000ull, 0x800000ull, 0xbf800000ull}, + {0, 0x3fc00000ull, 0x0ull, 0x3fc00000ull, 0x0ull}, + {0, 0x3fc00000ull, 0x80000000ull, 0xbfc00000ull, 0x3fc00000ull}, + {0, 0x3fc00000ull, 0x3f800000ull, 0xffc12345ull, 0x3fc00000ull}, + {0, 0x3fc00000ull, 0xbf800000ull, 0xbf800000ull, 0x3fc00000ull}, + {0, 0x3fc00000ull, 0x3fc00000ull, 0x3fc00000ull, 0x3fc00000ull}, + {0, 0x3fc00000ull, 0x7f800000ull, 0x807fffffull, 0x3fc00000ull}, + {0, 0x3fc00000ull, 0xff800000ull, 0xff800000ull, 0x3fc00000ull}, + {0, 0x3fc00000ull, 0x7fa00000ull, 0x80000000ull, 0x3fc00000ull}, + {0, 0x3fc00000ull, 0xff800001ull, 0xff800001ull, 0x3fc00000ull}, + {0, 0x3fc00000ull, 0x7f7fffffull, 0xc0200000ull, 0x3fc00000ull}, + {0, 0x3fc00000ull, 0x800000ull, 0x800000ull, 0x3fc00000ull}, + {0, 0xc0200000ull, 0x0ull, 0xc0200000ull, 0x0ull}, + {0, 0xc0200000ull, 0x80000000ull, 0x40200000ull, 0xc0200000ull}, + {0, 0xc0200000ull, 0x3f800000ull, 0x7fa00000ull, 0xc0200000ull}, + {0, 0xc0200000ull, 0xbf800000ull, 0xbf800000ull, 0xc0200000ull}, + {0, 0xc0200000ull, 0x7f800000ull, 0x7f7fffffull, 0xc0200000ull}, + {0, 0xc0200000ull, 0xff800000ull, 0xff800000ull, 0xc0200000ull}, + {0, 0xc0200000ull, 0x7fa00000ull, 0x3f800000ull, 0xc0200000ull}, + {0, 0xc0200000ull, 0xff800001ull, 0xff800001ull, 0xc0200000ull}, + {0, 0xc0200000ull, 0x7f7fffffull, 0x7f800000ull, 0xc0200000ull}, + {0, 0xc0200000ull, 0x800000ull, 0x800000ull, 0xc0200000ull}, + {0, 0x7f800000ull, 0x0ull, 0x7f800000ull, 0x0ull}, + {0, 0x7f800000ull, 0x80000000ull, 0xff800000ull, 0x7f800000ull}, + {0, 0x7f800000ull, 0x3f800000ull, 0xff800001ull, 0x7f800000ull}, + {0, 0x7f800000ull, 0xbf800000ull, 0xbf800000ull, 0x7f800000ull}, + {0, 0x7f800000ull, 0x7f800000ull, 0x800000ull, 0x7f800000ull}, + {0, 0xff800000ull, 0x0ull, 0xff800000ull, 0x0ull}, + {0, 0xff800000ull, 0x80000000ull, 0x7f800000ull, 0xff800000ull}, + {1, 0x0ull, 0x0ull, 0x0ull, 0x0ull}, + {1, 0x0ull, 0x80000000ull, 0x80000000ull, 0x80000000ull}, + {1, 0x0ull, 0x3f800000ull, 0xc0200000ull, 0x0ull}, + {1, 0x0ull, 0xbf800000ull, 0xbf800000ull, 0xbf800000ull}, + {1, 0x0ull, 0x3fc00000ull, 0x0ull, 0x0ull}, + {1, 0x0ull, 0xc0200000ull, 0x80000000ull, 0xc0200000ull}, + {1, 0x0ull, 0x7f800000ull, 0xffc12345ull, 0x0ull}, + {1, 0x0ull, 0xff800000ull, 0xff800000ull, 0xff800000ull}, + {1, 0x0ull, 0x7fc00000ull, 0x0ull, 0x0ull}, + {1, 0x0ull, 0xffc12345ull, 0x80000000ull, 0x0ull}, + {1, 0x0ull, 0x7fa00000ull, 0x807fffffull, 0x7fe00000ull}, + {1, 0x0ull, 0xff800001ull, 0xff800001ull, 0xffc00001ull}, + {1, 0x0ull, 0x1ull, 0x0ull, 0x0ull}, + {1, 0x0ull, 0x807fffffull, 0x80000000ull, 0x807fffffull}, + {1, 0x0ull, 0x7f7fffffull, 0x80000000ull, 0x0ull}, + {1, 0x0ull, 0x800000ull, 0x800000ull, 0x0ull}, + {1, 0x80000000ull, 0x0ull, 0x80000000ull, 0x80000000ull}, + {1, 0x80000000ull, 0x80000000ull, 0x0ull, 0x80000000ull}, + {1, 0x80000000ull, 0x3f800000ull, 0x7f800000ull, 0x80000000ull}, + {1, 0x80000000ull, 0xbf800000ull, 0xbf800000ull, 0xbf800000ull}, + {1, 0x80000000ull, 0x3fc00000ull, 0x80000000ull, 0x80000000ull}, + {1, 0x80000000ull, 0xc0200000ull, 0x0ull, 0xc0200000ull}, + {1, 0x80000000ull, 0x7f800000ull, 0x7fa00000ull, 0x80000000ull}, + {1, 0x80000000ull, 0xff800000ull, 0xff800000ull, 0xff800000ull}, + {1, 0x80000000ull, 0x7fc00000ull, 0x80000000ull, 0x80000000ull}, + {1, 0x80000000ull, 0xffc12345ull, 0x0ull, 0x80000000ull}, + {1, 0x80000000ull, 0x7fa00000ull, 0x7f7fffffull, 0x7fe00000ull}, + {1, 0x80000000ull, 0xff800001ull, 0xff800001ull, 0xffc00001ull}, + {1, 0x80000000ull, 0x1ull, 0x80000000ull, 0x80000000ull}, + {1, 0x80000000ull, 0x807fffffull, 0x0ull, 0x807fffffull}, + {1, 0x80000000ull, 0x7f7fffffull, 0x3f800000ull, 0x80000000ull}, + {1, 0x80000000ull, 0x800000ull, 0x800000ull, 0x80000000ull}, + {1, 0x3f800000ull, 0x0ull, 0x3f800000ull, 0x0ull}, + {1, 0x3f800000ull, 0x80000000ull, 0xbf800000ull, 0x80000000ull}, + {1, 0x3f800000ull, 0x3f800000ull, 0xff800000ull, 0x3f800000ull}, + {1, 0x3f800000ull, 0xbf800000ull, 0xbf800000ull, 0xbf800000ull}, + {1, 0x3f800000ull, 0x3fc00000ull, 0x3f800000ull, 0x3f800000ull}, + {1, 0x3f800000ull, 0xc0200000ull, 0xbf800000ull, 0xc0200000ull}, + {1, 0x3f800000ull, 0x7f800000ull, 0xff800001ull, 0x3f800000ull}, + {1, 0x3f800000ull, 0xff800000ull, 0xff800000ull, 0xff800000ull}, + {1, 0x3f800000ull, 0x7fc00000ull, 0x3f800000ull, 0x3f800000ull}, + {1, 0x3f800000ull, 0xffc12345ull, 0xbf800000ull, 0x3f800000ull}, + {1, 0x3f800000ull, 0x7fa00000ull, 0x800000ull, 0x7fe00000ull}, + {1, 0x3f800000ull, 0xff800001ull, 0xff800001ull, 0xffc00001ull}, + {1, 0x3f800000ull, 0x1ull, 0x3f800000ull, 0x1ull}, + {1, 0x3f800000ull, 0x807fffffull, 0xbf800000ull, 0x807fffffull}, + {1, 0x3f800000ull, 0x7f7fffffull, 0xbf800000ull, 0x3f800000ull}, + {1, 0x3f800000ull, 0x800000ull, 0x800000ull, 0x800000ull}, + {1, 0xbf800000ull, 0x0ull, 0xbf800000ull, 0xbf800000ull}, + {1, 0xbf800000ull, 0x80000000ull, 0x3f800000ull, 0xbf800000ull}, + {1, 0xbf800000ull, 0x3f800000ull, 0x7fc00000ull, 0xbf800000ull}, + {1, 0xbf800000ull, 0xbf800000ull, 0xbf800000ull, 0xbf800000ull}, + {1, 0xbf800000ull, 0x3fc00000ull, 0xbf800000ull, 0xbf800000ull}, + {1, 0xbf800000ull, 0xc0200000ull, 0x3f800000ull, 0xc0200000ull}, + {1, 0xbf800000ull, 0x7f800000ull, 0x1ull, 0xbf800000ull}, + {1, 0xbf800000ull, 0xff800000ull, 0xff800000ull, 0xff800000ull}, + {1, 0xbf800000ull, 0x7fc00000ull, 0xbf800000ull, 0xbf800000ull}, + {1, 0xbf800000ull, 0xffc12345ull, 0x3f800000ull, 0xbf800000ull}, + {1, 0xbf800000ull, 0x7fa00000ull, 0x0ull, 0x7fe00000ull}, + {1, 0xbf800000ull, 0xff800001ull, 0xff800001ull, 0xffc00001ull}, + {1, 0xbf800000ull, 0x1ull, 0xbf800000ull, 0xbf800000ull}, + {1, 0xbf800000ull, 0x807fffffull, 0x3f800000ull, 0xbf800000ull}, + {1, 0xbf800000ull, 0x7f7fffffull, 0x3fc00000ull, 0xbf800000ull}, + {1, 0xbf800000ull, 0x800000ull, 0x800000ull, 0xbf800000ull}, + {2, 0x0ull, 0x0ull, 0x0ull, 0x0ull}, + {2, 0x0ull, 0x80000000ull, 0x80000000ull, 0x0ull}, + {2, 0x0ull, 0x3f800000ull, 0xc0200000ull, 0x3f800000ull}, + {2, 0x0ull, 0xbf800000ull, 0xbf800000ull, 0x0ull}, + {2, 0x0ull, 0x3fc00000ull, 0x0ull, 0x3fc00000ull}, + {2, 0x0ull, 0xc0200000ull, 0x80000000ull, 0x0ull}, + {2, 0x0ull, 0x7f800000ull, 0xffc12345ull, 0x7f800000ull}, + {2, 0x0ull, 0xff800000ull, 0xff800000ull, 0x0ull}, + {2, 0x0ull, 0x7fc00000ull, 0x0ull, 0x0ull}, + {2, 0x0ull, 0xffc12345ull, 0x80000000ull, 0x0ull}, + {2, 0x0ull, 0x7fa00000ull, 0x807fffffull, 0x7fe00000ull}, + {2, 0x0ull, 0xff800001ull, 0xff800001ull, 0xffc00001ull}, + {2, 0x0ull, 0x1ull, 0x0ull, 0x1ull}, + {2, 0x0ull, 0x807fffffull, 0x80000000ull, 0x0ull}, + {2, 0x0ull, 0x7f7fffffull, 0x80000000ull, 0x7f7fffffull}, + {2, 0x0ull, 0x800000ull, 0x800000ull, 0x800000ull}, + {2, 0x80000000ull, 0x0ull, 0x80000000ull, 0x0ull}, + {2, 0x80000000ull, 0x80000000ull, 0x0ull, 0x80000000ull}, + {2, 0x80000000ull, 0x3f800000ull, 0x7f800000ull, 0x3f800000ull}, + {2, 0x80000000ull, 0xbf800000ull, 0xbf800000ull, 0x80000000ull}, + {2, 0x80000000ull, 0x3fc00000ull, 0x80000000ull, 0x3fc00000ull}, + {2, 0x80000000ull, 0xc0200000ull, 0x0ull, 0x80000000ull}, + {2, 0x80000000ull, 0x7f800000ull, 0x7fa00000ull, 0x7f800000ull}, + {2, 0x80000000ull, 0xff800000ull, 0xff800000ull, 0x80000000ull}, + {2, 0x80000000ull, 0x7fc00000ull, 0x80000000ull, 0x80000000ull}, + {2, 0x80000000ull, 0xffc12345ull, 0x0ull, 0x80000000ull}, + {2, 0x80000000ull, 0x7fa00000ull, 0x7f7fffffull, 0x7fe00000ull}, + {2, 0x80000000ull, 0xff800001ull, 0xff800001ull, 0xffc00001ull}, + {2, 0x80000000ull, 0x1ull, 0x80000000ull, 0x1ull}, + {2, 0x80000000ull, 0x807fffffull, 0x0ull, 0x80000000ull}, + {2, 0x80000000ull, 0x7f7fffffull, 0x3f800000ull, 0x7f7fffffull}, + {2, 0x80000000ull, 0x800000ull, 0x800000ull, 0x800000ull}, + {2, 0x3f800000ull, 0x0ull, 0x3f800000ull, 0x3f800000ull}, + {2, 0x3f800000ull, 0x80000000ull, 0xbf800000ull, 0x3f800000ull}, + {2, 0x3f800000ull, 0x3f800000ull, 0xff800000ull, 0x3f800000ull}, + {2, 0x3f800000ull, 0xbf800000ull, 0xbf800000ull, 0x3f800000ull}, + {2, 0x3f800000ull, 0x3fc00000ull, 0x3f800000ull, 0x3fc00000ull}, + {2, 0x3f800000ull, 0xc0200000ull, 0xbf800000ull, 0x3f800000ull}, + {2, 0x3f800000ull, 0x7f800000ull, 0xff800001ull, 0x7f800000ull}, + {2, 0x3f800000ull, 0xff800000ull, 0xff800000ull, 0x3f800000ull}, + {2, 0x3f800000ull, 0x7fc00000ull, 0x3f800000ull, 0x3f800000ull}, + {2, 0x3f800000ull, 0xffc12345ull, 0xbf800000ull, 0x3f800000ull}, + {2, 0x3f800000ull, 0x7fa00000ull, 0x800000ull, 0x7fe00000ull}, + {2, 0x3f800000ull, 0xff800001ull, 0xff800001ull, 0xffc00001ull}, + {2, 0x3f800000ull, 0x1ull, 0x3f800000ull, 0x3f800000ull}, + {2, 0x3f800000ull, 0x807fffffull, 0xbf800000ull, 0x3f800000ull}, + {2, 0x3f800000ull, 0x7f7fffffull, 0xbf800000ull, 0x7f7fffffull}, + {2, 0x3f800000ull, 0x800000ull, 0x800000ull, 0x3f800000ull}, + {2, 0xbf800000ull, 0x0ull, 0xbf800000ull, 0x0ull}, + {2, 0xbf800000ull, 0x80000000ull, 0x3f800000ull, 0x80000000ull}, + {2, 0xbf800000ull, 0x3f800000ull, 0x7fc00000ull, 0x3f800000ull}, + {2, 0xbf800000ull, 0xbf800000ull, 0xbf800000ull, 0xbf800000ull}, + {2, 0xbf800000ull, 0x3fc00000ull, 0xbf800000ull, 0x3fc00000ull}, + {2, 0xbf800000ull, 0xc0200000ull, 0x3f800000ull, 0xbf800000ull}, + {2, 0xbf800000ull, 0x7f800000ull, 0x1ull, 0x7f800000ull}, + {2, 0xbf800000ull, 0xff800000ull, 0xff800000ull, 0xbf800000ull}, + {2, 0xbf800000ull, 0x7fc00000ull, 0xbf800000ull, 0xbf800000ull}, + {2, 0xbf800000ull, 0xffc12345ull, 0x3f800000ull, 0xbf800000ull}, + {2, 0xbf800000ull, 0x7fa00000ull, 0x0ull, 0x7fe00000ull}, + {2, 0xbf800000ull, 0xff800001ull, 0xff800001ull, 0xffc00001ull}, + {2, 0xbf800000ull, 0x1ull, 0xbf800000ull, 0x1ull}, + {2, 0xbf800000ull, 0x807fffffull, 0x3f800000ull, 0x807fffffull}, + {2, 0xbf800000ull, 0x7f7fffffull, 0x3fc00000ull, 0x7f7fffffull}, + {2, 0xbf800000ull, 0x800000ull, 0x800000ull, 0x800000ull}, + {3, 0x0ull, 0x0ull, 0x0ull, 0x0ull}, + {3, 0x0ull, 0x8000000000000000ull, 0x8000000000000000ull, 0x8000000000000000ull}, + {3, 0x0ull, 0x3ff0000000000000ull, 0xc004000000000000ull, 0x0ull}, + {3, 0x0ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0x0ull}, + {3, 0x0ull, 0x3ff8000000000000ull, 0x0ull, 0x3ff8000000000000ull}, + {3, 0x0ull, 0x7ff0000000000000ull, 0xfff8000012345678ull, 0x0ull}, + {3, 0x0ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0x0ull}, + {3, 0x0ull, 0x7ff4000000000000ull, 0x800fffffffffffffull, 0x0ull}, + {3, 0x0ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0x0ull}, + {3, 0x0ull, 0x10000000000000ull, 0x10000000000000ull, 0x0ull}, + {3, 0x8000000000000000ull, 0x0ull, 0x8000000000000000ull, 0x0ull}, + {3, 0x8000000000000000ull, 0x8000000000000000ull, 0x0ull, 0x8000000000000000ull}, + {3, 0x8000000000000000ull, 0x3ff0000000000000ull, 0x7ff0000000000000ull, 0x8000000000000000ull}, + {3, 0x8000000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0x8000000000000000ull}, + {3, 0x8000000000000000ull, 0xc004000000000000ull, 0x0ull, 0xc004000000000000ull}, + {3, 0x8000000000000000ull, 0x7ff0000000000000ull, 0x7ff4000000000000ull, 0x8000000000000000ull}, + {3, 0x8000000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0x8000000000000000ull}, + {3, 0x8000000000000000ull, 0x7ff4000000000000ull, 0x7fefffffffffffffull, 0x8000000000000000ull}, + {3, 0x8000000000000000ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0x8000000000000000ull}, + {3, 0x8000000000000000ull, 0x7fefffffffffffffull, 0x3ff0000000000000ull, 0x8000000000000000ull}, + {3, 0x8000000000000000ull, 0x10000000000000ull, 0x10000000000000ull, 0x8000000000000000ull}, + {3, 0x3ff0000000000000ull, 0x0ull, 0x3ff0000000000000ull, 0x0ull}, + {3, 0x3ff0000000000000ull, 0x8000000000000000ull, 0xbff0000000000000ull, 0x3ff0000000000000ull}, + {3, 0x3ff0000000000000ull, 0x3ff0000000000000ull, 0xfff0000000000000ull, 0x3ff0000000000000ull}, + {3, 0x3ff0000000000000ull, 0x7ff0000000000000ull, 0xfff0000000000001ull, 0x3ff0000000000000ull}, + {3, 0x3ff0000000000000ull, 0x7ff4000000000000ull, 0x10000000000000ull, 0x3ff0000000000000ull}, + {3, 0xbff0000000000000ull, 0x0ull, 0xbff0000000000000ull, 0x0ull}, + {3, 0xbff0000000000000ull, 0x8000000000000000ull, 0x3ff0000000000000ull, 0xbff0000000000000ull}, + {3, 0xbff0000000000000ull, 0x3ff0000000000000ull, 0x7ff8000000000000ull, 0xbff0000000000000ull}, + {3, 0xbff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull}, + {3, 0xbff0000000000000ull, 0x7ff0000000000000ull, 0x1ull, 0xbff0000000000000ull}, + {3, 0xbff0000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0xbff0000000000000ull}, + {3, 0xbff0000000000000ull, 0x7ff4000000000000ull, 0x0ull, 0xbff0000000000000ull}, + {3, 0xbff0000000000000ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0xbff0000000000000ull}, + {3, 0xbff0000000000000ull, 0x7fefffffffffffffull, 0x3ff8000000000000ull, 0xbff0000000000000ull}, + {3, 0xbff0000000000000ull, 0x10000000000000ull, 0x10000000000000ull, 0xbff0000000000000ull}, + {3, 0x3ff8000000000000ull, 0x0ull, 0x3ff8000000000000ull, 0x0ull}, + {3, 0x3ff8000000000000ull, 0x8000000000000000ull, 0xbff8000000000000ull, 0x3ff8000000000000ull}, + {3, 0x3ff8000000000000ull, 0x3ff0000000000000ull, 0xfff8000012345678ull, 0x3ff8000000000000ull}, + {3, 0x3ff8000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0x3ff8000000000000ull}, + {3, 0x3ff8000000000000ull, 0x3ff8000000000000ull, 0x3ff8000000000000ull, 0x3ff8000000000000ull}, + {3, 0x3ff8000000000000ull, 0x7ff0000000000000ull, 0x800fffffffffffffull, 0x3ff8000000000000ull}, + {3, 0x3ff8000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0x3ff8000000000000ull}, + {3, 0x3ff8000000000000ull, 0x7ff4000000000000ull, 0x8000000000000000ull, 0x3ff8000000000000ull}, + {3, 0x3ff8000000000000ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0x3ff8000000000000ull}, + {3, 0x3ff8000000000000ull, 0x7fefffffffffffffull, 0xc004000000000000ull, 0x3ff8000000000000ull}, + {3, 0x3ff8000000000000ull, 0x10000000000000ull, 0x10000000000000ull, 0x3ff8000000000000ull}, + {3, 0xc004000000000000ull, 0x0ull, 0xc004000000000000ull, 0x0ull}, + {3, 0xc004000000000000ull, 0x8000000000000000ull, 0x4004000000000000ull, 0xc004000000000000ull}, + {3, 0xc004000000000000ull, 0x3ff0000000000000ull, 0x7ff4000000000000ull, 0xc004000000000000ull}, + {3, 0xc004000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0xc004000000000000ull}, + {3, 0xc004000000000000ull, 0x7ff0000000000000ull, 0x7fefffffffffffffull, 0xc004000000000000ull}, + {3, 0xc004000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0xc004000000000000ull}, + {3, 0xc004000000000000ull, 0x7ff4000000000000ull, 0x3ff0000000000000ull, 0xc004000000000000ull}, + {3, 0xc004000000000000ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0xc004000000000000ull}, + {3, 0xc004000000000000ull, 0x7fefffffffffffffull, 0x7ff0000000000000ull, 0xc004000000000000ull}, + {3, 0xc004000000000000ull, 0x10000000000000ull, 0x10000000000000ull, 0xc004000000000000ull}, + {3, 0x7ff0000000000000ull, 0x0ull, 0x7ff0000000000000ull, 0x0ull}, + {3, 0x7ff0000000000000ull, 0x8000000000000000ull, 0xfff0000000000000ull, 0x7ff0000000000000ull}, + {3, 0x7ff0000000000000ull, 0x3ff0000000000000ull, 0xfff0000000000001ull, 0x7ff0000000000000ull}, + {3, 0x7ff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0x7ff0000000000000ull}, + {3, 0x7ff0000000000000ull, 0x7ff0000000000000ull, 0x10000000000000ull, 0x7ff0000000000000ull}, + {3, 0xfff0000000000000ull, 0x0ull, 0xfff0000000000000ull, 0x0ull}, + {3, 0xfff0000000000000ull, 0x8000000000000000ull, 0x7ff0000000000000ull, 0xfff0000000000000ull}, + {4, 0x0ull, 0x0ull, 0x0ull, 0x0ull}, + {4, 0x0ull, 0x8000000000000000ull, 0x8000000000000000ull, 0x8000000000000000ull}, + {4, 0x0ull, 0x3ff0000000000000ull, 0xc004000000000000ull, 0x0ull}, + {4, 0x0ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull}, + {4, 0x0ull, 0x3ff8000000000000ull, 0x0ull, 0x0ull}, + {4, 0x0ull, 0xc004000000000000ull, 0x8000000000000000ull, 0xc004000000000000ull}, + {4, 0x0ull, 0x7ff0000000000000ull, 0xfff8000012345678ull, 0x0ull}, + {4, 0x0ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull}, + {4, 0x0ull, 0x7ff8000000000000ull, 0x0ull, 0x0ull}, + {4, 0x0ull, 0xfff8000012345678ull, 0x8000000000000000ull, 0x0ull}, + {4, 0x0ull, 0x7ff4000000000000ull, 0x800fffffffffffffull, 0x7ffc000000000000ull}, + {4, 0x0ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0xfff8000000000001ull}, + {4, 0x0ull, 0x1ull, 0x0ull, 0x0ull}, + {4, 0x0ull, 0x800fffffffffffffull, 0x8000000000000000ull, 0x800fffffffffffffull}, + {4, 0x0ull, 0x7fefffffffffffffull, 0x8000000000000000ull, 0x0ull}, + {4, 0x0ull, 0x10000000000000ull, 0x10000000000000ull, 0x0ull}, + {4, 0x8000000000000000ull, 0x0ull, 0x8000000000000000ull, 0x8000000000000000ull}, + {4, 0x8000000000000000ull, 0x8000000000000000ull, 0x0ull, 0x8000000000000000ull}, + {4, 0x8000000000000000ull, 0x3ff0000000000000ull, 0x7ff0000000000000ull, 0x8000000000000000ull}, + {4, 0x8000000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull}, + {4, 0x8000000000000000ull, 0x3ff8000000000000ull, 0x8000000000000000ull, 0x8000000000000000ull}, + {4, 0x8000000000000000ull, 0xc004000000000000ull, 0x0ull, 0xc004000000000000ull}, + {4, 0x8000000000000000ull, 0x7ff0000000000000ull, 0x7ff4000000000000ull, 0x8000000000000000ull}, + {4, 0x8000000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull}, + {4, 0x8000000000000000ull, 0x7ff8000000000000ull, 0x8000000000000000ull, 0x8000000000000000ull}, + {4, 0x8000000000000000ull, 0xfff8000012345678ull, 0x0ull, 0x8000000000000000ull}, + {4, 0x8000000000000000ull, 0x7ff4000000000000ull, 0x7fefffffffffffffull, 0x7ffc000000000000ull}, + {4, 0x8000000000000000ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0xfff8000000000001ull}, + {4, 0x8000000000000000ull, 0x1ull, 0x8000000000000000ull, 0x8000000000000000ull}, + {4, 0x8000000000000000ull, 0x800fffffffffffffull, 0x0ull, 0x800fffffffffffffull}, + {4, 0x8000000000000000ull, 0x7fefffffffffffffull, 0x3ff0000000000000ull, 0x8000000000000000ull}, + {4, 0x8000000000000000ull, 0x10000000000000ull, 0x10000000000000ull, 0x8000000000000000ull}, + {4, 0x3ff0000000000000ull, 0x0ull, 0x3ff0000000000000ull, 0x0ull}, + {4, 0x3ff0000000000000ull, 0x8000000000000000ull, 0xbff0000000000000ull, 0x8000000000000000ull}, + {4, 0x3ff0000000000000ull, 0x3ff0000000000000ull, 0xfff0000000000000ull, 0x3ff0000000000000ull}, + {4, 0x3ff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull}, + {4, 0x3ff0000000000000ull, 0x3ff8000000000000ull, 0x3ff0000000000000ull, 0x3ff0000000000000ull}, + {4, 0x3ff0000000000000ull, 0xc004000000000000ull, 0xbff0000000000000ull, 0xc004000000000000ull}, + {4, 0x3ff0000000000000ull, 0x7ff0000000000000ull, 0xfff0000000000001ull, 0x3ff0000000000000ull}, + {4, 0x3ff0000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull}, + {4, 0x3ff0000000000000ull, 0x7ff8000000000000ull, 0x3ff0000000000000ull, 0x3ff0000000000000ull}, + {4, 0x3ff0000000000000ull, 0xfff8000012345678ull, 0xbff0000000000000ull, 0x3ff0000000000000ull}, + {4, 0x3ff0000000000000ull, 0x7ff4000000000000ull, 0x10000000000000ull, 0x7ffc000000000000ull}, + {4, 0x3ff0000000000000ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0xfff8000000000001ull}, + {4, 0x3ff0000000000000ull, 0x1ull, 0x3ff0000000000000ull, 0x1ull}, + {4, 0x3ff0000000000000ull, 0x800fffffffffffffull, 0xbff0000000000000ull, 0x800fffffffffffffull}, + {4, 0x3ff0000000000000ull, 0x7fefffffffffffffull, 0xbff0000000000000ull, 0x3ff0000000000000ull}, + {4, 0x3ff0000000000000ull, 0x10000000000000ull, 0x10000000000000ull, 0x10000000000000ull}, + {4, 0xbff0000000000000ull, 0x0ull, 0xbff0000000000000ull, 0xbff0000000000000ull}, + {4, 0xbff0000000000000ull, 0x8000000000000000ull, 0x3ff0000000000000ull, 0xbff0000000000000ull}, + {4, 0xbff0000000000000ull, 0x3ff0000000000000ull, 0x7ff8000000000000ull, 0xbff0000000000000ull}, + {4, 0xbff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull}, + {4, 0xbff0000000000000ull, 0x3ff8000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull}, + {4, 0xbff0000000000000ull, 0xc004000000000000ull, 0x3ff0000000000000ull, 0xc004000000000000ull}, + {4, 0xbff0000000000000ull, 0x7ff0000000000000ull, 0x1ull, 0xbff0000000000000ull}, + {4, 0xbff0000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull}, + {4, 0xbff0000000000000ull, 0x7ff8000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull}, + {4, 0xbff0000000000000ull, 0xfff8000012345678ull, 0x3ff0000000000000ull, 0xbff0000000000000ull}, + {4, 0xbff0000000000000ull, 0x7ff4000000000000ull, 0x0ull, 0x7ffc000000000000ull}, + {4, 0xbff0000000000000ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0xfff8000000000001ull}, + {4, 0xbff0000000000000ull, 0x1ull, 0xbff0000000000000ull, 0xbff0000000000000ull}, + {4, 0xbff0000000000000ull, 0x800fffffffffffffull, 0x3ff0000000000000ull, 0xbff0000000000000ull}, + {4, 0xbff0000000000000ull, 0x7fefffffffffffffull, 0x3ff8000000000000ull, 0xbff0000000000000ull}, + {4, 0xbff0000000000000ull, 0x10000000000000ull, 0x10000000000000ull, 0xbff0000000000000ull}, + {5, 0x0ull, 0x0ull, 0x0ull, 0x0ull}, + {5, 0x0ull, 0x8000000000000000ull, 0x8000000000000000ull, 0x0ull}, + {5, 0x0ull, 0x3ff0000000000000ull, 0xc004000000000000ull, 0x3ff0000000000000ull}, + {5, 0x0ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0x0ull}, + {5, 0x0ull, 0x3ff8000000000000ull, 0x0ull, 0x3ff8000000000000ull}, + {5, 0x0ull, 0xc004000000000000ull, 0x8000000000000000ull, 0x0ull}, + {5, 0x0ull, 0x7ff0000000000000ull, 0xfff8000012345678ull, 0x7ff0000000000000ull}, + {5, 0x0ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0x0ull}, + {5, 0x0ull, 0x7ff8000000000000ull, 0x0ull, 0x0ull}, + {5, 0x0ull, 0xfff8000012345678ull, 0x8000000000000000ull, 0x0ull}, + {5, 0x0ull, 0x7ff4000000000000ull, 0x800fffffffffffffull, 0x7ffc000000000000ull}, + {5, 0x0ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0xfff8000000000001ull}, + {5, 0x0ull, 0x1ull, 0x0ull, 0x1ull}, + {5, 0x0ull, 0x800fffffffffffffull, 0x8000000000000000ull, 0x0ull}, + {5, 0x0ull, 0x7fefffffffffffffull, 0x8000000000000000ull, 0x7fefffffffffffffull}, + {5, 0x0ull, 0x10000000000000ull, 0x10000000000000ull, 0x10000000000000ull}, + {5, 0x8000000000000000ull, 0x0ull, 0x8000000000000000ull, 0x0ull}, + {5, 0x8000000000000000ull, 0x8000000000000000ull, 0x0ull, 0x8000000000000000ull}, + {5, 0x8000000000000000ull, 0x3ff0000000000000ull, 0x7ff0000000000000ull, 0x3ff0000000000000ull}, + {5, 0x8000000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0x8000000000000000ull}, + {5, 0x8000000000000000ull, 0x3ff8000000000000ull, 0x8000000000000000ull, 0x3ff8000000000000ull}, + {5, 0x8000000000000000ull, 0xc004000000000000ull, 0x0ull, 0x8000000000000000ull}, + {5, 0x8000000000000000ull, 0x7ff0000000000000ull, 0x7ff4000000000000ull, 0x7ff0000000000000ull}, + {5, 0x8000000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0x8000000000000000ull}, + {5, 0x8000000000000000ull, 0x7ff8000000000000ull, 0x8000000000000000ull, 0x8000000000000000ull}, + {5, 0x8000000000000000ull, 0xfff8000012345678ull, 0x0ull, 0x8000000000000000ull}, + {5, 0x8000000000000000ull, 0x7ff4000000000000ull, 0x7fefffffffffffffull, 0x7ffc000000000000ull}, + {5, 0x8000000000000000ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0xfff8000000000001ull}, + {5, 0x8000000000000000ull, 0x1ull, 0x8000000000000000ull, 0x1ull}, + {5, 0x8000000000000000ull, 0x800fffffffffffffull, 0x0ull, 0x8000000000000000ull}, + {5, 0x8000000000000000ull, 0x7fefffffffffffffull, 0x3ff0000000000000ull, 0x7fefffffffffffffull}, + {5, 0x8000000000000000ull, 0x10000000000000ull, 0x10000000000000ull, 0x10000000000000ull}, + {5, 0x3ff0000000000000ull, 0x0ull, 0x3ff0000000000000ull, 0x3ff0000000000000ull}, + {5, 0x3ff0000000000000ull, 0x8000000000000000ull, 0xbff0000000000000ull, 0x3ff0000000000000ull}, + {5, 0x3ff0000000000000ull, 0x3ff0000000000000ull, 0xfff0000000000000ull, 0x3ff0000000000000ull}, + {5, 0x3ff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0x3ff0000000000000ull}, + {5, 0x3ff0000000000000ull, 0x3ff8000000000000ull, 0x3ff0000000000000ull, 0x3ff8000000000000ull}, + {5, 0x3ff0000000000000ull, 0xc004000000000000ull, 0xbff0000000000000ull, 0x3ff0000000000000ull}, + {5, 0x3ff0000000000000ull, 0x7ff0000000000000ull, 0xfff0000000000001ull, 0x7ff0000000000000ull}, + {5, 0x3ff0000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0x3ff0000000000000ull}, + {5, 0x3ff0000000000000ull, 0x7ff8000000000000ull, 0x3ff0000000000000ull, 0x3ff0000000000000ull}, + {5, 0x3ff0000000000000ull, 0xfff8000012345678ull, 0xbff0000000000000ull, 0x3ff0000000000000ull}, + {5, 0x3ff0000000000000ull, 0x7ff4000000000000ull, 0x10000000000000ull, 0x7ffc000000000000ull}, + {5, 0x3ff0000000000000ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0xfff8000000000001ull}, + {5, 0x3ff0000000000000ull, 0x1ull, 0x3ff0000000000000ull, 0x3ff0000000000000ull}, + {5, 0x3ff0000000000000ull, 0x800fffffffffffffull, 0xbff0000000000000ull, 0x3ff0000000000000ull}, + {5, 0x3ff0000000000000ull, 0x7fefffffffffffffull, 0xbff0000000000000ull, 0x7fefffffffffffffull}, + {5, 0x3ff0000000000000ull, 0x10000000000000ull, 0x10000000000000ull, 0x3ff0000000000000ull}, + {5, 0xbff0000000000000ull, 0x0ull, 0xbff0000000000000ull, 0x0ull}, + {5, 0xbff0000000000000ull, 0x8000000000000000ull, 0x3ff0000000000000ull, 0x8000000000000000ull}, + {5, 0xbff0000000000000ull, 0x3ff0000000000000ull, 0x7ff8000000000000ull, 0x3ff0000000000000ull}, + {5, 0xbff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull}, + {5, 0xbff0000000000000ull, 0x3ff8000000000000ull, 0xbff0000000000000ull, 0x3ff8000000000000ull}, + {5, 0xbff0000000000000ull, 0xc004000000000000ull, 0x3ff0000000000000ull, 0xbff0000000000000ull}, + {5, 0xbff0000000000000ull, 0x7ff0000000000000ull, 0x1ull, 0x7ff0000000000000ull}, + {5, 0xbff0000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0xbff0000000000000ull}, + {5, 0xbff0000000000000ull, 0x7ff8000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull}, + {5, 0xbff0000000000000ull, 0xfff8000012345678ull, 0x3ff0000000000000ull, 0xbff0000000000000ull}, + {5, 0xbff0000000000000ull, 0x7ff4000000000000ull, 0x0ull, 0x7ffc000000000000ull}, + {5, 0xbff0000000000000ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0xfff8000000000001ull}, + {5, 0xbff0000000000000ull, 0x1ull, 0xbff0000000000000ull, 0x1ull}, + {5, 0xbff0000000000000ull, 0x800fffffffffffffull, 0x3ff0000000000000ull, 0x800fffffffffffffull}, + {5, 0xbff0000000000000ull, 0x7fefffffffffffffull, 0x3ff8000000000000ull, 0x7fefffffffffffffull}, + {5, 0xbff0000000000000ull, 0x10000000000000ull, 0x10000000000000ull, 0x10000000000000ull}, + {6, 0x0ull, 0x0ull, 0x0ull, 0x0ull}, + {6, 0x0ull, 0x1ull, 0x8000000000000000ull, 0x1ull}, + {6, 0x0ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x1ull}, + {6, 0x1ull, 0x0ull, 0x1ull, 0x0ull}, + {6, 0x1ull, 0x1ull, 0x8000000000000001ull, 0x0ull}, + {6, 0x1ull, 0x2ull, 0xfffffffffffffffeull, 0x2ull}, + {6, 0x1ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x2ull}, + {6, 0x7fffffffffffffffull, 0x0ull, 0x7fffffffffffffffull, 0x0ull}, + {6, 0x0ull, 0x2ull, 0xffffffffffffffffull, 0x1ull}, + {6, 0x0ull, 0x8000000000000000ull, 0x0ull, 0x1ull}, + {6, 0x0ull, 0xffffffffffffffffull, 0x8000000000000000ull, 0x1ull}, + {6, 0x0ull, 0xfffffffffffffffeull, 0x100000001ull, 0x1ull}, + {6, 0x0ull, 0xffffffffull, 0xffffffffull, 0x1ull}, + {6, 0x0ull, 0x100000000ull, 0x0ull, 0x1ull}, + {6, 0x0ull, 0x100000001ull, 0x8000000000000000ull, 0x1ull}, + {6, 0x0ull, 0x123456789abcdef0ull, 0x3ull, 0x1ull}, + {6, 0x0ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull, 0x1ull}, + {6, 0x0ull, 0xfffffffeull, 0x0ull, 0x1ull}, + {6, 0x0ull, 0x3ull, 0x8000000000000000ull, 0x1ull}, + {6, 0x0ull, 0x80000000ull, 0x1ull, 0x1ull}, + {6, 0x0ull, 0x7fffffffull, 0x7fffffffull, 0x1ull}, + {6, 0x1ull, 0x8000000000000000ull, 0x1ull, 0x2ull}, + {6, 0x1ull, 0xffffffffffffffffull, 0x8000000000000001ull, 0x2ull}, + {6, 0x1ull, 0xfffffffffffffffeull, 0x123456789abcdef0ull, 0x2ull}, + {6, 0x1ull, 0xffffffffull, 0xffffffffull, 0x2ull}, + {6, 0x1ull, 0x100000000ull, 0x1ull, 0x2ull}, + {6, 0x1ull, 0x100000001ull, 0x8000000000000001ull, 0x2ull}, + {6, 0x1ull, 0x123456789abcdef0ull, 0x80000000ull, 0x2ull}, + {6, 0x1ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull, 0x2ull}, + {6, 0x1ull, 0xfffffffeull, 0x1ull, 0x2ull}, + {6, 0x1ull, 0x3ull, 0x8000000000000001ull, 0x2ull}, + {6, 0x1ull, 0x80000000ull, 0x2ull, 0x2ull}, + {6, 0x1ull, 0x7fffffffull, 0x7fffffffull, 0x2ull}, + {6, 0x2ull, 0x0ull, 0x2ull, 0x0ull}, + {6, 0x2ull, 0x1ull, 0x8000000000000002ull, 0x0ull}, + {6, 0x2ull, 0x2ull, 0xffffffffull, 0x0ull}, + {6, 0x2ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x3ull}, + {6, 0x2ull, 0x8000000000000000ull, 0x2ull, 0x3ull}, + {6, 0x2ull, 0xffffffffffffffffull, 0x8000000000000002ull, 0x3ull}, + {6, 0x2ull, 0xfffffffffffffffeull, 0xfffffffeffffffffull, 0x3ull}, + {6, 0x2ull, 0xffffffffull, 0xffffffffull, 0x3ull}, + {6, 0x2ull, 0x100000000ull, 0x2ull, 0x3ull}, + {6, 0x2ull, 0x100000001ull, 0x8000000000000002ull, 0x3ull}, + {6, 0x2ull, 0x123456789abcdef0ull, 0x7fffffffull, 0x3ull}, + {6, 0x2ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull, 0x3ull}, + {6, 0x2ull, 0xfffffffeull, 0x2ull, 0x3ull}, + {6, 0x2ull, 0x3ull, 0x8000000000000002ull, 0x3ull}, + {6, 0x2ull, 0x80000000ull, 0x7fffffffffffffffull, 0x3ull}, + {6, 0x2ull, 0x7fffffffull, 0x7fffffffull, 0x3ull}, + {6, 0x7fffffffffffffffull, 0x1ull, 0xffffffffffffffffull, 0x0ull}, + {6, 0x7fffffffffffffffull, 0x2ull, 0x100000000ull, 0x0ull}, + {6, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x0ull}, + {6, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {6, 0x7fffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0x8000000000000000ull}, + {6, 0x7fffffffffffffffull, 0xfffffffffffffffeull, 0xfffffffeull, 0x8000000000000000ull}, + {6, 0x7fffffffffffffffull, 0xffffffffull, 0xffffffffull, 0x0ull}, + {6, 0x7fffffffffffffffull, 0x100000000ull, 0x7fffffffffffffffull, 0x0ull}, + {6, 0x7fffffffffffffffull, 0x100000001ull, 0xffffffffffffffffull, 0x0ull}, + {6, 0x7fffffffffffffffull, 0x123456789abcdef0ull, 0x0ull, 0x0ull}, + {6, 0x7fffffffffffffffull, 0xfffffffeffffffffull, 0xfffffffeffffffffull, 0x8000000000000000ull}, + {6, 0x7fffffffffffffffull, 0xfffffffeull, 0x7fffffffffffffffull, 0x0ull}, + {6, 0x7fffffffffffffffull, 0x3ull, 0xffffffffffffffffull, 0x0ull}, + {6, 0x7fffffffffffffffull, 0x80000000ull, 0x8000000000000000ull, 0x0ull}, + {6, 0x7fffffffffffffffull, 0x7fffffffull, 0x7fffffffull, 0x0ull}, + {7, 0x0ull, 0x0ull, 0x0ull, 0x0ull}, + {7, 0x0ull, 0x1ull, 0x8000000000000000ull, 0x1ull}, + {7, 0x0ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {7, 0x1ull, 0x0ull, 0x1ull, 0x0ull}, + {7, 0x1ull, 0x1ull, 0x8000000000000001ull, 0x0ull}, + {7, 0x1ull, 0x2ull, 0xfffffffffffffffeull, 0x0ull}, + {7, 0x1ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x0ull}, + {7, 0x7fffffffffffffffull, 0x0ull, 0x7fffffffffffffffull, 0x0ull}, + {7, 0x0ull, 0x2ull, 0xffffffffffffffffull, 0x2ull}, + {7, 0x0ull, 0x8000000000000000ull, 0x0ull, 0x8000000000000000ull}, + {7, 0x0ull, 0xffffffffffffffffull, 0x8000000000000000ull, 0xffffffffffffffffull}, + {7, 0x0ull, 0xfffffffffffffffeull, 0x100000001ull, 0xfffffffffffffffeull}, + {7, 0x0ull, 0xffffffffull, 0xffffffffull, 0xffffffffull}, + {7, 0x0ull, 0x100000000ull, 0x0ull, 0x100000000ull}, + {7, 0x0ull, 0x100000001ull, 0x8000000000000000ull, 0x100000001ull}, + {7, 0x0ull, 0x123456789abcdef0ull, 0x3ull, 0x123456789abcdef0ull}, + {7, 0x0ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, + {7, 0x0ull, 0xfffffffeull, 0x0ull, 0xfffffffeull}, + {7, 0x0ull, 0x3ull, 0x8000000000000000ull, 0x3ull}, + {7, 0x0ull, 0x80000000ull, 0x1ull, 0x80000000ull}, + {7, 0x0ull, 0x7fffffffull, 0x7fffffffull, 0x7fffffffull}, + {7, 0x1ull, 0x8000000000000000ull, 0x1ull, 0x0ull}, + {7, 0x1ull, 0xffffffffffffffffull, 0x8000000000000001ull, 0x0ull}, + {7, 0x1ull, 0xfffffffffffffffeull, 0x123456789abcdef0ull, 0x0ull}, + {7, 0x1ull, 0xffffffffull, 0xffffffffull, 0x0ull}, + {7, 0x1ull, 0x100000000ull, 0x1ull, 0x0ull}, + {7, 0x1ull, 0x100000001ull, 0x8000000000000001ull, 0x0ull}, + {7, 0x1ull, 0x123456789abcdef0ull, 0x80000000ull, 0x0ull}, + {7, 0x1ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull, 0x0ull}, + {7, 0x1ull, 0xfffffffeull, 0x1ull, 0x0ull}, + {7, 0x1ull, 0x3ull, 0x8000000000000001ull, 0x0ull}, + {7, 0x1ull, 0x80000000ull, 0x2ull, 0x0ull}, + {7, 0x1ull, 0x7fffffffull, 0x7fffffffull, 0x0ull}, + {7, 0x2ull, 0x0ull, 0x2ull, 0x0ull}, + {7, 0x2ull, 0x1ull, 0x8000000000000002ull, 0x1ull}, + {7, 0x2ull, 0x2ull, 0xffffffffull, 0x1ull}, + {7, 0x2ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x1ull}, + {7, 0x2ull, 0x8000000000000000ull, 0x2ull, 0x1ull}, + {7, 0x2ull, 0xffffffffffffffffull, 0x8000000000000002ull, 0x1ull}, + {7, 0x2ull, 0xfffffffffffffffeull, 0xfffffffeffffffffull, 0x1ull}, + {7, 0x2ull, 0xffffffffull, 0xffffffffull, 0x1ull}, + {7, 0x2ull, 0x100000000ull, 0x2ull, 0x1ull}, + {7, 0x2ull, 0x100000001ull, 0x8000000000000002ull, 0x1ull}, + {7, 0x2ull, 0x123456789abcdef0ull, 0x7fffffffull, 0x1ull}, + {7, 0x2ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull, 0x1ull}, + {7, 0x2ull, 0xfffffffeull, 0x2ull, 0x1ull}, + {7, 0x2ull, 0x3ull, 0x8000000000000002ull, 0x1ull}, + {7, 0x2ull, 0x80000000ull, 0x7fffffffffffffffull, 0x1ull}, + {7, 0x2ull, 0x7fffffffull, 0x7fffffffull, 0x1ull}, + {7, 0x7fffffffffffffffull, 0x1ull, 0xffffffffffffffffull, 0x1ull}, + {7, 0x7fffffffffffffffull, 0x2ull, 0x100000000ull, 0x2ull}, + {7, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7ffffffffffffffeull}, + {7, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7ffffffffffffffeull}, + {7, 0x7fffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0x7ffffffffffffffeull}, + {7, 0x7fffffffffffffffull, 0xfffffffffffffffeull, 0xfffffffeull, 0x7ffffffffffffffeull}, + {7, 0x7fffffffffffffffull, 0xffffffffull, 0xffffffffull, 0xffffffffull}, + {7, 0x7fffffffffffffffull, 0x100000000ull, 0x7fffffffffffffffull, 0x100000000ull}, + {7, 0x7fffffffffffffffull, 0x100000001ull, 0xffffffffffffffffull, 0x100000001ull}, + {7, 0x7fffffffffffffffull, 0x123456789abcdef0ull, 0x0ull, 0x123456789abcdef0ull}, + {7, 0x7fffffffffffffffull, 0xfffffffeffffffffull, 0xfffffffeffffffffull, 0x7ffffffffffffffeull}, + {7, 0x7fffffffffffffffull, 0xfffffffeull, 0x7fffffffffffffffull, 0xfffffffeull}, + {7, 0x7fffffffffffffffull, 0x3ull, 0xffffffffffffffffull, 0x3ull}, + {7, 0x7fffffffffffffffull, 0x80000000ull, 0x8000000000000000ull, 0x80000000ull}, + {7, 0x7fffffffffffffffull, 0x7fffffffull, 0x7fffffffull, 0x7fffffffull} +}}; + +class GuestBlock { +public: + explicit GuestBlock(bool writable) { +#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 float atomics: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, writable); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + const std::uint8_t* Data() const { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +void Put(std::vector& image, std::uint32_t offset, std::uint64_t value, std::uint32_t bytes) { + for (std::uint32_t byte = 0; byte < bytes; ++byte) image.at(offset + byte) = static_cast(value >> (byte * 8u)); +} + +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::uint64_t value) { + char text[24]; + std::snprintf(text, sizeof(text), "0x%llx", static_cast(value)); + return text; +} + +const Row& RowOf(std::uint32_t op, std::uint32_t tid) { + return Rows.at(op * Threads + tid); +} + +std::uint32_t OpBytes(std::uint32_t op) { + return op < NarrowOps ? 4u : 8u; +} + +std::vector Initial() { + std::vector image(BlockBytes, Fill); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).memory, OpBytes(op)); + } + } + return image; +} + +std::vector Expected() { + auto image = Initial(); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).final, OpBytes(op)); + } + } + return image; +} + +void FillInput() { + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + auto* words = &Input[(op * Threads + tid) * 4u]; + if (OpBytes(op) == 4u) { + words[0] = static_cast(row.data); + words[1] = static_cast(row.comparator); + words[2] = 0u; + words[3] = 0u; + } else { + words[0] = static_cast(row.data); + words[1] = static_cast(row.data >> 32u); + words[2] = static_cast(row.comparator); + words[3] = static_cast(row.comparator >> 32u); + } + } + } +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 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); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::span code(GlobalAtomicsFloatCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 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, memory}, + device.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(); +} + +void RunAtomics(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + FillInput(); + Output.fill(Sentinel); + Dispatch(device, waveSize, guest.Data()); + const auto wave = "global float atomics: wave" + std::to_string(waveSize) + " "; + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + const auto* words = &Output[(op * Threads + tid) * 2u]; + const auto returned = OpBytes(op) == 4u ? static_cast(words[0]) : (static_cast(words[1]) << 32u) | words[0]; + Require(returned == row.memory, wave + "op " + std::to_string(op) + " lane " + std::to_string(tid) + " returned " + Hex(returned) + ", expected " + Hex(row.memory)); + } + } + const auto expected = Expected(); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto offset = op * OpStride + tid * 8u; + std::uint64_t actual = 0; + std::memcpy(&actual, guest.Data() + offset, OpBytes(op)); + const auto& row = RowOf(op, tid); + Require(actual == row.final, wave + "op " + std::to_string(op) + " lane " + std::to_string(tid) + " memory " + Hex(row.memory) + " data " + Hex(row.data) + " comparator " + Hex(row.comparator) + " is " + Hex(actual) + ", expected " + Hex(row.final)); + } + } + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + const auto actual = guest.Data()[offset]; + Require(actual == expected[offset], wave + "byte " + std::to_string(offset) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[offset])); + } +} + +void RunReadOnly(AgcDriver::VulkanDevice& device, GuestBlock& guest) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + FillInput(); + Dispatch(device, 32, guest.Data()); + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + Require(guest.Data()[offset] == initial[offset], "global float atomics: an atomic into a read-only range changed byte " + std::to_string(offset)); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock writable(true); + GuestBlock readOnly(false); + RunAtomics(*device, writable, 32); + RunAtomics(*device, writable, 64); + RunReadOnly(*device, readOnly); + std::puts("global float atomics tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp index 7ef446397..54ccbafb1 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp @@ -317,6 +317,14 @@ enum class IrOpcode : std::uint16_t { AddressAtomicAnd64, AddressAtomicOr64, AddressAtomicXor64, + AddressAtomicFCmpSwap32, + AddressAtomicFMin32, + AddressAtomicFMax32, + AddressAtomicFCmpSwap64, + AddressAtomicFMin64, + AddressAtomicFMax64, + AddressAtomicInc64, + AddressAtomicDec64, LoadBufferU8, LoadBufferU16, LoadBufferU32, diff --git a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp index 497292518..5338c5d3f 100644 --- a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp +++ b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp @@ -358,6 +358,14 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("AddressAtomicAnd64", U64, AddressResource, U32, U32, U64, U1), makeMeta("AddressAtomicOr64", U64, AddressResource, U32, U32, U64, U1), makeMeta("AddressAtomicXor64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicFCmpSwap32", U32, AddressResource, U32, U32, U32, U32, U1), + makeMeta("AddressAtomicFMin32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicFMax32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicFCmpSwap64", U64, AddressResource, U32, U32, U64, U64, U1), + makeMeta("AddressAtomicFMin64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicFMax64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicInc64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicDec64", U64, AddressResource, U32, U32, U64, U1), makeMeta("LoadBufferU8", U8, BufferResource, U32, U32, U32, U1), makeMeta("LoadBufferU16", U16, BufferResource, U32, U32, U32, U1), makeMeta("LoadBufferU32", U32, BufferResource, U32, U32, U32, U1), @@ -739,6 +747,9 @@ AddressOpcodeInfo AddressOpcodeInfoOf(IrOpcode opcode) { case IrOpcode::AddressAtomicXor32: case IrOpcode::AddressAtomicInc32: case IrOpcode::AddressAtomicDec32: + case IrOpcode::AddressAtomicFCmpSwap32: + case IrOpcode::AddressAtomicFMin32: + case IrOpcode::AddressAtomicFMax32: return {AddressAccess::Atomic, 32u}; case IrOpcode::AddressAtomicSwap64: case IrOpcode::AddressAtomicCmpSwap64: @@ -751,6 +762,11 @@ AddressOpcodeInfo AddressOpcodeInfoOf(IrOpcode opcode) { case IrOpcode::AddressAtomicAnd64: case IrOpcode::AddressAtomicOr64: case IrOpcode::AddressAtomicXor64: + case IrOpcode::AddressAtomicFCmpSwap64: + case IrOpcode::AddressAtomicFMin64: + case IrOpcode::AddressAtomicFMax64: + case IrOpcode::AddressAtomicInc64: + case IrOpcode::AddressAtomicDec64: return {AddressAccess::Atomic, 32u, 2u}; default: return {}; diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp index 18932387b..0f8426c2d 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -800,6 +800,14 @@ enum class RdnaOpcode : std::uint16_t { FlatAtomicAndX2, FlatAtomicOrX2, FlatAtomicXorX2, + FlatAtomicFcmpswap, + FlatAtomicFmin, + FlatAtomicFmax, + FlatAtomicFcmpswapX2, + FlatAtomicFminX2, + FlatAtomicFmaxX2, + FlatAtomicIncX2, + FlatAtomicDecX2, DsAddU32, DsAddRtnU32, DsSubU32, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp index cb034b22f..cd8150725 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp @@ -208,6 +208,14 @@ constexpr MemoryOpcodeInfo flatOpcodes[] = { {0x59u, RdnaOpcode::FlatAtomicAndX2, 2, 32, false, false, false}, {0x5au, RdnaOpcode::FlatAtomicOrX2, 2, 32, false, false, false}, {0x5bu, RdnaOpcode::FlatAtomicXorX2, 2, 32, false, false, false}, + {0x3eu, RdnaOpcode::FlatAtomicFcmpswap, 1, 32, false, false, false}, + {0x3fu, RdnaOpcode::FlatAtomicFmin, 1, 32, false, false, false}, + {0x40u, RdnaOpcode::FlatAtomicFmax, 1, 32, false, false, false}, + {0x5eu, RdnaOpcode::FlatAtomicFcmpswapX2, 2, 32, false, false, false}, + {0x5fu, RdnaOpcode::FlatAtomicFminX2, 2, 32, false, false, false}, + {0x60u, RdnaOpcode::FlatAtomicFmaxX2, 2, 32, false, false, false}, + {0x5cu, RdnaOpcode::FlatAtomicIncX2, 2, 32, false, false, false}, + {0x5du, RdnaOpcode::FlatAtomicDecX2, 2, 32, false, false, false}, }; constexpr MemoryOpcodeInfo dsOpcodes[] = { @@ -583,6 +591,14 @@ bool isFlatAtomicOpcode(RdnaOpcode opcode) { case RdnaOpcode::FlatAtomicAndX2: case RdnaOpcode::FlatAtomicOrX2: case RdnaOpcode::FlatAtomicXorX2: + case RdnaOpcode::FlatAtomicFcmpswap: + case RdnaOpcode::FlatAtomicFmin: + case RdnaOpcode::FlatAtomicFmax: + case RdnaOpcode::FlatAtomicFcmpswapX2: + case RdnaOpcode::FlatAtomicFminX2: + case RdnaOpcode::FlatAtomicFmaxX2: + case RdnaOpcode::FlatAtomicIncX2: + case RdnaOpcode::FlatAtomicDecX2: return true; default: return false; } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp index 26570c011..b3806b856 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp @@ -491,6 +491,14 @@ void EmitDirectInstruction(SpirvValueEmitContext& ctx, const IrValue& inst) { case IrOpcode::AddressAtomicAnd64: return Invoke(EmitAddressAtomic, ctx, inst); case IrOpcode::AddressAtomicOr64: return Invoke(EmitAddressAtomic, ctx, inst); case IrOpcode::AddressAtomicXor64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicFCmpSwap32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicFMin32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicFMax32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicFCmpSwap64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicFMin64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicFMax64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicInc64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicDec64: return Invoke(EmitAddressAtomic, ctx, inst); case IrOpcode::LoadBufferU8: return Invoke(EmitLoadBufferU8, ctx, inst); case IrOpcode::LoadBufferU16: return Invoke(EmitLoadBufferU16, ctx, inst); case IrOpcode::LoadBufferU32: return Invoke(EmitLoadBufferU32, ctx, inst); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp index 55e17c84d..39f56e737 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp @@ -995,6 +995,16 @@ struct AtomicFloatBits { result = select(signalingNan(old), op(spv::OpBitwiseOr, old, constant(quiet())), result); return select(signalingNan(source), op(spv::OpBitwiseOr, source, constant(quiet())), result); } + std::uint32_t compareSwap(std::uint32_t old, std::uint32_t comparator, std::uint32_t desired) const { + return select(equal(old, comparator), desired, old); + } + std::uint32_t increment(std::uint32_t old, std::uint32_t limit) const { + return select(test(spv::OpUGreaterThanEqual, old, limit), constant(0u), op(spv::OpIAdd, old, constant(1u))); + } + std::uint32_t decrement(std::uint32_t old, std::uint32_t limit) const { + const auto wrap = Binary(state, spv::OpLogicalOr, TypeBool(state), test(spv::OpIEqual, old, constant(0u)), test(spv::OpUGreaterThan, old, limit)); + return select(wrap, limit, op(spv::OpISub, old, constant(1u))); + } }; std::uint32_t BufferFloatAtomic(SpirvValueEmitContext& ctx, const IrValue& inst, bool maxValue) { @@ -1115,6 +1125,27 @@ std::uint32_t AddressAtomic(SpirvValueEmitContext& ctx, const IrValue& inst) { return increment ? AtomicIncrement(state, current, value) : AtomicDecrement(state, current, value); }); } + const AtomicFloatBits bits{state, wide}; + const auto update = [&](auto&& replacement) { return AtomicUpdateTyped(state, pointer, mem.kind, scalarType, replacement); }; + switch (inst.Opcode()) { + case IrOpcode::AddressAtomicFCmpSwap32: + case IrOpcode::AddressAtomicFCmpSwap64: { + const auto comparator = scalar(ctx.Arg(inst, 4)); + return update([&](std::uint32_t current) { return bits.compareSwap(current, comparator, value); }); + } + case IrOpcode::AddressAtomicFMin32: + case IrOpcode::AddressAtomicFMin64: + return update([&](std::uint32_t current) { return bits.minMax(current, value, false); }); + case IrOpcode::AddressAtomicFMax32: + case IrOpcode::AddressAtomicFMax64: + return update([&](std::uint32_t current) { return bits.minMax(current, value, true); }); + case IrOpcode::AddressAtomicInc64: + return update([&](std::uint32_t current) { return bits.increment(current, value); }); + case IrOpcode::AddressAtomicDec64: + return update([&](std::uint32_t current) { return bits.decrement(current, value); }); + default: + break; + } const auto scope = ConstantU32(state, spv::ScopeDevice); const auto semantics = ConstantU32(state, spv::MemorySemanticsMaskNone); const auto opcode = AddressAtomicOpcode(inst.Opcode()); @@ -1839,20 +1870,14 @@ std::uint32_t EmitBufferAtomicFCmpSwap32(SpirvValueEmitContext& ctx, const IrVal const auto comparator = ctx.Arg(inst, inst.ArgumentCount() - 2u); const auto& mem = BufferMemory(ctx, inst); return EmitAtomicAccess(ctx, inst, mem, [&](std::uint32_t pointer) { - return AtomicUpdate(ctx.state, pointer, mem.kind, [&](std::uint32_t old) { - const AtomicFloatBits bits{ctx.state, false}; - return bits.select(bits.equal(old, comparator), desired, old); - }); + return AtomicUpdate(ctx.state, pointer, mem.kind, [&](std::uint32_t old) { return AtomicFloatBits{ctx.state, false}.compareSwap(old, comparator, desired); }); }); } std::uint32_t EmitBufferAtomicFCmpSwap64(SpirvValueEmitContext& ctx, const IrValue& inst) { const auto desired = ScalarU64Argument(ctx, inst, 3u); const auto comparator = ScalarU64Argument(ctx, inst, 2u); - return BufferAtomic64Update(ctx, inst, [&](std::uint32_t old) { - const AtomicFloatBits bits{ctx.state, true}; - return bits.select(bits.equal(old, comparator), desired, old); - }); + return BufferAtomic64Update(ctx, inst, [&](std::uint32_t old) { return AtomicFloatBits{ctx.state, true}.compareSwap(old, comparator, desired); }); } std::uint32_t EmitBufferAtomicFMin64(SpirvValueEmitContext& ctx, const IrValue& inst) { @@ -1867,19 +1892,12 @@ std::uint32_t EmitBufferAtomicFMax64(SpirvValueEmitContext& ctx, const IrValue& std::uint32_t EmitBufferAtomicInc64(SpirvValueEmitContext& ctx, const IrValue& inst) { const auto limit = ScalarU64Argument(ctx, inst, 2u); - return BufferAtomic64Update(ctx, inst, [&](std::uint32_t old) { - const AtomicFloatBits bits{ctx.state, true}; - return bits.select(bits.test(spv::OpUGreaterThanEqual, old, limit), bits.constant(0u), bits.op(spv::OpIAdd, old, bits.constant(1u))); - }); + return BufferAtomic64Update(ctx, inst, [&](std::uint32_t old) { return AtomicFloatBits{ctx.state, true}.increment(old, limit); }); } std::uint32_t EmitBufferAtomicDec64(SpirvValueEmitContext& ctx, const IrValue& inst) { const auto limit = ScalarU64Argument(ctx, inst, 2u); - return BufferAtomic64Update(ctx, inst, [&](std::uint32_t old) { - const AtomicFloatBits bits{ctx.state, true}; - const auto wrap = Binary(ctx.state, spv::OpLogicalOr, TypeBool(ctx.state), bits.test(spv::OpIEqual, old, bits.constant(0u)), bits.test(spv::OpUGreaterThan, old, limit)); - return bits.select(wrap, limit, bits.op(spv::OpISub, old, bits.constant(1u))); - }); + return BufferAtomic64Update(ctx, inst, [&](std::uint32_t old) { return AtomicFloatBits{ctx.state, true}.decrement(old, limit); }); } } diff --git a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp index 08768d213..cff0d65b7 100644 --- a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp +++ b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp @@ -154,7 +154,7 @@ void includeInstructionVectorRegisters(const RdnaInstruction& instruction, std:: includeVector(instruction.source2); includeVector(instruction.source3); if (instruction.family == RdnaInstructionFamily::FLAT) { - const bool compare = instruction.op == RdnaOpcode::FlatAtomicCmpswap || instruction.op == RdnaOpcode::FlatAtomicCmpswapX2; + const bool compare = instruction.op == RdnaOpcode::FlatAtomicCmpswap || instruction.op == RdnaOpcode::FlatAtomicCmpswapX2 || instruction.op == RdnaOpcode::FlatAtomicFcmpswap || instruction.op == RdnaOpcode::FlatAtomicFcmpswapX2; includeVector(instruction.source2, std::max(instruction.dataDwordCount, 1u) * (compare ? 2u : 1u)); } if (instruction.family == RdnaInstructionFamily::DS) { diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/FlatInstructions.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/FlatInstructions.cpp index 55754aac4..806a3a799 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/FlatInstructions.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/FlatInstructions.cpp @@ -146,7 +146,7 @@ bool TranslationContext::flatAtomic(const RdnaInstruction& inst, IrOpcode opcode const AddressOperands address = readAddressOperands(inst, 0u); const MemoryFlags flags = addMemoryInfo(memory, inst.programCounter); IrValue& active = ir.GetExec(); - const bool compare = opcode == IrOpcode::AddressAtomicCmpSwap32 || opcode == IrOpcode::AddressAtomicCmpSwap64; + const bool compare = opcode == IrOpcode::AddressAtomicCmpSwap32 || opcode == IrOpcode::AddressAtomicCmpSwap64 || opcode == IrOpcode::AddressAtomicFCmpSwap32 || opcode == IrOpcode::AddressAtomicFCmpSwap64; IrValue* result; if (IrOpcodeType(opcode) == IrType::U64) { const IrU64 value = readU64(inst.source2); diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp index fbaa93398..6086e6cad 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp @@ -255,6 +255,22 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) { return flatAtomic(inst, IrOpcode::AddressAtomicOr64); case RdnaOpcode::FlatAtomicXorX2: return flatAtomic(inst, IrOpcode::AddressAtomicXor64); + case RdnaOpcode::FlatAtomicFcmpswap: + return flatAtomic(inst, IrOpcode::AddressAtomicFCmpSwap32); + case RdnaOpcode::FlatAtomicFmin: + return flatAtomic(inst, IrOpcode::AddressAtomicFMin32); + case RdnaOpcode::FlatAtomicFmax: + return flatAtomic(inst, IrOpcode::AddressAtomicFMax32); + case RdnaOpcode::FlatAtomicFcmpswapX2: + return flatAtomic(inst, IrOpcode::AddressAtomicFCmpSwap64); + case RdnaOpcode::FlatAtomicFminX2: + return flatAtomic(inst, IrOpcode::AddressAtomicFMin64); + case RdnaOpcode::FlatAtomicFmaxX2: + return flatAtomic(inst, IrOpcode::AddressAtomicFMax64); + case RdnaOpcode::FlatAtomicIncX2: + return flatAtomic(inst, IrOpcode::AddressAtomicInc64); + case RdnaOpcode::FlatAtomicDecX2: + return flatAtomic(inst, IrOpcode::AddressAtomicDec64); case RdnaOpcode::DsAddU32: return dsAtomic(inst, IrOpcode::SharedAtomicIAdd32, false); From 76b7294f065ffb780bf2db3dccb754fa1d7d7f53 Mon Sep 17 00:00:00 2001 From: Nicolas Abosch <88494836+nicolasabosch@users.noreply.github.com> Date: Mon, 5 Oct 2026 00:15:56 -0300 Subject: [PATCH 082/164] fix(rtc): reject out-of-range source ticks in date arithmetic --- core/libs/prx/libSceRtc/Export.cpp | 2 ++ core/libs/tests/GuestRtc.cpp | 33 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/core/libs/prx/libSceRtc/Export.cpp b/core/libs/prx/libSceRtc/Export.cpp index 272b78cb3..3a0895ae1 100644 --- a/core/libs/prx/libSceRtc/Export.cpp +++ b/core/libs/prx/libSceRtc/Export.cpp @@ -109,6 +109,7 @@ std::int64_t localOffsetSeconds(std::uint64_t utcTick) { int addTicks(RtcTick* dst, const RtcTick* src, std::int64_t count, std::int64_t unit) { if (!dst || !src) return SCE_RTC_ERROR_INVALID_POINTER; + if (src->tick > MAX_TICK) return SCE_RTC_ERROR_INVALID_VALUE; if (count != 0 && (count > std::numeric_limits::max() / unit || count < std::numeric_limits::min() / unit)) return SCE_RTC_ERROR_INVALID_VALUE; const std::int64_t delta = count * unit; if (delta < 0 ? src->tick < static_cast(-(delta + 1)) + 1 : MAX_TICK - src->tick < static_cast(delta)) return SCE_RTC_ERROR_INVALID_VALUE; @@ -118,6 +119,7 @@ int addTicks(RtcTick* dst, const RtcTick* src, std::int64_t count, std::int64_t int addMonths(RtcTick* dst, const RtcTick* src, std::int64_t months) { if (!dst || !src) return SCE_RTC_ERROR_INVALID_POINTER; + if (src->tick > MAX_TICK) return SCE_RTC_ERROR_INVALID_VALUE; RtcDateTime time = fromTick(src->tick); const std::int64_t monthIndex = time.year * 12 + (time.month - 1) + months; const std::int64_t year = monthIndex / 12; diff --git a/core/libs/tests/GuestRtc.cpp b/core/libs/tests/GuestRtc.cpp index 23d203f06..fca1f2688 100644 --- a/core/libs/tests/GuestRtc.cpp +++ b/core/libs/tests/GuestRtc.cpp @@ -4,6 +4,7 @@ #include #include #include +#include extern "C" { int APS5_VABI sceRtcCheckValid(const RtcDateTime*); @@ -168,6 +169,38 @@ int main() { Require(sceRtcTickAddTicks(&result, &source, 1) == invalidValue); Require(sceRtcTickAddTicks(nullptr, &source, 1) == invalidPointer); + const std::uint64_t invalidTicks[] = {maxTick + 1, std::numeric_limits::max()}; + for (std::uint64_t invalidTick : invalidTicks) { + source.tick = invalidTick; + result.tick = 123; + Require(sceRtcTickAddTicks(&result, &source, 0) == invalidValue && result.tick == 123); + Require(sceRtcTickAddTicks(&result, &source, 1) == invalidValue && result.tick == 123); + Require(sceRtcTickAddTicks(&result, &source, -1) == invalidValue && result.tick == 123); + Require(sceRtcTickAddSeconds(&result, &source, 0) == invalidValue && result.tick == 123); + Require(sceRtcTickAddDays(&result, &source, -1) == invalidValue && result.tick == 123); + Require(sceRtcTickAddMonths(&result, &source, 0) == invalidValue && result.tick == 123); + Require(sceRtcTickAddYears(&result, &source, -1) == invalidValue && result.tick == 123); + Require(sceRtcTickAddTicks(&source, &source, 0) == invalidValue && source.tick == invalidTick); + Require(sceRtcTickAddMonths(&source, &source, -1) == invalidValue && source.tick == invalidTick); + Require(sceRtcTickAddTicks(nullptr, &source, 0) == invalidPointer); + Require(sceRtcTickAddMonths(nullptr, &source, 0) == invalidPointer); + } + + source.tick = maxTick; + Require(sceRtcTickAddTicks(&source, &source, 0) == 0 && source.tick == maxTick); + Require(sceRtcTickAddTicks(&source, &source, -1) == 0 && source.tick == maxTick - 1); + source.tick = maxTick; + Require(sceRtcTickAddMonths(&source, &source, 0) == 0 && source.tick == maxTick); + Require(sceRtcTickAddYears(&result, &source, -1) == 0); + Require(sceRtcSetTick(&converted, &result) == 0 && Equal(converted, RtcDateTime{9998, 12, 31, 23, 59, 59, 999999})); + result.tick = 123; + Require(sceRtcTickAddMonths(&result, &source, 1) == invalidValue && result.tick == 123); + Require(sceRtcTickAddTicks(&result, &source, std::numeric_limits::min()) == invalidValue && result.tick == 123); + Require(sceRtcTickAddSeconds(&result, &source, std::numeric_limits::max()) == invalidValue && result.tick == 123); + source.tick = 0; + Require(sceRtcTickAddMonths(&source, &source, 0) == 0 && source.tick == 0); + Require(sceRtcTickAddMonths(&result, &source, -1) == invalidValue && result.tick == 123); + RtcTick now{}; Require(sceRtcGetCurrentTick(&now) == 0 && now.tick > leapDayTick); RtcTick local{}; From c6df07e6040b15fe837db6635b34517c20875e5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 05:16:28 +0200 Subject: [PATCH 083/164] feat(shader): accept clamp on v_div_fmas/fixup and omod on their f64 forms Co-Authored-By: Claude --- .../execution/DivisionResultModifiers.cpp | 206 ++++++++++++++++++ .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 9 +- .../Translation/TranslationContext.hpp | 1 + .../Translation/src/Float64Instructions.cpp | 19 +- 4 files changed, 230 insertions(+), 5 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/DivisionResultModifiers.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DivisionResultModifiers.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DivisionResultModifiers.cpp new file mode 100644 index 000000000..d6ac16fc2 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DivisionResultModifiers.cpp @@ -0,0 +1,206 @@ +#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 +#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, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x36100e81, 0x7d8a1080, 0xd56f800a, + 0x041a0b04, 0xd55f800b, 0x041a0b04, 0xd55f820c, 0x241a0b04, 0xd56f800d, 0x441a0905, 0xd570800e, + 0x04120d04, 0xd5700010, 0x0c120d06, 0xd5608012, 0x04120d04, 0xd5600014, 0x1c1a0906, 0xd5608016, + 0x141a0906, 0xd5608018, 0x24120d04, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, + 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x32780000u, 0x7d2e1601u}, + {0x00000000u, 0x00800000u, 0x3ec00000u, 0x9ffbf25eu}, + {0x00000000u, 0x3e8e7000u, 0x8800759cu, 0x7e37e43cu}, + {0x00800000u, 0x40d00000u, 0xca667aa1u, 0x0f7a9d5bu}, + {0x32e94401u, 0x5f800000u, 0x3f800000u, 0xded37902u}, + {0x00000000u, 0x80000000u, 0x3f800000u, 0x0406ef86u}, + {0x00000000u, 0xfff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff00000u, 0xc2f8f359u, 0x81a56e1fu}, + {0x3f800000u, 0x2232f1b4u, 0x7f7fffffu, 0x998ad4a5u}, + {0x40000000u, 0xb6e038d3u, 0xf4787e84u, 0x37112fe1u}, + {0x40780000u, 0xbf800000u, 0x32680000u, 0x1bdea0a2u}, + {0x3f800000u, 0x5f800000u, 0xbeb80000u, 0xc009870du}, + {0x7f800000u, 0xff780000u, 0xc0900000u, 0xde1756c4u}, + {0x00000000u, 0x3ff00000u, 0x00000000u, 0x00100000u}, + {0x00000000u, 0x3ff00000u, 0x00000000u, 0xbff00000u}, + {0x3f680000u, 0x80f80000u, 0x80800000u, 0x9f8fe465u}, + {0x1f800000u, 0xc0200000u, 0xbf280000u, 0xb1fee08fu}, + {0x41e3bd75u, 0x7f800000u, 0xbf800000u, 0xece56e40u}, + {0xbef80000u, 0x40000000u, 0x40400000u, 0xfc972e59u}, + {0x00000000u, 0x001e1000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0x3c400000u, 0xbf800000u, 0xc8c6df8eu}, + {0x00000000u, 0xbff00000u, 0x00000000u, 0x00000000u}, + {0x47e2cc36u, 0x00800000u, 0x00000000u, 0x66263f9fu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x7ff80000u}, + {0x00000000u, 0x00000000u, 0xc2f8f359u, 0x81a56e1fu}, + {0x00000000u, 0x3f400000u, 0x80000000u, 0xa8333fe6u}, + {0x00000000u, 0x40080000u, 0xffffffffu, 0x7fefffffu}, + {0x00000000u, 0x7ff80000u, 0xc2f8f359u, 0x81a56e1fu}, + {0x00000000u, 0xff7fffffu, 0x80000000u, 0xd6ee400au}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00100000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x06420000u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x12780000u, 0x3f800000u, 0x3f800000u, 0x12780000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u}, + {0x3ec00000u, 0x00000000u, 0x00000000u, 0x3ec00000u, 0x00000000u, 0x00800000u, 0x2abc6ea9u, 0x00801868u, 0x00000000u, 0x00000000u, 0x3ec00000u, 0x9ffbf25eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x3e8e7000u, 0x8800759cu, 0x7e37e43cu, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3e8e7000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00800000u, 0x48d00000u, 0x00000000u, 0x3ff00000u, 0xca667aa1u, 0x0f7a9d5bu, 0xca667aa1u, 0x0f7a9d5bu, 0x00000000u, 0x3ff00000u}, + {0x3f800000u, 0x32e94401u, 0x32e94401u, 0x00000000u, 0x00000000u, 0x00000000u, 0x88d0c0dcu, 0x7db7b2f8u, 0x00000000u, 0x00000000u, 0x3f800000u, 0xded37902u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x1a32f1b4u, 0x3f800000u, 0x1a32f1b4u, 0x00000000u, 0x00000000u, 0x7f7fffffu, 0x998ad4a5u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0xaee038d3u, 0x00000000u, 0x00000000u, 0xf4787e84u, 0xb7112fe1u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x40780000u, 0xbf800000u, 0x00000000u, 0x00000000u, 0x32680000u, 0x9bdea0a2u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x67800000u, 0x00000000u, 0x00000000u, 0xbeb80000u, 0xc009870du, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3ff00000u, 0x7f7ffffeu, 0xff780000u, 0x00000000u, 0x3ff00000u, 0xc0900000u, 0x5e1756c4u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x00100000u, 0x00000000u, 0x00100000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x3f680000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x3f680000u, 0x00f80000u, 0x80800000u, 0x1f8fe465u, 0x80800000u, 0x1f8fe465u, 0x3f680000u, 0x00f80000u}, + {0x00000000u, 0x1f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x1f800000u, 0xc8200000u, 0x00000000u, 0x3ff00000u, 0xbf280000u, 0x31fee08fu, 0xbf280000u, 0x31fee08fu, 0x00000000u, 0x3ff00000u}, + {0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0xbf800000u, 0xece56e40u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x3ef80000u, 0x3ef80000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x40400000u, 0xfc972e59u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x002e1000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x001e1000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x001e1000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x1759d00au, 0x51a0597eu, 0x00000000u, 0x00000000u, 0xbf800000u, 0xc8c6df8eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x63f5b02cu, 0x1eb63f9fu, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x66263f9fu, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f400000u, 0x00000000u, 0x3f400000u, 0x00000000u, 0x00000000u, 0x80000000u, 0xa8333fe6u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x3ff00000u, 0xffffffffu, 0x7fefffffu, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0xff7fffffu, 0x00000000u, 0x3ff00000u, 0x80000000u, 0x56ee400au, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u} +}; +constexpr std::uint32_t Checked[32] = {0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xfff0u, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu}; +constexpr const char* Names[16] = { + "v_div_fmas_f32 v10, v4, v5, v6 clamp", + "v_div_fixup_f32 v11, v4, v5, v6 clamp", + "v_div_fixup_f32 v12, -v4, |v5|, v6 clamp", + "v_div_fmas_f32 v13, v5, -v4, v6 clamp", + "v_div_fmas_f64 v[14:15], v[4:5], v[6:7], v[4:5] clamp lo", + "v_div_fmas_f64 v[14:15], v[4:5], v[6:7], v[4:5] clamp hi", + "v_div_fmas_f64 v[16:17], v[6:7], v[6:7], v[4:5] mul:2 lo", + "v_div_fmas_f64 v[16:17], v[6:7], v[6:7], v[4:5] mul:2 hi", + "v_div_fixup_f64 v[18:19], v[4:5], v[6:7], v[4:5] clamp lo", + "v_div_fixup_f64 v[18:19], v[4:5], v[6:7], v[4:5] clamp hi", + "v_div_fixup_f64 v[20:21], v[6:7], v[4:5], v[6:7] div:2 lo", + "v_div_fixup_f64 v[20:21], v[6:7], v[4:5], v[6:7] div:2 hi", + "v_div_fixup_f64 v[22:23], v[6:7], v[4:5], v[6:7] clamp mul:4 lo", + "v_div_fixup_f64 v[22:23], v[6:7], v[4:5], v[6:7] clamp mul:4 hi", + "v_div_fixup_f64 v[24:25], -v[4:5], v[6:7], v[4:5] clamp lo", + "v_div_fixup_f64 v[24:25], -v[4:5], v[6:7], v[4:5] clamp hi", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("division result modifiers: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +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}, 0u, {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 std::span code(Code); + const auto result = Compile(device, code); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void CheckF32OmodRefused(AgcDriver::VulkanDevice& device) { + for (const std::uint32_t opcode : {0x15fu, 0x16fu}) { + alignas(256) const std::array code{0xd400000au | (opcode << 16u), 0x0c1a0b04u, 0xbf810000u}; + std::string refusal; + try { + static_cast(Compile(device, code)); + } catch (const std::exception& error) { + refusal = error.what(); + } + Require(refusal.find("VOP3 source modifiers are not implemented") != std::string::npos, "VOP3 opcode " + Hex(opcode) + " with omod was not refused"); + } +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + if ((Checked[tid] >> i) & 1u) Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + CheckF32OmodRefused(*device); + std::puts("division result modifiers tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index 3cf02f268..496ed2c39 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -1886,6 +1886,8 @@ bool supportsNativeVop3ResultModifiers(RdnaOpcode opcode) { case RdnaOpcode::VCvtPkrtzF16F32: case RdnaOpcode::VLdexpF32: case RdnaOpcode::VFmaF64: + case RdnaOpcode::VDivFmasF64: + case RdnaOpcode::VDivFixupF64: case RdnaOpcode::VAddF64: case RdnaOpcode::VMulF64: case RdnaOpcode::VMinF64: @@ -1899,8 +1901,13 @@ bool supportsNativeVop3ResultModifiers(RdnaOpcode opcode) { } } +bool isF32DivisionStepOpcode(RdnaOpcode opcode) { + return opcode == RdnaOpcode::VDivFmasF32 || opcode == RdnaOpcode::VDivFixupF32; +} + bool supportsNativeVop3Clamp(RdnaOpcode opcode) { - return supportsNativeVop3ResultModifiers(opcode) || usesInexactClampControl(opcode) || usesSignedSaturateClamp(opcode); + return supportsNativeVop3ResultModifiers(opcode) || usesInexactClampControl(opcode) || usesSignedSaturateClamp(opcode) || + isF32DivisionStepOpcode(opcode); } void checkNativeVop3Modifiers(RdnaOpcode opcode, bool permlane, bool carryInOut, bool scalarDst, std::uint32_t abs, std::uint32_t opSel, std::uint32_t clamp, std::uint32_t omod, std::uint32_t neg) { diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index e6abfdcce..ac35384ee 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -172,6 +172,7 @@ private: bool vMulLegacyF32(const RdnaInstruction& inst, bool accumulate); void emitFloat16ClassCompare(const RdnaInstruction& inst, bool cmpx); bool float64Operation(const RdnaInstruction& inst, IrOpcode opcode); + void writeF64Result(const RdnaOperand& operand, IrValue& value); bool vDivScaleF64(const RdnaInstruction& inst); bool vDivFmasF64(const RdnaInstruction& inst); bool vDivFixupF64(const RdnaInstruction& inst); diff --git a/core/shader/recompiler/Translation/src/Float64Instructions.cpp b/core/shader/recompiler/Translation/src/Float64Instructions.cpp index adcf0b8d2..622c6b41b 100644 --- a/core/shader/recompiler/Translation/src/Float64Instructions.cpp +++ b/core/shader/recompiler/Translation/src/Float64Instructions.cpp @@ -22,13 +22,24 @@ bool TranslationContext::float64Operation(const RdnaInstruction& inst, IrOpcode } const IrType type = IrOpcodeType(opcode); IrValue* result = count == 1u ? &ir.Emit(opcode, type, {args[0]}) : count == 2u ? &ir.Emit(opcode, type, {args[0], args[1]}) : &ir.Emit(opcode, type, {args[0], args[1], args[2]}); + if (type == IrType::U64) { + writeF64Result(inst.destination, *result); + return true; + } RdnaOperand destination = inst.destination; destination.omod = 0u; - if (type == IrType::U64 && destination.clamp) { + writeOperand(destination, result); + return true; +} + +void TranslationContext::writeF64Result(const RdnaOperand& operand, IrValue& value) { + RdnaOperand destination = operand; + destination.omod = 0u; + IrValue* result = &value; + if (destination.clamp) { result = &ir.Emit(IrOpcode::FPSaturate64, IrType::U64, {result}); } writeOperand(destination, result); - return true; } bool TranslationContext::vDivScaleF64(const RdnaInstruction& inst) { @@ -88,7 +99,7 @@ bool TranslationContext::vDivFmasF64(const RdnaInstruction& inst) { IrValue& power = ir.Select(scale.Value(), ir.Select(up.Value(), ir.Constant(128u), ir.Constant(static_cast(-128))), ir.Constant(0u)); IrValue& result = ir.Emit(IrOpcode::FPFmaScale64, IrType::U64, {&readF64(sourceAt(inst, 0u)).Value(), &readF64(sourceAt(inst, 1u)).Value(), &ir.ConstructU64(addend[0].Value(), addend[1].Value()), &power}); - writeOperand(inst.destination, &result); + writeF64Result(inst.destination, result); return true; } @@ -131,7 +142,7 @@ bool TranslationContext::vDivFixupF64(const RdnaInstruction& inst) { choose(IrU1(ir.LogicalOr(ir.LogicalAnd(isZero(denominator).Value(), isZero(numerator).Value()), ir.LogicalAnd(isInf(denominator).Value(), isInf(numerator).Value()))), defaultNan); choose(isNan(denominator), quiet(denominator)); choose(isNan(numerator), quiet(numerator)); - writeOperand(inst.destination, &ir.ConstructU64(result[0].Value(), result[1].Value())); + writeF64Result(inst.destination, ir.ConstructU64(result[0].Value(), result[1].Value())); return true; } From 65c139336e9125ef707e0902bfc9f402d312f553 Mon Sep 17 00:00:00 2001 From: Nicolas Abosch <88494836+nicolasabosch@users.noreply.github.com> Date: Mon, 5 Oct 2026 00:52:22 -0300 Subject: [PATCH 084/164] feat(ult): implement blocking queues and shared data pools --- core/libs/prx/libSceUlt/CMakeLists.txt | 24 +++ core/libs/prx/libSceUlt/Export.cpp | 180 +++++++++++++---- core/libs/prx/libSceUlt/SceUltTypes.hpp | 24 ++- core/libs/tests/GuestUltQueue.cpp | 255 ++++++++++++++++++++++++ docs/dev/TechnicalDebt.md | 2 + 5 files changed, 437 insertions(+), 48 deletions(-) create mode 100644 core/libs/prx/libSceUlt/CMakeLists.txt create mode 100644 core/libs/tests/GuestUltQueue.cpp diff --git a/core/libs/prx/libSceUlt/CMakeLists.txt b/core/libs/prx/libSceUlt/CMakeLists.txt new file mode 100644 index 000000000..2d57d4f69 --- /dev/null +++ b/core/libs/prx/libSceUlt/CMakeLists.txt @@ -0,0 +1,24 @@ +add_library(libSceUlt SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceUlt PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceUlt) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_ult_queue_tests ${LIBS_INCLUDE_DIR}/tests/GuestUltQueue.cpp) + target_include_directories(guest_ult_queue_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_ult_queue_tests PRIVATE libSceUlt libc) + configure_windows_unwind(guest_ult_queue_tests) + foreach(testCase IN ITEMS validation fifo blocking shared-pool finalize stress) + add_test(NAME guest_ult_queue_${testCase} COMMAND guest_ult_queue_tests ${testCase}) + set_tests_properties(guest_ult_queue_${testCase} PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_ult_queue_${testCase} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() +endif() diff --git a/core/libs/prx/libSceUlt/Export.cpp b/core/libs/prx/libSceUlt/Export.cpp index ce49fcc2c..a224d3c35 100644 --- a/core/libs/prx/libSceUlt/Export.cpp +++ b/core/libs/prx/libSceUlt/Export.cpp @@ -3,9 +3,10 @@ #include #include #include -#include +#include #include #include +#include #include #include @@ -23,7 +24,7 @@ std::mutex gMutex; std::unordered_map> gMutexes; std::unordered_map> gSemaphores; std::unordered_map gResourcePools; -std::unordered_map gQueueDataPools; +std::unordered_map> gQueueDataPools; std::unordered_map> gQueues; std::unordered_map gRuntimes; std::unordered_map> gUlthreads; @@ -52,6 +53,59 @@ int queueGetState(void* queue, std::shared_ptr* out) { return ULT_OK; } +int QueueTransfer(void* queue, void* data, const void* source, bool push, bool blocking) { + std::shared_ptr state; + if (int result = queueGetState(queue, &state); result != ULT_OK) return result; + if ((push ? source : data) == nullptr) return ULT_ERROR_NULL; + auto& pool = *state->_pool; + std::unique_lock lock(pool._mutex); + if (!state->_alive) return ULT_ERROR_STATE; + const auto ready = [&] { return !state->_alive || (push ? pool._free : state->_head) != UINT32_MAX; }; + if (!ready()) { + if (!blocking) return ULT_ERROR_AGAIN; + ++state->_waiters; + try { + (push ? pool._spaceAvailable : state->_dataAvailable).wait(lock, ready); + } catch (...) { + --state->_waiters; + throw; + } + --state->_waiters; + if (!state->_alive) return ULT_ERROR_STATE; + } + if (push) { + const auto slot = pool._free; + std::memcpy(pool._data.data() + slot * pool._dataSize, source, state->_dataSize); + pool._free = pool._next[slot]; + pool._next[slot] = UINT32_MAX; + if (state->_tail == UINT32_MAX) state->_head = slot; + else pool._next[state->_tail] = slot; + state->_tail = slot; + } else { + const auto slot = state->_head; + std::memcpy(data, pool._data.data() + slot * pool._dataSize, state->_dataSize); + state->_head = pool._next[slot]; + if (state->_head == UINT32_MAX) state->_tail = UINT32_MAX; + pool._next[slot] = pool._free; + pool._free = slot; + } + lock.unlock(); + if (push) state->_dataAvailable.notify_one(); + else pool._spaceAvailable.notify_one(); + return ULT_OK; +} + +void ReleaseQueue(UltQueueState& state) { + auto& pool = *state._pool; + if (state._tail != UINT32_MAX) { + pool._next[state._tail] = pool._free; + pool._free = state._head; + } + state._head = state._tail = UINT32_MAX; + state._alive = false; + --pool._queues; +} + void* APS5_VABI ulthreadRunner(void* arg) { auto* state = static_cast(arg); return reinterpret_cast(static_cast(state->_entry(state->_arg))); @@ -79,8 +133,15 @@ int APS5_VABI sceUltFinalize() { gSemaphores.clear(); gMutexes.clear(); gResourcePools.clear(); - gQueueDataPools.clear(); + for (auto& entry : gQueues) { + auto& state = *entry.second; + std::lock_guard stateLock(state._pool->_mutex); + ReleaseQueue(state); + state._dataAvailable.notify_all(); + state._pool->_spaceAvailable.notify_all(); + } gQueues.clear(); + gQueueDataPools.clear(); gRuntimes.clear(); gUlthreads.clear(); } @@ -162,38 +223,71 @@ int APS5_VABI sceUltWaitingQueueResourcePoolCreate(void* pool, const char* name, } std::uint64_t APS5_VABI sceUltQueueDataResourcePoolGetWorkAreaSize(std::uint32_t numData, std::uint64_t dataSize, std::uint32_t numQueueObject) { - const std::uint64_t dataArea = static_cast(numData) * alignUp(dataSize, 8u); - const std::uint64_t queueArea = static_cast(numQueueObject) * 512u; + if (dataSize > UINT64_MAX - 7u) throw std::out_of_range("ULT queue data size overflow"); + const auto stride = alignUp(dataSize, 8u); + const auto queueArea = static_cast(numQueueObject) * 512u; + if (stride != 0 && numData > (UINT64_MAX - queueArea) / stride) { + throw std::out_of_range("ULT queue work area size overflow"); + } + const std::uint64_t dataArea = static_cast(numData) * stride; return alignUp(dataArea + queueArea, 8u); } int APS5_VABI sceUltQueueDataResourcePoolCreate(void* pool, const char* name, std::uint32_t numData, std::uint64_t dataSize, std::uint32_t numQueueObject, void* waitingQueueResourcePool, void* workArea, const void* optParam, std::uint32_t buildVersion) { (void)name; - (void)optParam; (void)buildVersion; if (pool == nullptr) { return ULT_ERROR_NULL; } + if ((reinterpret_cast(pool) & 7u) != 0) return ULT_ERROR_ALIGNMENT; + if (numData == 0 || numQueueObject == 0 || dataSize == 0 || + dataSize > std::numeric_limits::max() / numData) return ULT_ERROR_RANGE; + if (optParam != nullptr) NotImplemented_nid_no_patch("sceUltQueueDataResourcePoolCreate: optParam"); std::lock_guard lock(gMutex); if (waitingQueueResourcePool != nullptr && gResourcePools.find(waitingQueueResourcePool) == gResourcePools.end()) { return ULT_ERROR_INVALID; } + if (gQueueDataPools.contains(pool)) return ULT_ERROR_STATE; + auto state = std::make_shared(); + state->_numData = numData; + state->_dataSize = dataSize; + state->_numQueueObject = numQueueObject; + state->_waitingPool = waitingQueueResourcePool; + state->_workArea = workArea; + state->_data.resize(numData * dataSize); + state->_next.resize(numData); + for (std::uint32_t slot = 0; slot < numData; ++slot) { + state->_next[slot] = slot + 1u < numData ? slot + 1u : UINT32_MAX; + } + state->_free = 0; + gQueueDataPools.emplace(pool, std::move(state)); std::memset(pool, 0, 512); - gQueueDataPools[pool] = {numData, dataSize, numQueueObject, waitingQueueResourcePool, workArea}; + return ULT_OK; +} + +int APS5_VABI sceUltQueueDataResourcePoolDestroy(void* pool) { + if (pool == nullptr) return ULT_ERROR_NULL; + std::lock_guard lock(gMutex); + const auto it = gQueueDataPools.find(pool); + if (it == gQueueDataPools.end()) return ULT_ERROR_STATE; + auto state = it->second; + std::lock_guard stateLock(state->_mutex); + if (state->_queues != 0) return ULT_ERROR_BUSY; + gQueueDataPools.erase(it); return ULT_OK; } int APS5_VABI sceUltQueueCreate(void* queue, const char* name, std::uint64_t dataSize, void* waitingQueueResourcePool, void* queueDataResourcePool, const void* optParam, std::uint32_t buildVersion) { (void)name; - (void)optParam; (void)buildVersion; if (queue == nullptr) { return ULT_ERROR_NULL; } + if ((reinterpret_cast(queue) & 7u) != 0) return ULT_ERROR_ALIGNMENT; + if (dataSize == 0) return ULT_ERROR_RANGE; + if (optParam != nullptr) NotImplemented_nid_no_patch("sceUltQueueCreate: optParam"); auto state = std::make_shared(); state->_dataSize = dataSize; - state->_waitingPool = waitingQueueResourcePool; - state->_dataPool = queueDataResourcePool; std::lock_guard lock(gMutex); auto dataPoolIt = gQueueDataPools.find(queueDataResourcePool); if (dataPoolIt == gQueueDataPools.end()) { @@ -202,48 +296,48 @@ int APS5_VABI sceUltQueueCreate(void* queue, const char* name, std::uint64_t dat if (waitingQueueResourcePool != nullptr && gResourcePools.find(waitingQueueResourcePool) == gResourcePools.end()) { return ULT_ERROR_INVALID; } - state->_capacity = dataPoolIt->second._numData; + if (gQueues.contains(queue)) return ULT_ERROR_STATE; + state->_pool = dataPoolIt->second; + auto& pool = *state->_pool; + std::lock_guard stateLock(pool._mutex); + if (dataSize > pool._dataSize || waitingQueueResourcePool != pool._waitingPool) return ULT_ERROR_INVALID; + if (pool._queues == pool._numQueueObject) return ULT_ERROR_AGAIN; + gQueues.emplace(queue, std::move(state)); + ++pool._queues; std::memset(queue, 0, 512); - gQueues[queue] = std::move(state); return ULT_OK; } int APS5_VABI sceUltQueuePush(void* queue, const void* data) { - std::shared_ptr state; - if (int ret = queueGetState(queue, &state); ret != ULT_OK) { - return ret; - } - if (data == nullptr && state->_dataSize != 0) { - return ULT_ERROR_NULL; - } - std::lock_guard lock(state->_mutex); - if (state->_capacity != 0 && state->_items.size() >= state->_capacity) { - return ULT_OK; - } - auto& item = state->_items.emplace_back(static_cast(state->_dataSize)); - if (!item.empty()) { - std::memcpy(item.data(), data, item.size()); - } - return ULT_OK; + return QueueTransfer(queue, nullptr, data, true, true); +} + +int APS5_VABI sceUltQueueTryPush(void* queue, const void* data) { + return QueueTransfer(queue, nullptr, data, true, false); +} + +int APS5_VABI sceUltQueuePop(void* queue, void* data) { + return QueueTransfer(queue, data, nullptr, false, true); } int APS5_VABI sceUltQueueTryPop(void* queue, void* data) { - std::shared_ptr state; - if (int ret = queueGetState(queue, &state); ret != ULT_OK) { - return ret; - } - if (data == nullptr && state->_dataSize != 0) { - return ULT_ERROR_NULL; - } - std::lock_guard lock(state->_mutex); - if (state->_items.empty()) { - return ULT_ERROR_AGAIN; - } - auto item = std::move(state->_items.front()); - state->_items.pop_front(); - if (!item.empty()) { - std::memcpy(data, item.data(), item.size()); + return QueueTransfer(queue, data, nullptr, false, false); +} + +int APS5_VABI sceUltQueueDestroy(void* queue) { + if (queue == nullptr) return ULT_ERROR_NULL; + std::lock_guard lock(gMutex); + const auto it = gQueues.find(queue); + if (it == gQueues.end()) return ULT_ERROR_STATE; + auto state = it->second; + auto& pool = *state->_pool; + { + std::lock_guard stateLock(pool._mutex); + if (state->_waiters != 0) return ULT_ERROR_BUSY; + ReleaseQueue(*state); + gQueues.erase(it); } + pool._spaceAvailable.notify_all(); return ULT_OK; } diff --git a/core/libs/prx/libSceUlt/SceUltTypes.hpp b/core/libs/prx/libSceUlt/SceUltTypes.hpp index d37181ef8..1a8a0c8d5 100644 --- a/core/libs/prx/libSceUlt/SceUltTypes.hpp +++ b/core/libs/prx/libSceUlt/SceUltTypes.hpp @@ -1,6 +1,13 @@ #ifndef CORE_LIBS_PRX_LIBSCEULT_SCEULTTYPES_HPP #define CORE_LIBS_PRX_LIBSCEULT_SCEULTTYPES_HPP +#include +#include +#include +#include +#include +#include "SceTypes.hpp" + constexpr int ULT_OK = 0; constexpr int ULT_ERROR_NULL = -2139029503; constexpr int ULT_ERROR_ALIGNMENT = -2139029502; @@ -30,6 +37,12 @@ struct UltResourcePoolState { }; struct UltQueueDataPoolState { + std::mutex _mutex; + std::condition_variable _spaceAvailable; + std::vector _data; + std::vector _next; + std::uint32_t _free = UINT32_MAX; + std::uint32_t _queues = 0; std::uint32_t _numData = 0; std::uint64_t _dataSize = 0; std::uint32_t _numQueueObject = 0; @@ -38,12 +51,13 @@ struct UltQueueDataPoolState { }; struct UltQueueState { - std::mutex _mutex; - std::deque> _items; + std::shared_ptr _pool; + std::condition_variable _dataAvailable; + std::uint32_t _head = UINT32_MAX; + std::uint32_t _tail = UINT32_MAX; + std::uint32_t _waiters = 0; + bool _alive = true; std::uint64_t _dataSize = 0; - std::uint32_t _capacity = 0; - void* _waitingPool = nullptr; - void* _dataPool = nullptr; }; struct UltRuntimeState { diff --git a/core/libs/tests/GuestUltQueue.cpp b/core/libs/tests/GuestUltQueue.cpp new file mode 100644 index 000000000..1ab7d5e97 --- /dev/null +++ b/core/libs/tests/GuestUltQueue.cpp @@ -0,0 +1,255 @@ +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceUltInitialize(); +int APS5_VABI sceUltFinalize(); +std::uint64_t APS5_VABI sceUltQueueDataResourcePoolGetWorkAreaSize(std::uint32_t, std::uint64_t, std::uint32_t); +int APS5_VABI sceUltQueueDataResourcePoolCreate(void*, const char*, std::uint32_t, std::uint64_t, std::uint32_t, void*, void*, const void*, std::uint32_t); +int APS5_VABI sceUltQueueDataResourcePoolDestroy(void*); +int APS5_VABI sceUltQueueCreate(void*, const char*, std::uint64_t, void*, void*, const void*, std::uint32_t); +int APS5_VABI sceUltQueueDestroy(void*); +int APS5_VABI sceUltQueuePush(void*, const void*); +int APS5_VABI sceUltQueueTryPush(void*, const void*); +int APS5_VABI sceUltQueuePop(void*, void*); +int APS5_VABI sceUltQueueTryPop(void*, void*); +} + +static constexpr int Null = -2139029503; +static constexpr int Alignment = -2139029502; +static constexpr int Range = -2139029501; +static constexpr int Invalid = -2139029500; +static constexpr int State = -2139029498; +static constexpr int Busy = -2139029497; +static constexpr int Again = -2139029496; + +static void Require(bool value, int line) { + if (!value) { + std::fprintf(stderr, "ULT queue check failed at line %d\n", line); + std::abort(); + } +} + +#define CHECK(value) Require((value), __LINE__) + +struct alignas(8) Object { std::array bytes{}; }; + +struct Fixture { + Object pool; + Object first; + Object second; + + void Create(std::uint32_t slots, std::uint32_t queues = 1, std::uint64_t size = sizeof(std::uint64_t)) { + CHECK(sceUltQueueDataResourcePoolCreate(&pool, "pool", slots, size, queues, nullptr, nullptr, nullptr, 0) == 0); + CHECK(sceUltQueueCreate(&first, "first", size, nullptr, &pool, nullptr, 0) == 0); + if (queues > 1) CHECK(sceUltQueueCreate(&second, "second", size, nullptr, &pool, nullptr, 0) == 0); + } + + void Destroy(bool secondQueue = false) { + CHECK(sceUltQueueDestroy(&first) == 0); + if (secondQueue) CHECK(sceUltQueueDestroy(&second) == 0); + CHECK(sceUltQueueDataResourcePoolDestroy(&pool) == 0); + } +}; + +static void Validation() { + Fixture fixture; + Object extra; + std::uint64_t value = 99; + CHECK(sceUltQueueDataResourcePoolCreate(nullptr, nullptr, 1, 8, 1, nullptr, nullptr, nullptr, 0) == Null); + CHECK(sceUltQueueDataResourcePoolCreate(fixture.pool.bytes.data() + 1, nullptr, 1, 8, 1, nullptr, nullptr, nullptr, 0) == Alignment); + CHECK(sceUltQueueDataResourcePoolCreate(&fixture.pool, nullptr, 0, 8, 1, nullptr, nullptr, nullptr, 0) == Range); + CHECK(sceUltQueueDataResourcePoolCreate(&fixture.pool, nullptr, 1, 0, 1, nullptr, nullptr, nullptr, 0) == Range); + CHECK(sceUltQueueDataResourcePoolCreate(&fixture.pool, nullptr, 1, 8, 0, nullptr, nullptr, nullptr, 0) == Range); + CHECK(sceUltQueueDataResourcePoolCreate(&fixture.pool, nullptr, 2, UINT64_MAX, 1, nullptr, nullptr, nullptr, 0) == Range); + CHECK(sceUltQueueDataResourcePoolCreate(&fixture.pool, nullptr, 1, 8, 1, &extra, nullptr, nullptr, 0) == Invalid); + CHECK(sceUltQueueCreate(&fixture.first, nullptr, 8, nullptr, &fixture.pool, nullptr, 0) == Invalid); + fixture.Create(2); + CHECK(sceUltQueuePush(&fixture.first, &value) == 0); + CHECK(sceUltQueueDataResourcePoolCreate(&fixture.pool, nullptr, 1, 8, 1, nullptr, nullptr, nullptr, 0) == State); + CHECK(sceUltQueueCreate(&fixture.first, nullptr, 8, nullptr, &fixture.pool, nullptr, 0) == State); + std::uint64_t saved = 0; + CHECK(sceUltQueueTryPop(&fixture.first, &saved) == 0 && saved == value); + CHECK(sceUltQueueCreate(&extra, nullptr, 9, nullptr, &fixture.pool, nullptr, 0) == Invalid); + CHECK(sceUltQueueCreate(&extra, nullptr, 8, &extra, &fixture.pool, nullptr, 0) == Invalid); + CHECK(sceUltQueueCreate(&extra, nullptr, 8, nullptr, &fixture.pool, nullptr, 0) == Again); + CHECK(sceUltQueueCreate(nullptr, nullptr, 8, nullptr, &fixture.pool, nullptr, 0) == Null); + CHECK(sceUltQueueCreate(extra.bytes.data() + 1, nullptr, 8, nullptr, &fixture.pool, nullptr, 0) == Alignment); + CHECK(sceUltQueueCreate(&extra, nullptr, 0, nullptr, &fixture.pool, nullptr, 0) == Range); + CHECK(sceUltQueueTryPop(&fixture.first, &value) == Again && value == 99); + CHECK(sceUltQueueTryPush(&fixture.first, nullptr) == Null); + CHECK(sceUltQueuePop(&fixture.first, nullptr) == Null); + CHECK(sceUltQueuePush(nullptr, &value) == Null); + CHECK(sceUltQueuePop(&extra, &value) == State); + CHECK(sceUltQueueDataResourcePoolDestroy(&fixture.pool) == Busy); + CHECK(sceUltQueueDataResourcePoolDestroy(nullptr) == Null); + CHECK(sceUltQueueDestroy(nullptr) == Null); + CHECK(sceUltQueueDataResourcePoolGetWorkAreaSize(3, 9, 2) == 1072); + bool overflow = false; + try { sceUltQueueDataResourcePoolGetWorkAreaSize(1, UINT64_MAX, 1); } + catch (const std::out_of_range&) { overflow = true; } + CHECK(overflow); + bool rejected = false; + try { sceUltQueueCreate(&extra, nullptr, 8, nullptr, &fixture.pool, &extra, 0); } + catch (const std::runtime_error&) { rejected = true; } + CHECK(rejected); + rejected = false; + try { sceUltQueueDataResourcePoolCreate(&extra, nullptr, 1, 8, 1, nullptr, nullptr, &extra, 0); } + catch (const std::runtime_error&) { rejected = true; } + CHECK(rejected); + overflow = false; + try { sceUltQueueDataResourcePoolGetWorkAreaSize(UINT32_MAX, UINT64_MAX / 2, 1); } + catch (const std::out_of_range&) { overflow = true; } + CHECK(overflow); + fixture.Destroy(); + CHECK(sceUltQueueTryPush(&fixture.first, &value) == State); + CHECK(sceUltQueueDestroy(&fixture.first) == State); + CHECK(sceUltQueueDataResourcePoolDestroy(&fixture.pool) == State); + fixture.Create(1); + fixture.Destroy(); +} + +static void Fifo() { + Fixture fixture; + fixture.Create(3, 1, 13); + std::array, 4> items{}; + for (std::size_t index = 0; index < items.size(); ++index) { + for (std::size_t byte = 0; byte < items[index].size(); ++byte) items[index][byte] = index * 31 + byte; + } + for (int round = 0; round < 100; ++round) { + for (int index = 0; index < 3; ++index) CHECK(sceUltQueueTryPush(&fixture.first, items[index].data()) == 0); + CHECK(sceUltQueueTryPush(&fixture.first, items[3].data()) == Again); + std::array output; + for (int index = 0; index < 3; ++index) { + output.fill(0xAA); + CHECK(sceUltQueuePop(&fixture.first, output.data() + 1) == 0); + CHECK(output.front() == 0xAA && output.back() == 0xAA); + CHECK(std::memcmp(output.data() + 1, items[index].data(), 13) == 0); + } + CHECK(sceUltQueueTryPop(&fixture.first, output.data()) == Again); + } + fixture.Destroy(); +} + +static void WaitUntilBlocked(std::future& result) { + CHECK(result.wait_for(std::chrono::milliseconds(100)) == std::future_status::timeout); +} + +static void Blocking() { + Fixture fixture; + fixture.Create(1); + std::uint64_t first = 11, second = 22, output = 0; + CHECK(sceUltQueuePush(&fixture.first, &first) == 0); + auto producer = std::async(std::launch::async, [&] { return sceUltQueuePush(&fixture.first, &second); }); + WaitUntilBlocked(producer); + CHECK(sceUltQueueDestroy(&fixture.first) == Busy); + CHECK(sceUltQueuePop(&fixture.first, &output) == 0 && output == first); + CHECK(producer.get() == 0); + CHECK(sceUltQueuePop(&fixture.first, &output) == 0 && output == second); + auto consumer = std::async(std::launch::async, [&] { return sceUltQueuePop(&fixture.first, &output); }); + WaitUntilBlocked(consumer); + CHECK(sceUltQueueDestroy(&fixture.first) == Busy); + CHECK(sceUltQueuePush(&fixture.first, &first) == 0); + CHECK(consumer.get() == 0 && output == first); + fixture.Destroy(); +} + +static void SharedPool() { + Fixture fixture; + fixture.Create(1, 2); + std::uint64_t first = 111, second = 222, output = 0; + auto consumer = std::async(std::launch::async, [&] { return sceUltQueuePop(&fixture.second, &output); }); + WaitUntilBlocked(consumer); + CHECK(sceUltQueuePush(&fixture.first, &first) == 0); + WaitUntilBlocked(consumer); + CHECK(sceUltQueueTryPush(&fixture.second, &second) == Again); + CHECK(sceUltQueueTryPop(&fixture.second, &output) == Again); + auto producer = std::async(std::launch::async, [&] { return sceUltQueuePush(&fixture.second, &second); }); + WaitUntilBlocked(producer); + CHECK(sceUltQueueDestroy(&fixture.second) == Busy); + CHECK(sceUltQueueDestroy(&fixture.first) == 0); + CHECK(producer.get() == 0); + CHECK(consumer.get() == 0 && output == second); + CHECK(sceUltQueueCreate(&fixture.first, "replacement", 4, nullptr, &fixture.pool, nullptr, 0) == 0); + const std::uint32_t small = 42; + CHECK(sceUltQueuePush(&fixture.first, &small) == 0); + std::uint32_t smallOutput = 0; + CHECK(sceUltQueuePop(&fixture.first, &smallOutput) == 0 && smallOutput == small); + fixture.Destroy(true); +} + +static void Finalize() { + Fixture fixture; + fixture.Create(1, 2); + std::uint64_t value = 7, output = 123; + CHECK(sceUltQueuePush(&fixture.first, &value) == 0); + auto producer = std::async(std::launch::async, [&] { return sceUltQueuePush(&fixture.first, &value); }); + auto consumer = std::async(std::launch::async, [&] { return sceUltQueuePop(&fixture.second, &output); }); + WaitUntilBlocked(producer); + WaitUntilBlocked(consumer); + CHECK(sceUltQueueDestroy(&fixture.first) == Busy); + CHECK(sceUltQueueDestroy(&fixture.second) == Busy); + CHECK(sceUltFinalize() == 0); + CHECK(producer.get() == State); + CHECK(consumer.get() == State && output == 123); + CHECK(sceUltQueueTryPop(&fixture.first, &output) == State); + CHECK(sceUltInitialize() == 0); + fixture.Create(1, 2); + CHECK(sceUltQueueTryPop(&fixture.first, &output) == Again); + CHECK(sceUltQueuePush(&fixture.second, &value) == 0); + CHECK(sceUltQueuePop(&fixture.second, &output) == 0 && output == value); + fixture.Destroy(true); +} + +static void Stress() { + Fixture fixture; + fixture.Create(7); + constexpr std::size_t workers = 4; + constexpr std::size_t perWorker = 2000; + std::array, workers * perWorker> seen{}; + std::vector producers, consumers; + for (std::size_t worker = 0; worker < workers; ++worker) { + consumers.emplace_back([&] { + for (std::size_t index = 0; index < perWorker; ++index) { + std::uint64_t value = UINT64_MAX; + CHECK(sceUltQueuePop(&fixture.first, &value) == 0); + CHECK(value < seen.size()); + ++seen[value]; + } + }); + producers.emplace_back([&, worker] { + for (std::size_t index = 0; index < perWorker; ++index) { + const std::uint64_t value = worker * perWorker + index; + CHECK(sceUltQueuePush(&fixture.first, &value) == 0); + } + }); + } + for (auto& producer : producers) producer.join(); + for (auto& consumer : consumers) consumer.join(); + for (const auto& count : seen) CHECK(count == 1); + std::uint64_t output = 0; + CHECK(sceUltQueueTryPop(&fixture.first, &output) == Again); + fixture.Destroy(); +} + +int main(int argc, char** argv) { + CHECK(argc == 2); + CHECK(sceUltInitialize() == 0); + if (std::strcmp(argv[1], "validation") == 0) Validation(); + else if (std::strcmp(argv[1], "fifo") == 0) Fifo(); + else if (std::strcmp(argv[1], "blocking") == 0) Blocking(); + else if (std::strcmp(argv[1], "shared-pool") == 0) SharedPool(); + else if (std::strcmp(argv[1], "finalize") == 0) Finalize(); + else if (std::strcmp(argv[1], "stress") == 0) Stress(); + else CHECK(false); + CHECK(sceUltFinalize() == 0); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index e5cefcc19..4eae84edf 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -24,6 +24,7 @@ Throughout the project, every function at every stage either **does exactly what ### Unknown function info +- [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) - the added `sceUltQueuePop`, `sceUltQueueTryPush`, `sceUltQueueDestroy` and `sceUltQueueDataResourcePoolDestroy` signatures are inferred from the existing push/pop and object APIs. Queue destruction is assumed to reject blocked callers with `ULT_ERROR_BUSY` and otherwise discard queued data; pool exhaustion returns `ULT_ERROR_AGAIN`. These rules, alignment checks and shared slot limits have not been confirmed on PS5 hardware or a title. Non-null queue and data-pool option parameters throw because their layouts are unknown. - [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - the open param's first word is unknown (PPSA21564 passes 16; it is not the byte size, since the affinity mask is at offset 16); only 16 is accepted. Whether the param continues past offset 24 is also unknown - [sceRtcParseDateTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - accepted format assumed to be the ISO 8601 subset `YYYY-MM-DDTHH:MM:SS[.ffffff]` (T/t/space separator, optional fractional seconds) with the sibling RFC 3339 zone suffix (`Z` or `±HH:MM`); a string without a zone is taken as UTC, and anything outside this shape throws instead of returning a parse error, unverified against hardware - [AMPR WriteAddressFromCounterPairOnCompletion](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - the pair of an even counter n is assumed to give counter n in the low 32 bits and n + 1 in the high 32 bits. Wait compare values, wait flush and counter index ranges follow the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation; the flush is accepted and has no effect, as commands run in order @@ -73,6 +74,7 @@ Throughout the project, every function at every stage either **does exactly what ### Functional +- [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) use preallocated host storage instead of the supplied guest work area; its existing size formula and the 512-byte object layouts remain unverified. Waiting-queue pool thread limits and waiter priority ordering are not implemented. Finalization wakes blocked queue callers with `ULT_ERROR_STATE`. - The shader recompiler [ignores `s_setreg_b32`](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp): writes to MODE (float rounding and denormal controls) and the other hardware registers have no effect, and `s_getreg_b32` reads of the MODE round fields report round to nearest even even after `s_setreg_b32` wrote another mode. The initial round mode from the shader's program registers is not read either. - [Shader recompilation](../../core/shader/recompiler/Recompiler.cpp) currently occurs right before it was transferred to Vulkan with caching, but should be moved to the [relinker](../../core/relinker/main.cpp) stage. For this purpose, [shader/recompiler](../../core/shader/recompiler) was written completely independently from [libs/prx](../../core/libs/prx). - [v_fma_f64](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp) is fused exactly only for normal inputs: with a subnormal input it is a separate multiply and add, which rounds twice. From 043332ba3274b3900550649e1ea1569b23437fbf Mon Sep 17 00:00:00 2001 From: Nicolas Abosch <88494836+nicolasabosch@users.noreply.github.com> Date: Mon, 5 Oct 2026 00:57:02 -0300 Subject: [PATCH 085/164] docs(ult): avoid overlapping AvPlayer debt note --- docs/dev/TechnicalDebt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 4eae84edf..c6f8f8edc 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -24,7 +24,6 @@ Throughout the project, every function at every stage either **does exactly what ### Unknown function info -- [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) - the added `sceUltQueuePop`, `sceUltQueueTryPush`, `sceUltQueueDestroy` and `sceUltQueueDataResourcePoolDestroy` signatures are inferred from the existing push/pop and object APIs. Queue destruction is assumed to reject blocked callers with `ULT_ERROR_BUSY` and otherwise discard queued data; pool exhaustion returns `ULT_ERROR_AGAIN`. These rules, alignment checks and shared slot limits have not been confirmed on PS5 hardware or a title. Non-null queue and data-pool option parameters throw because their layouts are unknown. - [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - the open param's first word is unknown (PPSA21564 passes 16; it is not the byte size, since the affinity mask is at offset 16); only 16 is accepted. Whether the param continues past offset 24 is also unknown - [sceRtcParseDateTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - accepted format assumed to be the ISO 8601 subset `YYYY-MM-DDTHH:MM:SS[.ffffff]` (T/t/space separator, optional fractional seconds) with the sibling RFC 3339 zone suffix (`Z` or `±HH:MM`); a string without a zone is taken as UTC, and anything outside this shape throws instead of returning a parse error, unverified against hardware - [AMPR WriteAddressFromCounterPairOnCompletion](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - the pair of an even counter n is assumed to give counter n in the low 32 bits and n + 1 in the high 32 bits. Wait compare values, wait flush and counter index ranges follow the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation; the flush is accepted and has no effect, as commands run in order @@ -71,6 +70,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceSystemServiceReenableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature - [sceVoiceSetMuteFlag](../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature - [snwprintf_s](../../core/libs/prx/libc/src/RuntimeSupport.cpp) (libc) - unknown signature +- [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) - the added `sceUltQueuePop`, `sceUltQueueTryPush`, `sceUltQueueDestroy` and `sceUltQueueDataResourcePoolDestroy` signatures are inferred from the existing push/pop and object APIs. Queue destruction is assumed to reject blocked callers with `ULT_ERROR_BUSY` and otherwise discard queued data; pool exhaustion returns `ULT_ERROR_AGAIN`. These rules, alignment checks and shared slot limits have not been confirmed on PS5 hardware or a title. Non-null queue and data-pool option parameters throw because their layouts are unknown. ### Functional From 1c4aadb3e33b4775b213c9eaadc03ab02d931dea Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Sun, 4 Oct 2026 18:03:27 +1300 Subject: [PATCH 086/164] fix(agc): route compacted color exports to the slots CB_SHADER_MASK enables Pixel shader color exports are compacted: export MRTi goes to the i-th MRT slot with a non-zero CB_SHADER_MASK nibble and takes the i-th SPI_SHADER_COL_FORMAT field. Draws still mapped export i to slot i and read slot i's format field, so a shader with CB_SHADER_MASK 0xf000f and SPI_SHADER_COL_FORMAT 0x44 (export 1 to slot 4) was refused with "color export format 0". Each color target now carries its export index next to its slot. Attachment references, blend states, export mappings and the pipeline and pass keys follow the export index; the color buffer, blend control and target mask come from the slot. An unwritten export below the highest written one stays an unused attachment. --- .../src/Driver/Draw/DrawRegisterKey.cpp | 2 +- .../Graphics/include/State.hpp | 1 + .../prx/libSceAgcDriver/Graphics/src/Draw.cpp | 4 +-- .../libSceAgcDriver/Graphics/src/Pipeline.cpp | 6 ++-- .../libSceAgcDriver/Graphics/src/State.cpp | 28 ++++++++++------- .../prx/libSceAgcDriver/tests/Graphics.cpp | 31 +++++++++++++++++++ 6 files changed, 55 insertions(+), 17 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp index f4184ee0f..27d567cd6 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp @@ -54,7 +54,7 @@ bool Driver::sameDecode(const DrawDecode& a, const DrawDecode& b) { const auto& s = a.state; const auto& t = b.state; const auto sameColor = [](const Graphics::ColorTarget& x, const Graphics::ColorTarget& y) { - return x.address == y.address && x.extent.width == y.extent.width && x.extent.height == y.extent.height && x.format == y.format && x.bytes == y.bytes && x.componentMapping == y.componentMapping && x.tileMode == y.tileMode && x.elementBytes == y.elementBytes && x.dccAddress == y.dccAddress && x.dccAlphaOnMsb == y.dccAlphaOnMsb && x.slot == y.slot; + return x.address == y.address && x.extent.width == y.extent.width && x.extent.height == y.extent.height && x.format == y.format && x.bytes == y.bytes && x.componentMapping == y.componentMapping && x.tileMode == y.tileMode && x.elementBytes == y.elementBytes && x.dccAddress == y.dccAddress && x.dccAlphaOnMsb == y.dccAlphaOnMsb && x.slot == y.slot && x.exportIndex == y.exportIndex; }; const auto sameBlend = [](const VkPipelineColorBlendAttachmentState& x, const VkPipelineColorBlendAttachmentState& y) { return x.blendEnable == y.blendEnable && x.srcColorBlendFactor == y.srcColorBlendFactor && x.dstColorBlendFactor == y.dstColorBlendFactor && x.colorBlendOp == y.colorBlendOp && x.srcAlphaBlendFactor == y.srcAlphaBlendFactor && x.dstAlphaBlendFactor == y.dstAlphaBlendFactor && x.alphaBlendOp == y.alphaBlendOp && x.colorWriteMask == y.colorWriteMask; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp index a32309b7c..3fa483f13 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp @@ -50,6 +50,7 @@ struct ColorTarget { std::uint32_t slot = 0; std::uint32_t depth = 1; std::uint32_t depthSlice = 0; + std::uint32_t exportIndex = 0; }; struct DepthTarget { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 3ca3ffd1f..75d874fec 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -1312,7 +1312,7 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam for (const auto view : record.targetViews) mix(reinterpret_cast(view)); if (state.blends.size() != state.colors.size()) { mix(state.blends.size()); - for (const auto& color : state.colors) mix(color.slot); + for (const auto& color : state.colors) mix(color.exportIndex); } mix(state.renderExtent.width); mix(state.renderExtent.height); @@ -1558,7 +1558,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& } } timer.phase(PhaseReadTarget); - binding.target = std::make_unique(context, color, state.blends.at(color.slot).blendEnable != 0); + binding.target = std::make_unique(context, color, state.blends.at(color.exportIndex).blendEnable != 0); targetViews.push_back(binding.target->View()); } if (state.depth) targetViews.push_back(DepthSurfaceView(context, *state.depth)); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp index 1a4c8a7a8..3d56df8c9 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp @@ -94,7 +94,7 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput // A cached pipeline may outlive its device's teardown (see ClearCachedPipelines); it must not keep // the buffer pool, which is reset with the device, alive past it. this->context.bufferPool.reset(); - Require(state.blends.size() == (state.colors.empty() ? 0u : state.colors.back().slot + 1u) && state.colors.size() <= state.blends.size(), "blend states do not match decoded color state"); + Require(state.blends.size() == (state.colors.empty() ? 0u : state.colors.back().exportIndex + 1u) && state.colors.size() <= state.blends.size(), "blend states do not match decoded color state"); Require(state.blends.size() <= context.limits.maxColorAttachments, "color targets exceed device attachment limits"); Require(state.hasColorTarget || (context.limits.framebufferNoAttachmentsSampleCounts & VK_SAMPLE_COUNT_1_BIT) != 0, "device does not support single-sample rendering without attachments"); Require(!depthBounds || context.depthBounds, "device does not support the depth bounds test"); @@ -151,7 +151,7 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput color.initialLayout = attachmentLayout; color.finalLayout = attachmentLayout; colors.push_back(color); - references.at(state.colors[index].slot) = {index, attachmentLayout}; + references.at(state.colors[index].exportIndex) = {index, attachmentLayout}; } VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; @@ -402,7 +402,7 @@ std::vector pipelineKey(const Context& context, const State& state, c append(key, state.colors.size()); for (const auto& color : state.colors) append(key, color.format); if (state.blends.size() != state.colors.size()) { - for (const auto& color : state.colors) append(key, color.slot); + for (const auto& color : state.colors) append(key, color.exportIndex); } append(key, state.depth.has_value()); if (state.depth) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index 5214fd141..11884ff47 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -501,13 +501,17 @@ State DecodeState(const QueueState& queue) { // matters where the shader exports. const auto targetMask = read(cx, 0x8e) & shaderMask; APS5_LOG_OUT_DEBUG("CB_TARGET_MASK=0x%x CB_SHADER_MASK=0x%x", targetMask, shaderMask); - std::uint32_t slotCount = 0; + std::vector exportSlots; for (std::uint32_t slot = 0; slot < 8; ++slot) { - if (((targetMask >> (4u * slot)) & 0xfu) != 0) slotCount = slot + 1; + if (((shaderMask >> (4u * slot)) & 0xfu) != 0) exportSlots.push_back(slot); } const auto written = [&](std::uint32_t slot) { return ((targetMask >> (4u * slot)) & 0xfu) != 0; }; - result.hasColorTarget = slotCount != 0; - APS5_LOG_OUT_DEBUG("hasColorTarget=%u slots=%u", result.hasColorTarget ? 1u : 0u, slotCount); + std::uint32_t exportCount = 0; + for (std::uint32_t index = 0; index < exportSlots.size(); ++index) { + if (written(exportSlots[index])) exportCount = index + 1; + } + result.hasColorTarget = exportCount != 0; + APS5_LOG_OUT_DEBUG("hasColorTarget=%u exports=%u", result.hasColorTarget ? 1u : 0u, exportCount); // CB_COLOR_CONTROL mode 0 disables color writes, which only matters when a target is written. if (const auto colorControl = read(cx, 0x202); !colorControlSupported(colorControl, result.hasColorTarget)) throw std::runtime_error(colorControlMessage(colorControl)); @@ -517,12 +521,14 @@ State DecodeState(const QueueState& queue) { // SPI_SHADER_POS_FORMAT: POS0 must be a 4-component position; later vectors carry the misc/clip // exports that PA_CL_VS_OUT_CNTL validation above already limits to ignored layer/viewport data. Require((read(cx, 0x1c3) & 0xfu) == 4, "additional position exports are unsupported"); - for (std::uint32_t slot = 0; slot < slotCount; ++slot) { + for (std::uint32_t index = 0; index < exportCount; ++index) { + const auto slot = exportSlots[index]; if (!written(slot)) continue; // Export formats only matter for the targets the draw writes. - const auto slotExport = (exportFormat >> (4u * slot)) & 0xfu; + const auto slotExport = (exportFormat >> (4u * index)) & 0xfu; if (slotExport == 0 || slotExport == 7 || slotExport == 8 || slotExport > 9) throw std::runtime_error("AGC graphics: color export format " + std::to_string(slotExport) + " is unsupported"); - const auto color = DecodeColorBuffer(cx, slot); + auto color = DecodeColorBuffer(cx, slot); + color.exportIndex = index; APS5_LOG_OUT_DEBUG("Color %u address=0x%llx extent=%ux%u bytes=%llu VkFormat=%u", slot, static_cast(color.address), color.extent.width, color.extent.height, static_cast(color.bytes), static_cast(color.format)); if (result.colors.empty()) { result.renderExtent = color.extent; @@ -566,7 +572,7 @@ State DecodeState(const QueueState& queue) { intersect(result.scissor, cx, 0x90, false); if ((read(cx, 0x292) & 2u) != 0) intersect(result.scissor, cx, 0x94, false); APS5_LOG_OUT_DEBUG("Scissor offset=(%d,%d) extent=%ux%u", result.scissor.offset.x, result.scissor.offset.y, result.scissor.extent.width, result.scissor.extent.height); - result.blends.assign(slotCount, VkPipelineColorBlendAttachmentState{}); + result.blends.assign(exportCount, VkPipelineColorBlendAttachmentState{}); for (const auto& color : result.colors) { const auto slot = color.slot; const auto blend = read(cx, 0x1e0 + slot); @@ -595,9 +601,9 @@ State DecodeState(const QueueState& queue) { } for (std::uint32_t i = 0; i < 4; ++i) result.blendConstants[i] = readFloat(cx, 0x105 + i); } - result.blends[slot] = state; + result.blends[color.exportIndex] = state; } - if (!result.colors.empty()) result.blend = result.blends[result.colors.front().slot]; + if (!result.colors.empty()) result.blend = result.blends[result.colors.front().exportIndex]; APS5_LOG_OUT_DEBUG("DecodeState done colorTarget=%u render=%ux%u topology=%u", result.hasColorTarget ? 1u : 0u, result.renderExtent.width, result.renderExtent.height, static_cast(result.topology)); return result; } @@ -606,7 +612,7 @@ std::array ExportMappings(const State& state) { std::array mappings{}; mappings.fill(0xe4u); for (const auto& color : state.colors) { - if (color.slot < mappings.size()) mappings[color.slot] = color.componentMapping; + if (color.exportIndex < mappings.size()) mappings[color.exportIndex] = color.componentMapping; } return mappings; } diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 249a2a721..f5720c1fc 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -449,6 +449,36 @@ void DisabledColorTests() { expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "missing register"); } +void CompactedExportTests() { + alignas(256) static std::array slotFourMemory{}; + const auto slotFour = reinterpret_cast(slotFourMemory.data()); + auto queue = makeState(); + for (const auto offset : {0x31bu, 0x31cu, 0x31du}) queue.context[offset + 4u * 0xfu] = queue.context.at(offset); + for (const auto offset : {0x3b0u, 0x3b8u}) queue.context[offset + 4u] = queue.context.at(offset); + queue.context[0x318 + 4u * 0xfu] = static_cast(slotFour >> 8u); + queue.context[0x390 + 4u] = static_cast(slotFour >> 40u); + queue.context[0x1e4] = 0x40010001u; + for (std::uint32_t i = 0; i < 4; ++i) queue.context[0x105 + i] = 0; + queue.context[0x8e] = 0x3000fu; + queue.context[0x8f] = 0xf000fu; + queue.context[0x1c5] = 0x44u; + auto state = AgcDriver::Graphics::DecodeState(queue); + Require(state.colors.size() == 2 && state.blends.size() == 2, "two compacted exports did not give two attachments"); + Require(state.colors[0].slot == 0 && state.colors[0].exportIndex == 0 && state.colors[0].address == reinterpret_cast(colorMemory.data()), "export 0 did not reach MRT slot 0"); + Require(state.colors[1].slot == 4 && state.colors[1].exportIndex == 1 && state.colors[1].address == slotFour, "export 1 did not reach MRT slot 4, the second slot CB_SHADER_MASK enables"); + Require(!state.blends[0].blendEnable && state.blends[1].blendEnable && state.blends[1].colorWriteMask == (VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT), "export 1 did not take MRT slot 4's blend control and target mask"); + Require(AgcDriver::Graphics::ExportMappings(state)[1] == state.colors[1].componentMapping, "export 1 did not take MRT slot 4's component mapping"); + queue.context[0x1c5] = 0x90009u; + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "color export format 0"); + queue.context[0x8e] = 0xf000fu; + queue.context[0x8f] = 0xf0f0fu; + queue.context[0x1c5] = 0x999u; + state = AgcDriver::Graphics::DecodeState(queue); + Require(state.colors.size() == 2 && state.blends.size() == 3, "a write-masked export between written ones changed the attachment count"); + Require(state.colors[0].exportIndex == 0 && state.colors[1].slot == 4 && state.colors[1].exportIndex == 2, "export 2 did not reach MRT slot 4, the third slot CB_SHADER_MASK enables"); + Require(state.blends[1].colorWriteMask == 0 && !state.blends[1].blendEnable && state.blends[2].blendEnable, "the write-masked export 1 was not left unused"); +} + void DepthStencilTests() { auto queue = makeState(); queue.context[0x000] = 0; @@ -1860,6 +1890,7 @@ int main() { DepthStencilTests(); DepthBoundsBiasTests(); DisabledColorTests(); + CompactedExportTests(); metadataPassTests(); ShaderStageTests(); PixelInputLayoutTests(); From 93efde8056d6b323935739a00ad2d8428a1073d9 Mon Sep 17 00:00:00 2001 From: Nicolas Abosch <88494836+nicolasabosch@users.noreply.github.com> Date: Mon, 5 Oct 2026 01:50:14 -0300 Subject: [PATCH 087/164] fix(relinker): merge function symbol alias extents --- .../src/analysis/CodeInstructionCollector.cpp | 9 +++++---- .../tests/test_tls_function_coverage.py | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp index a2ba2e2f2..e065dc76e 100644 --- a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp +++ b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp @@ -80,7 +80,7 @@ std::set CodeInstructionCollector::Collect(const std::ve std::set roots; std::map functions; const auto addRoot = [&](std::uint64_t address) { if (isCode(address)) roots.insert(address); }; - const auto addFunction = [&](std::uint64_t begin, std::uint64_t size) { + const auto addFunction = [&](std::uint64_t begin, std::uint64_t size, bool symbolAlias) { if (size == 0) return; if (!isCode(begin) || size > std::numeric_limits::max() - begin) throw Domain::RelinkerException("Code analysis: invalid function range", begin); @@ -95,8 +95,9 @@ std::set CodeInstructionCollector::Collect(const std::ve } if (!mapped) throw Domain::RelinkerException("Code analysis: function is not file-backed", begin); const auto [position, inserted] = functions.emplace(begin, begin + size); - if (!inserted && position->second != begin + size) + if (!inserted && !symbolAlias && position->second != begin + size) throw Domain::RelinkerException("Code analysis: conflicting function ranges", begin); + position->second = std::max(position->second, begin + size); roots.insert(begin); }; range(0, 64); @@ -117,7 +118,7 @@ std::set CodeInstructionCollector::Collect(const std::ve if ((bytes[offset + 4] & 15) == 2 && Io::ReadU16(bytes, offset + 6) != 0) { const auto address = Io::ReadU64(bytes, offset + 8); addRoot(address); - addFunction(address, Io::ReadU64(bytes, offset + 16)); + addFunction(address, Io::ReadU64(bytes, offset + 16), true); } } } @@ -169,7 +170,7 @@ std::set CodeInstructionCollector::Collect(const std::ve } for (const auto& function : UnusedNidFilter::ReadExceptionFunctions(bytes, headers, pointers.Values, importSlots)) { if (function.End <= function.Begin) throw Domain::RelinkerException("Code analysis: invalid unwind function range", function.Begin); - addFunction(function.Begin, function.End - function.Begin); + addFunction(function.Begin, function.End - function.Begin, false); for (const auto target : function.ExtraTargets) addRoot(target); } if (roots.empty()) throw Domain::RelinkerException("Code analysis: no code entry points"); diff --git a/core/relinker/relinker/tests/test_tls_function_coverage.py b/core/relinker/relinker/tests/test_tls_function_coverage.py index 825e7f543..f39c91929 100644 --- a/core/relinker/relinker/tests/test_tls_function_coverage.py +++ b/core/relinker/relinker/tests/test_tls_function_coverage.py @@ -197,6 +197,12 @@ def pe_bytes_at(pe, rva, size): raise AssertionError(f"Unmapped PE RVA {rva:#x}") +def add_alias(image, size): + struct.pack_into(" Date: Mon, 5 Oct 2026 14:09:32 +1300 Subject: [PATCH 088/164] perf(recompiler): drop exec selects whose inactive lanes nobody reads Every VGPR write under EXEC is translated to Select(exec, new, old), so in SSA each register's previous value stays live until the next write, even when the guest only used the register as a temporary. In NGG mesh shaders at 255 registers that keeps dead values alive and the driver spills them to local memory. MaskedSelectEliminator (after SSA, folding and the readlane pass) replaces r = Select(m, new, old) by new when no lane where m is false can observe r: - every value derived from r is either a lane-local pure op or a phi, and their uses are checked the same way; - or the derived value reaches the true operand of a select, or an operand of a LogicalAnd, whose condition implies m: m itself, a LogicalAnd containing m, or a thread bit rebuilt from a ballot of such a condition (also through ANDs of the mask words); - anything else (cross-lane ops, ballots, readfirstlane, DPP, swizzle, permutes, stores, exports, atomics, branch references, implicit-LOD pixel samples, the false operand of any select) keeps the select. The select and every value it reaches must be in blocks outside loops, so each of them runs at most once per invocation and every use sees the same instance of m. Selects whose condition is the full wave ((0xffffffff >> (lane & 31)) & 1) are replaced in any block. Exact: in lanes where m is true r equals new, so every derived value is unchanged there; in lanes where m is false only derived values change, and each path from them ends in a select or AND that discards those lanes. The selects are removed latest first and dead code is removed between rounds, so chains of writes to one register collapse. --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 7 +- .../libSceAgcDriver/tests/MaskedSelect.cpp | 340 +++++++++++++++++ core/shader/recompiler/CMakeLists.txt | 1 + core/shader/recompiler/Deserialize.cpp | 8 + .../Optimization/MaskedSelectEliminator.hpp | 20 + .../src/MaskedSelectEliminator.cpp | 360 ++++++++++++++++++ core/shader/recompiler/Recompiler.cpp | 6 + 7 files changed, 741 insertions(+), 1 deletion(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/MaskedSelect.cpp create mode 100644 core/shader/recompiler/Optimization/include/Optimization/MaskedSelectEliminator.hpp create mode 100644 core/shader/recompiler/Optimization/src/MaskedSelectEliminator.cpp diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 10da8fa12..b373847f0 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -279,6 +279,11 @@ target_link_libraries(agc_dpp_write_tests PRIVATE shader_recompiler_agc_driver l set_target_properties(agc_dpp_write_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_dpp_write_tests) +add_test_executable(agc_masked_select_tests EXCLUDE_FROM_ALL tests/MaskedSelect.cpp) +target_link_libraries(agc_masked_select_tests PRIVATE shader_recompiler_agc_driver libc) +set_target_properties(agc_masked_select_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_masked_select_tests) + add_test_executable(agc_vop1_control_decode_tests EXCLUDE_FROM_ALL tests/Vop1ControlDecode.cpp) target_link_libraries(agc_vop1_control_decode_tests PRIVATE shader_recompiler_agc_driver libc) set_target_properties(agc_vop1_control_decode_tests PROPERTIES CXX_EXTENSIONS OFF) @@ -498,7 +503,7 @@ foreach(agcExecutionTestSource IN LISTS agcExecutionTestSources) endforeach() if(BUILD_TESTING) - foreach(agcTest IN ITEMS agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_dpp_write_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_shader_disk_cache_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests ${agcExecutionTests}) + foreach(agcTest IN ITEMS agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_dpp_write_tests agc_masked_select_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_shader_disk_cache_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests ${agcExecutionTests}) string(REGEX REPLACE "_tests$" "" agcTestName "${agcTest}") set_target_properties(${agcTest} PROPERTIES EXCLUDE_FROM_ALL OFF) add_test(NAME ${agcTestName} COMMAND ${agcTest}) diff --git a/core/libs/prx/libSceAgcDriver/tests/MaskedSelect.cpp b/core/libs/prx/libSceAgcDriver/tests/MaskedSelect.cpp new file mode 100644 index 000000000..8e1cd2e3f --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/MaskedSelect.cpp @@ -0,0 +1,340 @@ +#include "IntermediateRepresentation/IrProgram.hpp" +#include "Optimization/MaskedSelectEliminator.hpp" +#include "RdnaDecoder/RdnaInstruction.hpp" +#include +#include +#include +#include +#include +#include +#include + +using namespace ShaderRecompiler; + +namespace { + +struct Builder { + IrProgram program; + IrBlock* block = nullptr; + + Builder() { + block = &newBlock(); + program.SetEntryBlock(*block); + } + + IrBlock& newBlock() { + auto& created = program.CreateBlock(); + program.BlockOrder().push_back(&created); + return created; + } + + IrValue& constant(std::uint32_t value) { + auto& created = program.CreateValue(IrOpcode::Void, IrType::U32); + created.SetImmediateU32(value); + return created; + } + + IrValue& emit(IrOpcode opcode, IrType type, std::initializer_list arguments, std::uint64_t flags = 0) { + auto& created = program.CreateValue(opcode, type, flags); + for (auto* argument : arguments) created.AddArgument(argument); + block->AppendInstruction(&created); + return created; + } + + IrValue& lane() { + return emit(IrOpcode::LaneId, IrType::U32, {}); + } + + IrValue& mask(std::uint32_t lanes) { + return emit(IrOpcode::ULessThan32, IrType::Bool, {&lane(), &constant(lanes)}); + } + + IrValue& select(IrValue& condition, IrValue& active, IrValue& inactive) { + return emit(IrOpcode::SelectU32, IrType::U32, {&condition, &active, &inactive}); + } + + IrValue& add(IrValue& value, std::uint32_t immediate) { + return emit(IrOpcode::IAdd32, IrType::U32, {&value, &constant(immediate)}); + } + + IrValue& logicalAnd(IrValue& left, IrValue& right) { + return emit(IrOpcode::LogicalAnd, IrType::Bool, {&left, &right}); + } + + IrValue& threadBit(IrValue& low, IrValue& high) { + auto& laneId = lane(); + auto& word = select(emit(IrOpcode::ULessThan32, IrType::Bool, {&laneId, &constant(32u)}), low, high); + auto& shift = emit(IrOpcode::BitwiseAnd32, IrType::U32, {&laneId, &constant(31u)}); + auto& shifted = emit(IrOpcode::ShiftRightLogical32, IrType::U32, {&word, &shift}); + auto& bit = emit(IrOpcode::BitwiseAnd32, IrType::U32, {&shifted, &constant(1u)}); + return emit(IrOpcode::INotEqual32, IrType::Bool, {&bit, &constant(0u)}); + } + + IrValue& ballotBit(IrValue& predicate) { + auto& ballot = emit(IrOpcode::Ballot, IrType::U32x4, {&predicate}); + auto& low = emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&ballot, &constant(0u)}); + auto& high = emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&ballot, &constant(1u)}); + return threadBit(low, high); + } + + void keep(IrValue& value) { + (void)emit(IrOpcode::ReferenceU32, IrType::Void, {&value}); + } + + std::uint32_t eliminate() { + return MaskedSelectEliminator{}.Eliminate(program).removedSelects; + } +}; + +bool removed(const IrValue& value) { + return value.Parent() == nullptr; +} + +bool expect(const char* name, bool condition) { + if (!condition) std::fprintf(stderr, "%s\n", name); + return condition; +} + +bool run(const char* name, const std::function& test) { + try { + return expect(name, test()); + } catch (const std::exception& error) { + std::fprintf(stderr, "%s: %s\n", name, error.what()); + return false; + } +} + +} + +int main() { + bool passed = true; + + passed &= run("a write read only under its own exec must lose its select", [] { + Builder b; + auto& exec = b.mask(16u); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + auto& next = b.select(exec, b.add(written, 2u), old); + b.keep(next); + return b.eliminate() == 1u && removed(written) && !removed(next); + }); + + passed &= run("a write read under a wider exec must keep its select", [] { + Builder b; + auto& wide = b.mask(32u); + auto& exec = b.logicalAnd(wide, b.mask(16u)); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + auto& next = b.select(wide, b.add(written, 2u), old); + b.keep(next); + return b.eliminate() == 0u && !removed(written); + }); + + passed &= run("a write read under a narrower exec must lose its select", [] { + Builder b; + auto& exec = b.mask(32u); + auto& narrow = b.logicalAnd(exec, b.mask(16u)); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + auto& next = b.select(narrow, b.add(written, 2u), old); + b.keep(next); + return b.eliminate() == 1u && removed(written); + }); + + passed &= run("a write whose old lanes flow into the next write must keep its select", [] { + Builder b; + auto& exec = b.mask(16u); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + auto& next = b.select(exec, b.add(old, 2u), written); + b.keep(next); + return b.eliminate() == 0u && !removed(written); + }); + + passed &= run("a chain of writes under one exec must collapse once the last one goes", [] { + Builder b; + auto& exec = b.mask(16u); + auto& old = b.lane(); + auto& first = b.select(exec, b.add(old, 1u), old); + auto& second = b.select(exec, b.add(first, 2u), first); + auto& sink = b.select(exec, b.add(second, 3u), old); + b.keep(sink); + return b.eliminate() == 2u && removed(first) && removed(second) && !removed(sink); + }); + + passed &= run("cross-lane reads must keep the select", [] { + bool ok = true; + for (const IrOpcode opcode : {IrOpcode::ReadFirstLane, IrOpcode::DppMoveU32, IrOpcode::BpermuteU32, IrOpcode::Permlane16U32, IrOpcode::SwizzleU32}) { + Builder b; + auto& exec = b.mask(16u); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + auto& read = b.emit(opcode, IrType::U32, {&written, &exec}); + b.keep(b.select(exec, read, old)); + ok &= b.eliminate() == 0u && !removed(written); + } + Builder b; + auto& exec = b.mask(16u); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + auto& ballot = b.emit(IrOpcode::Ballot, IrType::U32x4, {&b.emit(IrOpcode::INotEqual32, IrType::Bool, {&written, &b.constant(0u)})}); + b.keep(b.emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&ballot, &b.constant(0u)})); + return ok && b.eliminate() == 0u && !removed(written); + }); + + passed &= run("a branch condition derived from the write keeps the select", [] { + Builder b; + auto& exec = b.mask(16u); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + auto& taken = b.emit(IrOpcode::INotEqual32, IrType::Bool, {&written, &b.constant(0u)}); + (void)b.emit(IrOpcode::Reference, IrType::Void, {&taken}); + return b.eliminate() == 0u && !removed(written); + }); + + passed &= run("a compare masked by the same exec must not keep the select", [] { + Builder b; + auto& exec = b.mask(16u); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + auto& compare = b.logicalAnd(exec, b.emit(IrOpcode::INotEqual32, IrType::Bool, {&written, &b.constant(0u)})); + auto& ballot = b.emit(IrOpcode::Ballot, IrType::U32x4, {&compare}); + b.keep(b.emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&ballot, &b.constant(0u)})); + return b.eliminate() == 1u && removed(written); + }); + + passed &= run("an exec rebuilt from a ballot of a narrower mask implies the write's exec", [] { + Builder b; + auto& exec = b.mask(48u); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + auto& later = b.ballotBit(b.logicalAnd(exec, b.mask(40u))); + auto& next = b.select(later, b.add(written, 2u), old); + b.keep(next); + return b.eliminate() == 1u && removed(written); + }); + + passed &= run("an exec rebuilt from a ballot of a wider mask keeps the select", [] { + Builder b; + auto& wide = b.mask(48u); + auto& exec = b.logicalAnd(wide, b.mask(40u)); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + auto& later = b.ballotBit(wide); + b.keep(b.select(later, b.add(written, 2u), old)); + return b.eliminate() == 0u && !removed(written); + }); + + passed &= run("a select under the full wave's exec must go whatever reads it", [] { + Builder b; + auto& full = b.threadBit(b.constant(0xffffffffu), b.constant(0xffffffffu)); + auto& old = b.lane(); + auto& written = b.select(full, b.add(old, 1u), old); + b.keep(written); + return b.eliminate() != 0u && removed(written); + }); + + passed &= run("a partial wave's exec is not the full wave", [] { + Builder b; + auto& partial = b.threadBit(b.constant(0xffffffffu), b.constant(0x0000ffffu)); + auto& old = b.lane(); + auto& written = b.select(partial, b.add(old, 1u), old); + b.keep(written); + return b.eliminate() == 0u && !removed(written); + }); + + const auto diamond = [](bool wideRead) { + Builder b; + auto& wide = b.mask(48u); + auto& exec = b.logicalAnd(wide, b.mask(16u)); + auto& old = b.lane(); + IrBlock& head = *b.block; + IrBlock& body = b.newBlock(); + IrBlock& merge = b.newBlock(); + head.AddBranch(&body); + head.AddBranch(&merge); + body.AddBranch(&merge); + b.block = &body; + auto& written = b.select(exec, b.add(old, 1u), old); + b.block = &merge; + auto& phi = b.program.CreateValue(IrOpcode::Phi, IrType::U32); + merge.AppendInstruction(&phi); + phi.AddPhiOperand(&head, &old); + phi.AddPhiOperand(&body, &written); + b.keep(b.select(wideRead ? wide : exec, b.add(phi, 2u), old)); + return std::pair{b.eliminate(), removed(written)}; + }; + + passed &= run("a write merged by a phi and read under its own exec must lose its select", [&] { + const auto [count, gone] = diamond(false); + return count == 1u && gone; + }); + + passed &= run("a write merged by a phi and read under the wider exec must keep its select", [&] { + const auto [count, gone] = diamond(true); + return count == 0u && !gone; + }); + + passed &= run("a write in a loop must keep its select", [] { + Builder b; + auto& exec = b.mask(16u); + auto& old = b.lane(); + IrBlock& head = *b.block; + IrBlock& loop = b.newBlock(); + IrBlock& exit = b.newBlock(); + head.AddBranch(&loop); + loop.AddBranch(&loop); + loop.AddBranch(&exit); + b.block = &loop; + auto& written = b.select(exec, b.add(old, 1u), old); + auto& next = b.select(exec, b.add(written, 2u), old); + b.keep(next); + return b.eliminate() == 0u && !removed(written); + }); + + passed &= run("a write read inside a later loop must keep its select", [] { + Builder b; + auto& exec = b.mask(16u); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + IrBlock& head = *b.block; + IrBlock& loop = b.newBlock(); + head.AddBranch(&loop); + loop.AddBranch(&loop); + b.block = &loop; + b.keep(b.select(exec, b.add(written, 2u), old)); + return b.eliminate() == 0u && !removed(written); + }); + + const auto sample = [](IrShaderStage stage, std::uint32_t sampleFlags) { + Builder b; + b.program.Resources().stage = stage; + MemoryInfo memory; + memory.kind = ResourceKind::Image; + memory.imageSampleFlags = sampleFlags; + b.program.Resources().memoryInfo.push_back(memory); + auto& exec = b.mask(16u); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + auto& address = b.emit(IrOpcode::MakeImageAddress, IrType::ImageAddress, {&written, &old}); + auto& image = b.emit(IrOpcode::GetImageResource, IrType::ImageResource, {}); + auto& sampler = b.emit(IrOpcode::GetSamplerResource, IrType::SamplerResource, {}); + MemoryFlags flags{0u, 0u}; + std::uint64_t bits = 0; + std::memcpy(&bits, &flags, sizeof(flags)); + auto& sampled = b.emit(IrOpcode::ImageSampleRaw, IrType::U32x4, {&image, &sampler, &address}, bits); + b.keep(b.select(exec, b.emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&sampled, &b.constant(0u)}), old)); + b.eliminate(); + return removed(written); + }; + + passed &= run("an implicit-LOD pixel sample reads its quad's coordinates and keeps the select", [&] { + return !sample(IrShaderStage::Pixel, 0u); + }); + + passed &= run("an explicit-LOD sample reads only its lane and drops the select", [&] { + return sample(IrShaderStage::Pixel, RdnaImageSampleFlagLevelZero) && sample(IrShaderStage::Mesh, 0u); + }); + + return passed ? 0 : 1; +} diff --git a/core/shader/recompiler/CMakeLists.txt b/core/shader/recompiler/CMakeLists.txt index 29d4bf176..50ff3356d 100644 --- a/core/shader/recompiler/CMakeLists.txt +++ b/core/shader/recompiler/CMakeLists.txt @@ -96,6 +96,7 @@ function(add_shader_recompiler target runtimeTarget) ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/ConstantFolder.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/DeadCodeEliminator.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/ReadLaneEliminator.cpp + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/MaskedSelectEliminator.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/ResourceTracker.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/ResourceMaterializer.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/BindingAllocator.cpp diff --git a/core/shader/recompiler/Deserialize.cpp b/core/shader/recompiler/Deserialize.cpp index 4e2574a14..59f3c075d 100644 --- a/core/shader/recompiler/Deserialize.cpp +++ b/core/shader/recompiler/Deserialize.cpp @@ -7,6 +7,7 @@ #include "Optimization/BindingAllocator.hpp" #include "Optimization/ConstantFolder.hpp" #include "Optimization/DeadCodeEliminator.hpp" +#include "Optimization/MaskedSelectEliminator.hpp" #include "Optimization/ReadLaneEliminator.hpp" #include "Optimization/ResourceTracker.hpp" #include "Optimization/ShaderInfoCollector.hpp" @@ -208,6 +209,13 @@ int main(int argc, char** argv) { deadCodeEliminator.Eliminate(program); } + MaskedSelectEliminator maskedSelectEliminator; + const auto maskedSelectStats = maskedSelectEliminator.Eliminate(program); + std::cout << "MaskedSelectEliminator removed " << maskedSelectStats.removedSelects << "\n"; + if (maskedSelectStats.removedSelects != 0u) { + deadCodeEliminator.Eliminate(program); + } + SrtWalker srtWalker; srtWalker.BuildPlan(program); std::cout << "SrtWalker::BuildPlan OK\n"; diff --git a/core/shader/recompiler/Optimization/include/Optimization/MaskedSelectEliminator.hpp b/core/shader/recompiler/Optimization/include/Optimization/MaskedSelectEliminator.hpp new file mode 100644 index 000000000..fad7eed9c --- /dev/null +++ b/core/shader/recompiler/Optimization/include/Optimization/MaskedSelectEliminator.hpp @@ -0,0 +1,20 @@ +#ifndef CORE_SHADER_RECOMPILIER_OPTIMIZATION_INCLUDE_OPTIMIZATION_MASKEDSELECTELIMINATOR_HPP +#define CORE_SHADER_RECOMPILIER_OPTIMIZATION_INCLUDE_OPTIMIZATION_MASKEDSELECTELIMINATOR_HPP + +#include "IntermediateRepresentation/IrProgram.hpp" +#include + +namespace ShaderRecompiler { + +struct MaskedSelectEliminationStats { + std::uint32_t removedSelects = 0; +}; + +class MaskedSelectEliminator { +public: + [[nodiscard]] MaskedSelectEliminationStats Eliminate(IrProgram& program) const; +}; + +} + +#endif diff --git a/core/shader/recompiler/Optimization/src/MaskedSelectEliminator.cpp b/core/shader/recompiler/Optimization/src/MaskedSelectEliminator.cpp new file mode 100644 index 000000000..6ab6062c4 --- /dev/null +++ b/core/shader/recompiler/Optimization/src/MaskedSelectEliminator.cpp @@ -0,0 +1,360 @@ +#include "Optimization/MaskedSelectEliminator.hpp" +#include "Optimization/DeadCodeEliminator.hpp" +#include "RdnaDecoder/RdnaInstruction.hpp" +#include +#include +#include +#include +#include + +namespace ShaderRecompiler { +namespace { + +constexpr std::size_t VisitLimit = 4096; +constexpr std::uint32_t DepthLimit = 8; + +bool isSelect(IrOpcode opcode) { + return opcode == IrOpcode::SelectU1 || opcode == IrOpcode::SelectU32 || opcode == IrOpcode::SelectF32; +} + +bool isLaneLocal(IrOpcode opcode) { + if (IrOpcodeHasSideEffects(opcode)) return false; + switch (opcode) { + case IrOpcode::Identity: + case IrOpcode::IAdd32: + case IrOpcode::ISub32: + case IrOpcode::IMul32: + case IrOpcode::SelectU1: + case IrOpcode::SelectU32: + case IrOpcode::SelectF32: + case IrOpcode::BitCastU16F16: + case IrOpcode::BitCastF16U16: + case IrOpcode::BitCastU32F32: + case IrOpcode::BitCastF32U32: + case IrOpcode::ConvertU16U32: + case IrOpcode::ConvertU32U16: + case IrOpcode::ConvertU8U32: + case IrOpcode::ConvertU32U8: + case IrOpcode::ConvertF32F16: + case IrOpcode::ConvertF16F32: + case IrOpcode::ConvertS32F32: + case IrOpcode::ConvertU32F32: + case IrOpcode::ConvertF32S32: + case IrOpcode::ConvertF32U32: + case IrOpcode::ConvertF32F64: + case IrOpcode::ConvertF64F32: + case IrOpcode::ConvertF64S32: + case IrOpcode::ConvertF64U32: + case IrOpcode::ConvertS32F64: + case IrOpcode::ConvertU32F64: + case IrOpcode::CompositeConstructU64: + case IrOpcode::CompositeConstructU32x2: + case IrOpcode::CompositeConstructU32x3: + case IrOpcode::CompositeConstructF32x2: + case IrOpcode::CompositeConstructU32x4: + case IrOpcode::CompositeExtractU64: + case IrOpcode::CompositeExtractU32x2: + case IrOpcode::CompositeExtractU32x3: + case IrOpcode::CompositeExtractU32x4: + case IrOpcode::PackHalf2x16: + case IrOpcode::PackFloat2x16Rtz: + case IrOpcode::BitFieldInsert: + case IrOpcode::BitFieldUExtract: + case IrOpcode::BitFieldSExtract: + case IrOpcode::IAdd64: + case IrOpcode::IAddCarry32: + case IrOpcode::ISub64: + case IrOpcode::IMul64: + case IrOpcode::SMulHi: + case IrOpcode::UMulHi: + case IrOpcode::IAbs32: + case IrOpcode::ShiftLeftLogical32: + case IrOpcode::ShiftLeftLogical64: + case IrOpcode::ShiftRightLogical32: + case IrOpcode::ShiftRightLogical64: + case IrOpcode::ShiftRightArithmetic32: + case IrOpcode::ShiftRightArithmetic64: + case IrOpcode::BitwiseAnd32: + case IrOpcode::BitwiseAnd64: + case IrOpcode::BitwiseOr32: + case IrOpcode::BitwiseXor32: + case IrOpcode::BitwiseNot32: + case IrOpcode::BitReverse32: + case IrOpcode::BitCount32: + case IrOpcode::BitCount64: + case IrOpcode::FindUMsb32: + case IrOpcode::FindUMsb64: + case IrOpcode::FindILsb32: + case IrOpcode::SMin32: + case IrOpcode::UMin32: + case IrOpcode::SMax32: + case IrOpcode::UMax32: + case IrOpcode::SMinTri32: + case IrOpcode::UMinTri32: + case IrOpcode::SMaxTri32: + case IrOpcode::UMaxTri32: + case IrOpcode::SMedTri32: + case IrOpcode::UMedTri32: + case IrOpcode::SLessThan32: + case IrOpcode::SLessThan64: + case IrOpcode::ULessThan32: + case IrOpcode::ULessThan64: + case IrOpcode::IEqual32: + case IrOpcode::IEqual64: + case IrOpcode::SLessThanEqual32: + case IrOpcode::ULessThanEqual32: + case IrOpcode::SGreaterThan32: + case IrOpcode::UGreaterThan32: + case IrOpcode::UGreaterThan64: + case IrOpcode::INotEqual32: + case IrOpcode::INotEqual64: + case IrOpcode::SGreaterThanEqual32: + case IrOpcode::UGreaterThanEqual32: + case IrOpcode::LogicalOr: + case IrOpcode::LogicalAnd: + case IrOpcode::LogicalXor: + case IrOpcode::LogicalNot: + case IrOpcode::FPOrdEqual32: + case IrOpcode::FPUnordEqual32: + case IrOpcode::FPOrdNotEqual32: + case IrOpcode::FPUnordNotEqual32: + case IrOpcode::FPOrdLessThan32: + case IrOpcode::FPUnordLessThan32: + case IrOpcode::FPOrdGreaterThan32: + case IrOpcode::FPUnordGreaterThan32: + case IrOpcode::FPOrdLessThanEqual32: + case IrOpcode::FPUnordLessThanEqual32: + case IrOpcode::FPOrdGreaterThanEqual32: + case IrOpcode::FPUnordGreaterThanEqual32: + case IrOpcode::FPIsNan32: + case IrOpcode::FPCmpClass32: + case IrOpcode::FPAbs32: + case IrOpcode::FPNeg32: + case IrOpcode::FPSaturate32: + case IrOpcode::FPAdd32: + case IrOpcode::FPSub32: + case IrOpcode::FPFma32: + case IrOpcode::FPMad32: + case IrOpcode::FPMul32: + case IrOpcode::FPMin32: + case IrOpcode::FPMax32: + case IrOpcode::FPMinTri32: + case IrOpcode::FPMaxTri32: + case IrOpcode::FPMedTri32: + case IrOpcode::FPRecip32: + case IrOpcode::FPRecipIFlag32: + case IrOpcode::FPRecipSqrt32: + case IrOpcode::FPSqrt: + case IrOpcode::FPSin: + case IrOpcode::FPCos: + case IrOpcode::FPExp2: + case IrOpcode::FPLog2: + case IrOpcode::FPRoundEven32: + case IrOpcode::FPFloor32: + case IrOpcode::FPCeil32: + case IrOpcode::FPTrunc32: + case IrOpcode::FPFract32: + case IrOpcode::FPAdd64: + case IrOpcode::FPMul64: + case IrOpcode::FPFma64: + case IrOpcode::FPMin64: + case IrOpcode::FPMax64: + case IrOpcode::FPSaturate64: + case IrOpcode::FPLdexp64: + case IrOpcode::FPRoundEven64: + case IrOpcode::FPFloor64: + case IrOpcode::FPCeil64: + case IrOpcode::FPTrunc64: + case IrOpcode::FPFract64: + case IrOpcode::FPFrexpMant64: + case IrOpcode::FPFrexpExp64: + case IrOpcode::MakeImageAddress: + return true; + default: + return false; + } +} + +bool isExplicitLodSample(const IrProgram& program, const IrValue& value) { + if (value.Opcode() != IrOpcode::ImageSampleRaw) return false; + if (program.Resources().stage != IrShaderStage::Pixel) return true; + const auto index = value.Flags().index; + if (index >= program.Resources().memoryInfo.size()) return false; + return (program.Resources().memoryInfo[index].imageSampleFlags & (RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLod | RdnaImageSampleFlagLevelZero)) != 0u; +} + +bool isImmediate(const IrValue* value, std::uint32_t immediate) { + return value->HasImmediate() && value->Type() == IrType::U32 && value->ImmediateU32() == immediate; +} + +const IrValue* otherArgument(const IrValue* value, std::uint32_t immediate) { + if (value->ArgumentCount() != 2u) return nullptr; + const IrValue* left = value->Argument(0)->Resolve(); + const IrValue* right = value->Argument(1)->Resolve(); + if (isImmediate(right, immediate)) return left; + if (isImmediate(left, immediate)) return right; + return nullptr; +} + +struct ThreadBit { + const IrValue* low = nullptr; + const IrValue* high = nullptr; +}; + +bool isLane(const IrValue* value) { + return value->Resolve()->Opcode() == IrOpcode::LaneId; +} + +std::optional threadBit(const IrValue* value, std::uint32_t waveSize) { + value = value->Resolve(); + if (value->Opcode() != IrOpcode::INotEqual32) return std::nullopt; + const IrValue* bit = otherArgument(value, 0u); + if (bit == nullptr || bit->Opcode() != IrOpcode::BitwiseAnd32) return std::nullopt; + const IrValue* shifted = otherArgument(bit, 1u); + if (shifted == nullptr || shifted->Opcode() != IrOpcode::ShiftRightLogical32) return std::nullopt; + const IrValue* amount = shifted->Argument(1)->Resolve(); + if (amount->Opcode() != IrOpcode::BitwiseAnd32) return std::nullopt; + const IrValue* lane = otherArgument(amount, 31u); + if (lane == nullptr || !isLane(lane)) return std::nullopt; + const IrValue* word = shifted->Argument(0)->Resolve(); + if (waveSize == 32u || word->HasImmediate()) return ThreadBit{word, word}; + if (word->Opcode() != IrOpcode::SelectU32) return std::nullopt; + const IrValue* half = word->Argument(0)->Resolve(); + if (half->Opcode() != IrOpcode::ULessThan32 || !isLane(half->Argument(0)) || !isImmediate(half->Argument(1)->Resolve(), 32u)) return std::nullopt; + return ThreadBit{word->Argument(1)->Resolve(), word->Argument(2)->Resolve()}; +} + +bool alwaysTrue(const IrValue* value, std::uint32_t waveSize, std::uint32_t depth = 0) { + value = value->Resolve(); + if (value->HasImmediate()) return value->Type() == IrType::Bool && value->ImmediateBool(); + if (depth > DepthLimit) return false; + if (value->Opcode() == IrOpcode::LogicalAnd) return alwaysTrue(value->Argument(0), waveSize, depth + 1u) && alwaysTrue(value->Argument(1), waveSize, depth + 1u); + const auto bit = threadBit(value, waveSize); + return bit && isImmediate(bit->low, 0xffffffffu) && isImmediate(bit->high, 0xffffffffu); +} + +bool implies(const IrValue* condition, const IrValue* mask, std::uint32_t waveSize, std::uint32_t depth = 0); + +bool wordsImply(const IrValue* low, const IrValue* high, const IrValue* mask, std::uint32_t waveSize, std::uint32_t depth) { + if (depth > DepthLimit) return false; + if (isImmediate(low, 0u) && (waveSize == 32u || isImmediate(high, 0u))) return true; + if (low->Opcode() == IrOpcode::CompositeExtractU32x4 && isImmediate(low->Argument(1)->Resolve(), 0u)) { + const IrValue* ballot = low->Argument(0)->Resolve(); + const bool sameBallot = waveSize == 32u || (high->Opcode() == IrOpcode::CompositeExtractU32x4 && high->Argument(0)->Resolve() == ballot && isImmediate(high->Argument(1)->Resolve(), 1u)); + if (ballot->Opcode() == IrOpcode::Ballot && sameBallot && implies(ballot->Argument(0), mask, waveSize, depth + 1u)) return true; + } + if (low->Opcode() != IrOpcode::BitwiseAnd32) return false; + if (waveSize == 32u) return wordsImply(low->Argument(0)->Resolve(), high, mask, waveSize, depth + 1u) || wordsImply(low->Argument(1)->Resolve(), high, mask, waveSize, depth + 1u); + if (high->Opcode() != IrOpcode::BitwiseAnd32) return false; + for (std::size_t lowIndex = 0; lowIndex < 2u; ++lowIndex) { + for (std::size_t highIndex = 0; highIndex < 2u; ++highIndex) { + if (wordsImply(low->Argument(lowIndex)->Resolve(), high->Argument(highIndex)->Resolve(), mask, waveSize, depth + 1u)) return true; + } + } + return false; +} + +bool implies(const IrValue* condition, const IrValue* mask, std::uint32_t waveSize, std::uint32_t depth) { + condition = condition->Resolve(); + if (condition == mask || alwaysTrue(mask, waveSize)) return true; + if (condition->HasImmediate()) return condition->Type() == IrType::Bool && !condition->ImmediateBool(); + if (depth > DepthLimit) return false; + if (condition->Opcode() == IrOpcode::LogicalAnd) return implies(condition->Argument(0), mask, waveSize, depth + 1u) || implies(condition->Argument(1), mask, waveSize, depth + 1u); + const auto bit = threadBit(condition, waveSize); + return bit && wordsImply(bit->low, bit->high, mask, waveSize, depth + 1u); +} + +std::unordered_set blocksOutsideLoops(const IrProgram& program) { + const auto& order = program.BlockOrder(); + if (order.empty()) return {}; + std::unordered_map state; + std::unordered_set looping; + std::vector> stack; + state[order.front()] = 1; + stack.emplace_back(order.front(), 0u); + while (!stack.empty()) { + auto& [block, next] = stack.back(); + if (next == block->Successors().size()) { + state[block] = 2; + stack.pop_back(); + continue; + } + const IrBlock* source = block; + const IrBlock* successor = block->Successors()[next++]; + int& successorState = state[successor]; + if (successorState == 0) { + successorState = 1; + stack.emplace_back(successor, 0u); + } else if (successorState == 1) { + std::unordered_set body{successor}; + std::vector pending{source}; + while (!pending.empty()) { + const IrBlock* member = pending.back(); + pending.pop_back(); + if (!body.insert(member).second) continue; + for (const IrBlock* predecessor : member->Predecessors()) pending.push_back(predecessor); + } + looping.insert(body.begin(), body.end()); + } + } + std::unordered_set result; + for (const auto& [block, visited] : state) { + if (visited == 2 && !looping.contains(block)) result.insert(block); + } + return result; +} + +bool unobservedWhereMasked(const IrProgram& program, const std::unordered_set& once, const IrValue& select, const IrValue* mask) { + const auto waveSize = program.WaveSize(); + std::vector pending{&select}; + std::unordered_set visited{&select}; + while (!pending.empty()) { + const IrValue* value = pending.back(); + pending.pop_back(); + for (const IrUse& use : value->OperandUses()) { + const IrValue* user = use.user; + if (user->Parent() == nullptr || !once.contains(user->Parent())) return false; + if (isSelect(user->Opcode()) && use.operand == 1u && implies(user->Argument(0), mask, waveSize)) continue; + if (user->Opcode() == IrOpcode::LogicalAnd && use.operand < 2u && implies(user->Argument(1u - use.operand), mask, waveSize)) continue; + if (!user->IsPhi() && !isLaneLocal(user->Opcode()) && !isExplicitLodSample(program, *user)) return false; + if (visited.insert(user).second) { + if (visited.size() > VisitLimit) return false; + pending.push_back(user); + } + } + } + return true; +} + +} + +MaskedSelectEliminationStats MaskedSelectEliminator::Eliminate(IrProgram& program) const { + MaskedSelectEliminationStats stats; + const auto once = blocksOutsideLoops(program); + bool changed = true; + while (changed) { + changed = false; + for (auto blockIt = program.BlockOrder().rbegin(); blockIt != program.BlockOrder().rend(); ++blockIt) { + const bool single = once.contains(*blockIt); + auto& instructions = (*blockIt)->Instructions(); + auto it = instructions.end(); + while (it != instructions.begin()) { + --it; + IrValue* inst = *it; + if (!isSelect(inst->Opcode()) || inst->ArgumentCount() != 3u || !inst->HasUses()) continue; + const IrValue* mask = inst->Argument(0)->Resolve(); + if (!alwaysTrue(mask, program.WaveSize()) && (!single || !unobservedWhereMasked(program, once, *inst, mask))) continue; + inst->ReplaceAllUsesWith(inst->Argument(1)); + inst->Invalidate(); + inst->SetParent(nullptr); + it = instructions.erase(it); + ++stats.removedSelects; + changed = true; + } + } + if (changed) DeadCodeEliminator{}.Eliminate(program); + } + return stats; +} + +} diff --git a/core/shader/recompiler/Recompiler.cpp b/core/shader/recompiler/Recompiler.cpp index 89dd3909a..817f4fc7f 100644 --- a/core/shader/recompiler/Recompiler.cpp +++ b/core/shader/recompiler/Recompiler.cpp @@ -19,6 +19,7 @@ #include "Optimization/include/Optimization/ConstantFolder.hpp" #include "Optimization/include/Optimization/DeadCodeEliminator.hpp" #include "Optimization/include/Optimization/DescriptorBindingBuilder.hpp" +#include "Optimization/include/Optimization/MaskedSelectEliminator.hpp" #include "Optimization/include/Optimization/ReadLaneEliminator.hpp" #include "Optimization/include/Optimization/RequestMemoryView.hpp" #include "Optimization/include/Optimization/ResourceMaterializer.hpp" @@ -155,6 +156,11 @@ IrProgram PrepareResourceProgram(const RecompileRequest& request) { deadCodeEliminator.Eliminate(program); } + constexpr MaskedSelectEliminator maskedSelectEliminator; + if (maskedSelectEliminator.Eliminate(program).removedSelects != 0u) { + deadCodeEliminator.Eliminate(program); + } + constexpr SrtWalker srtWalker; srtWalker.BuildPlan(program); deadCodeEliminator.Eliminate(program); From 60c655211d5575da28ccbea8a65126fdaeca307e Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Mon, 5 Oct 2026 14:10:02 +1300 Subject: [PATCH 089/164] perf(recompiler): let exec-guarded memory and exports end a masked select A register written under EXEC is usually consumed by an LDS or buffer access or an export under the same EXEC. MaskedSelectEliminator kept the select in that case, which leaves the NGG mesh shaders where they were. The emitter performs buffer and LDS loads and stores (8 to 128 bits, GPU-selected V# included) and SetAttribute only in lanes whose active operand is true: stores and exports sit in an if on it, loads return 0 outside it. Every other operand is read only in those lanes. So such an access whose active operand implies the select's condition reads the derived value only where it equals the original, and it now ends the search like a masked select does. The active operand itself, atomics, append/consume, scratch and address-space accesses and image accesses still keep the select. --- .../libSceAgcDriver/tests/MaskedSelect.cpp | 38 +++++++++++++++++++ .../src/MaskedSelectEliminator.cpp | 34 +++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/tests/MaskedSelect.cpp b/core/libs/prx/libSceAgcDriver/tests/MaskedSelect.cpp index 8e1cd2e3f..119f87925 100644 --- a/core/libs/prx/libSceAgcDriver/tests/MaskedSelect.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/MaskedSelect.cpp @@ -336,5 +336,43 @@ int main() { return sample(IrShaderStage::Pixel, RdnaImageSampleFlagLevelZero) && sample(IrShaderStage::Mesh, 0u); }); + const auto guarded = [](IrOpcode opcode, bool sameExec, bool asActive) { + Builder b; + auto& wide = b.mask(48u); + auto& exec = b.logicalAnd(wide, b.mask(16u)); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + IrValue& active = asActive ? b.emit(IrOpcode::INotEqual32, IrType::Bool, {&written, &b.constant(0u)}) : sameExec ? exec : wide; + switch (opcode) { + case IrOpcode::WriteSharedU32: + b.emit(opcode, IrType::Void, {&old, &written, &active}); + break; + case IrOpcode::LoadBufferU32: + b.keep(b.select(exec, b.emit(opcode, IrType::U32, {&b.emit(IrOpcode::GetBufferResource, IrType::BufferResource, {}), &written, &b.constant(0u), &b.constant(0u), &active}), old)); + break; + default: + b.emit(opcode, IrType::Void, {&b.emit(IrOpcode::CompositeConstructU32x4, IrType::U32x4, {&written, &old, &old, &old}), &active}); + break; + } + b.eliminate(); + return removed(written); + }; + + passed &= run("a store, load or export whose exec implies the write's exec drops the select", [&] { + return guarded(IrOpcode::WriteSharedU32, true, false) && guarded(IrOpcode::LoadBufferU32, true, false) && guarded(IrOpcode::SetAttribute, true, false); + }); + + passed &= run("a store, load or export under a wider exec keeps the select", [&] { + return !guarded(IrOpcode::WriteSharedU32, false, false) && !guarded(IrOpcode::LoadBufferU32, false, false) && !guarded(IrOpcode::SetAttribute, false, false); + }); + + passed &= run("a value used as a store's exec keeps the select", [&] { + return !guarded(IrOpcode::WriteSharedU32, true, true); + }); + + passed &= run("an atomic keeps the select", [&] { + return !guarded(IrOpcode::SharedAtomicIAdd32, true, false); + }); + return passed ? 0 : 1; } diff --git a/core/shader/recompiler/Optimization/src/MaskedSelectEliminator.cpp b/core/shader/recompiler/Optimization/src/MaskedSelectEliminator.cpp index 6ab6062c4..74f4cda6f 100644 --- a/core/shader/recompiler/Optimization/src/MaskedSelectEliminator.cpp +++ b/core/shader/recompiler/Optimization/src/MaskedSelectEliminator.cpp @@ -175,6 +175,39 @@ bool isLaneLocal(IrOpcode opcode) { } } +bool readsOnlyWhereActive(IrOpcode opcode) { + switch (opcode) { + case IrOpcode::LoadBufferU8: + case IrOpcode::LoadBufferU16: + case IrOpcode::LoadBufferU32: + case IrOpcode::LoadBufferU32x2: + case IrOpcode::LoadBufferU32x3: + case IrOpcode::LoadBufferU32x4: + case IrOpcode::StoreBufferU8: + case IrOpcode::StoreBufferU16: + case IrOpcode::StoreBufferU32: + case IrOpcode::StoreBufferU32x2: + case IrOpcode::StoreBufferU32x3: + case IrOpcode::StoreBufferU32x4: + case IrOpcode::LoadSharedU8: + case IrOpcode::LoadSharedU16: + case IrOpcode::LoadSharedU32: + case IrOpcode::LoadSharedU32x2: + case IrOpcode::LoadSharedU32x3: + case IrOpcode::LoadSharedU32x4: + case IrOpcode::WriteSharedU8: + case IrOpcode::WriteSharedU16: + case IrOpcode::WriteSharedU32: + case IrOpcode::WriteSharedU32x2: + case IrOpcode::WriteSharedU32x3: + case IrOpcode::WriteSharedU32x4: + case IrOpcode::SetAttribute: + return true; + default: + return false; + } +} + bool isExplicitLodSample(const IrProgram& program, const IrValue& value) { if (value.Opcode() != IrOpcode::ImageSampleRaw) return false; if (program.Resources().stage != IrShaderStage::Pixel) return true; @@ -316,6 +349,7 @@ bool unobservedWhereMasked(const IrProgram& program, const std::unordered_setParent() == nullptr || !once.contains(user->Parent())) return false; if (isSelect(user->Opcode()) && use.operand == 1u && implies(user->Argument(0), mask, waveSize)) continue; if (user->Opcode() == IrOpcode::LogicalAnd && use.operand < 2u && implies(user->Argument(1u - use.operand), mask, waveSize)) continue; + if (readsOnlyWhereActive(user->Opcode()) && use.operand + 1u < user->ArgumentCount() && implies(user->Argument(user->ArgumentCount() - 1u), mask, waveSize)) continue; if (!user->IsPhi() && !isLaneLocal(user->Opcode()) && !isExplicitLodSample(program, *user)) return false; if (visited.insert(user).second) { if (visited.size() > VisitLimit) return false; From bdf39840cf58a6758186748b3a9052c89796aad4 Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Mon, 5 Oct 2026 12:07:05 +1300 Subject: [PATCH 090/164] fix(recompiler): size per-invocation LDS arrays to the addresses they reach Stages without workgroup memory (vertex, pixel, ...) keep LDS in a per-invocation Function array of 8192 dwords. PPSA21564 creates a pixel shader that keeps three lane-strided slots there (lane * 4 at byte offsets 0, 256 and 512). On NVIDIA (driver 610.57) the array becomes 32864 bytes of local memory per thread, and about 15 s after the pipeline is created the GPU hangs: Xid 109 (CTX SWITCH TIMEOUT), or Xid 31 (FAULT_PDE VIRT_WRITE) with VK_EXT_device_fault enabled, at an address no Vulkan object ever held. The analysis now bounds each LDS access's address over lane ids, constants, adds, multiplies, shifts, ands, ors, xors, min/max, selects, bitfield extracts and phis of those. A loop-carried or otherwise unknown address, an add that could wrap with the instruction offset, or a shared access whose width is not listed has no bound. The array becomes the dwords the accesses can reach, rounded up to 64, and stays at 8192 when any access is unbounded. No access can reach past the smaller array, so the bounds checks and results are unchanged. The pixel shader above now takes 192 dwords. --- .../libSceAgcDriver/tests/ShaderMemory.cpp | 92 +++++++++ .../include/SpirvBackend/SpirvAnalysis.hpp | 3 + .../SpirvBackend/src/SpirvAnalysis.cpp | 176 ++++++++++++++++++ .../src/SpirvMemory/SpirvBufferAccess.cpp | 5 +- 4 files changed, 273 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index fd8aa6493..dad622661 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -893,6 +893,97 @@ void verifyTwoLaneUniformValues() { require(multiplies(32u) == oneLane, "two-lane uniform values: a two-lane invocation computes a scalar value once per lane"); } +void verifyFunctionLdsBound() { + using namespace ShaderRecompiler; + const auto build = [](const auto& body) { + IrProgram program; + program.Resources().stage = IrShaderStage::Pixel; + for (const std::uint32_t offset : {0u, 256u, 512u, 0xfffffff0u}) { + MemoryInfo lds; + lds.kind = ResourceKind::Lds; + lds.offset = offset; + program.Resources().memoryInfo.push_back(lds); + } + auto& block = program.CreateBlock(); + program.SetEntryBlock(block); + program.BlockOrder().push_back(&block); + const auto emit = [&](IrOpcode opcode, IrType type, std::initializer_list arguments, std::uint32_t memory = ~0u) -> IrValue& { + std::uint64_t bits = 0; + if (memory != ~0u) { + MemoryFlags flags{memory, 0u}; + std::memcpy(&bits, &flags, sizeof(flags)); + } + auto& value = program.CreateValue(opcode, type, bits); + for (auto* argument : arguments) value.AddArgument(argument); + block.AppendInstruction(&value); + return value; + }; + const auto constant = [&](std::uint32_t immediate) -> IrValue& { + auto& value = program.CreateValue(IrOpcode::Void, IrType::U32); + value.SetImmediateU32(immediate); + return value; + }; + auto& active = program.CreateValue(IrOpcode::Void, IrType::U1); + active.SetImmediateBool(true); + body(program, block, emit, constant, active); + return std::pair{FunctionLdsDwords(program), AnalyzeProgramRequirements(program)}; + }; + + const auto [laneSlots, laneRequirements] = build([](IrProgram&, IrBlock&, auto& emit, auto& constant, IrValue& active) { + auto& lane = emit(IrOpcode::LaneId, IrType::U32, {}); + auto& address = emit(IrOpcode::ShiftLeftLogical32, IrType::U32, {&lane, &constant(2u)}); + for (const std::uint32_t memory : {0u, 1u, 2u}) emit(IrOpcode::WriteSharedU32, IrType::Void, {&address, &lane, &active}, memory); + emit(IrOpcode::LoadSharedU32, IrType::U32, {&address, &active}, 2u); + }); + require(laneSlots == 192u, "function LDS: lane-strided slots up to byte 767 must take 192 dwords"); + require(laneRequirements.functionLds && laneRequirements.functionLdsDwords == 192u, "function LDS: the requirements do not carry the bounded size"); + + const auto [wide, wideRequirements] = build([](IrProgram&, IrBlock&, auto& emit, auto& constant, IrValue& active) { + auto& lane = emit(IrOpcode::LaneId, IrType::U32, {}); + auto& masked = emit(IrOpcode::BitwiseAnd32, IrType::U32, {&lane, &constant(7u)}); + auto& scaled = emit(IrOpcode::IMul32, IrType::U32, {&masked, &constant(16u)}); + auto& chosen = emit(IrOpcode::SelectU32, IrType::U32, {&active, &scaled, &constant(0x100u)}); + emit(IrOpcode::LoadSharedU32x4, IrType::U32x4, {&chosen, &active}, 0u); + }); + require(wide == 128u, "function LDS: a 4-dword access at byte 0x100 must take 68 dwords, rounded to 128"); + require(wideRequirements.functionLdsDwords == 128u, "function LDS: the requirements do not carry the wide access's size"); + + const auto [unbounded, unboundedRequirements] = build([](IrProgram&, IrBlock&, auto& emit, auto& constant, IrValue& active) { + auto& user = emit(IrOpcode::GetUserData, IrType::U32, {&constant(0u)}); + emit(IrOpcode::WriteSharedU32, IrType::Void, {&user, &user, &active}, 0u); + }); + require(unbounded == FunctionLdsDwordLimit && unboundedRequirements.functionLdsDwords == FunctionLdsDwordLimit, "function LDS: an address without a bound must keep the full array"); + + const auto wrapping = build([](IrProgram&, IrBlock&, auto& emit, auto& constant, IrValue& active) { + emit(IrOpcode::LoadSharedU32, IrType::U32, {&constant(0x20u), &active}, 3u); + }).first; + require(wrapping == FunctionLdsDwordLimit, "function LDS: an offset that can wrap the address must keep the full array"); + + const auto loop = build([](IrProgram& program, IrBlock& block, auto& emit, auto& constant, IrValue& active) { + auto& phi = program.CreateValue(IrOpcode::Phi, IrType::U32); + block.AppendInstruction(&phi); + auto& next = emit(IrOpcode::IAdd32, IrType::U32, {&phi, &constant(4u)}); + phi.AddPhiOperand(&block, &constant(0u)); + phi.AddPhiOperand(&block, &next); + emit(IrOpcode::WriteSharedU32, IrType::Void, {&phi, &next, &active}, 0u); + }).first; + require(loop == FunctionLdsDwordLimit, "function LDS: a loop-carried address must keep the full array"); + + const auto joined = build([](IrProgram& program, IrBlock& block, auto& emit, auto& constant, IrValue& active) { + auto& phi = program.CreateValue(IrOpcode::Phi, IrType::U32); + block.AppendInstruction(&phi); + phi.AddPhiOperand(&block, &constant(0x40u)); + phi.AddPhiOperand(&block, &constant(0x3fcu)); + emit(IrOpcode::WriteSharedU32, IrType::Void, {&phi, &constant(1u), &active}, 0u); + }).first; + require(joined == 256u, "function LDS: a phi of bounded addresses must take its largest"); + + const auto unsized = build([](IrProgram&, IrBlock&, auto& emit, auto& constant, IrValue& active) { + emit(IrOpcode::LoadShared, IrType::U32, {&constant(0u), &active}, 0u); + }).first; + require(unsized == FunctionLdsDwordLimit, "function LDS: an access without a known width must keep the full array"); +} + int main() { try { using namespace ShaderRecompiler; @@ -908,6 +999,7 @@ int main() { verifyComputedTexelOffsets(); verifyWaveUniformValues(); verifyTwoLaneUniformValues(); + verifyFunctionLdsBound(); #if ANYPS5_ENABLE_SPIRV_TOOLS const std::vector minimalSpirv{ 0x07230203u, 0x00010000u, 0u, 5u, 0u, diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp index 2a2437dc8..0492acf15 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp @@ -17,6 +17,7 @@ struct SpirvRequirements { bool imageGatherExtended = false; bool functionLds = false; bool ldsLock = false; + std::uint32_t functionLdsDwords = 0; bool functionScratch = false; bool pixelValidMask = false; bool bufferInt64Atomics = false; @@ -28,6 +29,8 @@ struct SpirvRequirements { }; [[nodiscard]] SpirvRequirements AnalyzeProgramRequirements(const IrProgram& program); +inline constexpr std::uint32_t FunctionLdsDwordLimit = 8192u; +[[nodiscard]] std::uint32_t FunctionLdsDwords(const IrProgram& program); [[nodiscard]] std::unordered_set WaveUniformValues(const IrProgram& program); [[nodiscard]] bool IsWaveMaskBranch(BranchCondition condition); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp index 8c1a4e4a6..d85b226ab 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp @@ -1,6 +1,9 @@ #include "SpirvBackend/SpirvAnalysis.hpp" +#include +#include #include #include +#include namespace ShaderRecompiler { @@ -63,6 +66,178 @@ bool LaneSource(const IrProgram& program, const IrValue& value) { } +namespace { + +class AddressBound { +public: + std::optional Of(const IrValue* value) { + if (value == nullptr) return std::nullopt; + value = value->Resolve(); + if (value == nullptr) return std::nullopt; + if (value->HasImmediate()) return static_cast(value->ImmediateU32()); + if (const auto known = bounds.find(value); known != bounds.end()) return known->second; + if (!visiting.insert(value).second || visiting.size() > MaxDepth) return std::nullopt; + const auto result = compute(*value); + visiting.erase(value); + bounds.emplace(value, result); + return result; + } + +private: + static constexpr std::size_t MaxDepth = 256; + static constexpr std::uint64_t Max32 = 0xffffffffull; + + static std::optional fits(std::uint64_t value) { + return value <= Max32 ? std::optional(value) : std::nullopt; + } + static std::uint64_t ones(std::uint64_t value) { + std::uint64_t mask = 0; + while (mask < value) mask = (mask << 1u) | 1u; + return mask; + } + + std::optional compute(const IrValue& value) { + const auto argument = [&](std::size_t index) { return index < value.ArgumentCount() ? Of(value.Argument(index)) : std::nullopt; }; + switch (value.Opcode()) { + case IrOpcode::LaneId: + return 63u; + case IrOpcode::IAdd32: { + const auto a = argument(0), b = argument(1); + if (!a || !b) return std::nullopt; + return fits(*a + *b); + } + case IrOpcode::IMul32: { + const auto a = argument(0), b = argument(1); + if (!a || !b || (*b != 0 && *a > Max32 / *b)) return std::nullopt; + return *a * *b; + } + case IrOpcode::ShiftLeftLogical32: + case IrOpcode::IShiftLeft32: { + const auto a = argument(0), b = argument(1); + if (!a || !b || *b >= 32u) return std::nullopt; + return fits(*a << *b); + } + case IrOpcode::ShiftRightLogical32: + case IrOpcode::IShiftRightLogical32: + return argument(0); + case IrOpcode::BitwiseAnd32: + case IrOpcode::IAnd32: { + const auto a = argument(0), b = argument(1); + if (a && b) return std::min(*a, *b); + return a ? a : b; + } + case IrOpcode::BitwiseOr32: + case IrOpcode::IOr32: + case IrOpcode::BitwiseXor32: + case IrOpcode::IXor32: { + const auto a = argument(0), b = argument(1); + if (!a || !b) return std::nullopt; + return ones(std::max(*a, *b)); + } + case IrOpcode::UMin32: { + const auto a = argument(0), b = argument(1); + if (a && b) return std::min(*a, *b); + return a ? a : b; + } + case IrOpcode::UMax32: { + const auto a = argument(0), b = argument(1); + if (!a || !b) return std::nullopt; + return std::max(*a, *b); + } + case IrOpcode::SelectU32: { + const auto a = argument(1), b = argument(2); + if (!a || !b) return std::nullopt; + return std::max(*a, *b); + } + case IrOpcode::BitFieldUExtract: { + const auto width = argument(2); + if (width && *width < 32u) return (1ull << *width) - 1u; + return argument(0); + } + case IrOpcode::Phi: { + std::uint64_t result = 0; + for (std::size_t index = 0; index < value.ArgumentCount(); ++index) { + const auto incoming = Of(value.Argument(index)); + if (!incoming) return std::nullopt; + result = std::max(result, *incoming); + } + return result; + } + default: + return std::nullopt; + } + } + + std::unordered_map> bounds; + std::unordered_set visiting; +}; + +std::uint32_t SharedAccessDwords(IrOpcode opcode) { + switch (opcode) { + case IrOpcode::LoadSharedU8: + case IrOpcode::LoadSharedU16: + case IrOpcode::LoadSharedU32: + case IrOpcode::WriteSharedU8: + case IrOpcode::WriteSharedU16: + case IrOpcode::WriteSharedU32: + case IrOpcode::SharedAtomicFMin32: + case IrOpcode::SharedAtomicFMax32: + case IrOpcode::SharedAtomicSwap32: + case IrOpcode::SharedAtomicIAdd32: + case IrOpcode::SharedAtomicISub32: + case IrOpcode::SharedAtomicInc32: + case IrOpcode::SharedAtomicDec32: + case IrOpcode::SharedAtomicSMin32: + case IrOpcode::SharedAtomicUMin32: + case IrOpcode::SharedAtomicSMax32: + case IrOpcode::SharedAtomicUMax32: + case IrOpcode::SharedAtomicAnd32: + case IrOpcode::SharedAtomicOr32: + case IrOpcode::SharedAtomicXor32: + case IrOpcode::SharedAtomicRsub32: + case IrOpcode::SharedAtomicFAdd32: + case IrOpcode::SharedAtomicCmpst32: + case IrOpcode::SharedAtomicCmpstF32: + case IrOpcode::SharedAtomicMskor32: + case IrOpcode::SharedAtomicWrap32: + return 1u; + case IrOpcode::LoadSharedU32x2: + case IrOpcode::WriteSharedU32x2: + return 2u; + case IrOpcode::LoadSharedU32x3: + case IrOpcode::WriteSharedU32x3: + return 3u; + case IrOpcode::LoadSharedU32x4: + case IrOpcode::WriteSharedU32x4: + return 4u; + default: + return 0u; + } +} + +} + +std::uint32_t FunctionLdsDwords(const IrProgram& program) { + AddressBound bound; + std::uint64_t needed = 0; + for (const IrBlock* block : program.BlockOrder()) { + for (const IrValue* inst : block->Instructions()) { + if (SharedAccessOf(inst->Opcode()) == SharedAccess::None) continue; + const auto index = inst->Flags().index; + if (index >= program.Resources().memoryInfo.size()) return FunctionLdsDwordLimit; + const auto& memory = program.Resources().memoryInfo[index]; + if (memory.kind != ResourceKind::Lds) continue; + const auto dwords = SharedAccessDwords(inst->Opcode()); + if (dwords == 0u || inst->ArgumentCount() == 0) return FunctionLdsDwordLimit; + const auto address = bound.Of(inst->Argument(0)); + if (!address || *address + memory.offset > 0xffffffffull) return FunctionLdsDwordLimit; + needed = std::max(needed, ((*address + memory.offset) >> 2u) + dwords); + if (needed >= FunctionLdsDwordLimit) return FunctionLdsDwordLimit; + } + } + return static_cast(std::min(std::max((needed + 63u) & ~63ull, 64u), FunctionLdsDwordLimit)); +} + SpirvRequirements AnalyzeProgramRequirements(const IrProgram& program) { SpirvRequirements requirements {}; for (const IrBlock* block : program.BlockOrder()) { @@ -197,6 +372,7 @@ SpirvRequirements AnalyzeProgramRequirements(const IrProgram& program) { requirements.subgroupBallot = true; } } + if (requirements.functionLds) requirements.functionLdsDwords = FunctionLdsDwords(program); return requirements; } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp index 627319a5a..9c7ee7228 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp @@ -11,8 +11,6 @@ namespace ShaderRecompiler { namespace { - constexpr std::uint32_t FunctionLdsDwords = 8192u; - [[noreturn]] void FailEmit(const std::string& reason) { throw std::runtime_error("SPIR-V module emission failed: " + reason); } @@ -96,7 +94,8 @@ void EmitMemoryOffsets(SpirvEmitterState& state) { std::uint32_t LdsDwordCount(const SpirvEmitterState& state) { const auto* workgroup = ShaderWorkgroupInput(state); - return workgroup != nullptr ? workgroup->ldsSizeDwords : FunctionLdsDwords; + if (workgroup != nullptr) return workgroup->ldsSizeDwords; + return state.requirements.functionLdsDwords != 0u ? state.requirements.functionLdsDwords : FunctionLdsDwordLimit; } std::uint32_t EmitLdsLockPointer(SpirvEmitterState& state) { From 31af5a9d2232b9950ecd977c59f2ce29ca175b20 Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Mon, 5 Oct 2026 13:36:44 +1300 Subject: [PATCH 091/164] perf(recompiler): index lane-strided per-invocation LDS by constant slots Stages without workgroup memory keep LDS in a per-invocation Function array. With the array sized to the addresses it reaches (#586), PPSA21564's intro pixel shader still takes 864 bytes of local memory per thread on NVIDIA: its accesses are lane * 4 + {0, 256, 512}, so the index stays dynamic and the 192-dword array lives in local memory. When every LDS access's address is lane * stride + constant (lane id, constants, adds, multiplies and left shifts), with one stride, a whole number of dwords, for all of them and no wrap (the bound analysis already proves that), the emitter now addresses each access by its constant plus its instruction offset. The lane id is the same for every access of an invocation, so taking lane * stride out maps the addresses one to one, keeps which accesses alias and keeps every access in bounds; the array sees only constant indices and the driver keeps it in registers. Shaders with any other address form are emitted as before. --- .../libSceAgcDriver/tests/ShaderMemory.cpp | 49 ++++++ .../tests/execution/PixelLdsLaneSlots.cpp | 165 ++++++++++++++++++ .../include/SpirvBackend/SpirvAnalysis.hpp | 3 + .../SpirvBackend/src/SpirvAnalysis.cpp | 74 +++++++- .../SpirvMemory/SpirvMemoryInstructions.cpp | 5 + 5 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/PixelLdsLaneSlots.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index dad622661..a65c7d9e5 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -937,6 +937,54 @@ void verifyFunctionLdsBound() { }); require(laneSlots == 192u, "function LDS: lane-strided slots up to byte 767 must take 192 dwords"); require(laneRequirements.functionLds && laneRequirements.functionLdsDwords == 192u, "function LDS: the requirements do not carry the bounded size"); + { + std::vector rebased; + for (const auto& [inst, address] : laneRequirements.functionLdsAddresses) rebased.push_back(address); + std::sort(rebased.begin(), rebased.end()); + require(rebased == std::vector{0u, 0u, 0u, 0u}, "function LDS: lane-strided slots must be addressed without the lane term"); + } + + const auto [mixed, mixedRequirements] = build([](IrProgram&, IrBlock&, auto& emit, auto& constant, IrValue& active) { + auto& lane = emit(IrOpcode::LaneId, IrType::U32, {}); + auto& address = emit(IrOpcode::ShiftLeftLogical32, IrType::U32, {&lane, &constant(2u)}); + emit(IrOpcode::WriteSharedU32, IrType::Void, {&address, &lane, &active}, 0u); + emit(IrOpcode::LoadSharedU32, IrType::U32, {&constant(8u), &active}, 0u); + }); + require(mixed == 64u && mixedRequirements.functionLdsAddresses.empty(), "function LDS: a constant address beside lane-strided ones must keep the lane term"); + + const auto [strides, stridesRequirements] = build([](IrProgram&, IrBlock&, auto& emit, auto& constant, IrValue& active) { + auto& lane = emit(IrOpcode::LaneId, IrType::U32, {}); + auto& four = emit(IrOpcode::ShiftLeftLogical32, IrType::U32, {&lane, &constant(2u)}); + auto& eight = emit(IrOpcode::IMul32, IrType::U32, {&lane, &constant(8u)}); + emit(IrOpcode::WriteSharedU32, IrType::Void, {&four, &lane, &active}, 0u); + emit(IrOpcode::LoadSharedU32, IrType::U32, {&eight, &active}, 0u); + }); + require(strides == 128u && stridesRequirements.functionLdsAddresses.empty(), "function LDS: two lane strides must keep the lane term"); + + const auto [halfWord, halfWordRequirements] = build([](IrProgram&, IrBlock&, auto& emit, auto& constant, IrValue& active) { + auto& lane = emit(IrOpcode::LaneId, IrType::U32, {}); + auto& address = emit(IrOpcode::ShiftLeftLogical32, IrType::U32, {&lane, &constant(1u)}); + emit(IrOpcode::WriteSharedU32, IrType::Void, {&address, &lane, &active}, 0u); + }); + require(halfWord == 64u && halfWordRequirements.functionLdsAddresses.empty(), "function LDS: a lane stride that is not whole dwords must keep the lane term"); + + const auto [summed, summedRequirements] = build([](IrProgram&, IrBlock&, auto& emit, auto& constant, IrValue& active) { + auto& lane = emit(IrOpcode::LaneId, IrType::U32, {}); + auto& scaled = emit(IrOpcode::IMul32, IrType::U32, {&constant(16u), &lane}); + auto& low = emit(IrOpcode::IAdd32, IrType::U32, {&scaled, &constant(4u)}); + auto& other = emit(IrOpcode::LaneId, IrType::U32, {}); + auto& shifted = emit(IrOpcode::ShiftLeftLogical32, IrType::U32, {&other, &constant(4u)}); + auto& high = emit(IrOpcode::IAdd32, IrType::U32, {&constant(1024u), &shifted}); + emit(IrOpcode::WriteSharedU32x2, IrType::Void, {&low, &lane, &lane, &active}, 0u); + emit(IrOpcode::LoadSharedU32x4, IrType::U32x4, {&high, &active}, 1u); + }); + { + std::vector rebased; + for (const auto& [inst, address] : summedRequirements.functionLdsAddresses) rebased.push_back(address); + std::sort(rebased.begin(), rebased.end()); + require(rebased == std::vector{4u, 1024u}, "function LDS: lane ids of separate instructions with one stride must share the rebase"); + require(summedRequirements.functionLdsDwords == 384u && summed == 576u, "function LDS: the rebased array must hold the rebased addresses"); + } const auto [wide, wideRequirements] = build([](IrProgram&, IrBlock&, auto& emit, auto& constant, IrValue& active) { auto& lane = emit(IrOpcode::LaneId, IrType::U32, {}); @@ -947,6 +995,7 @@ void verifyFunctionLdsBound() { }); require(wide == 128u, "function LDS: a 4-dword access at byte 0x100 must take 68 dwords, rounded to 128"); require(wideRequirements.functionLdsDwords == 128u, "function LDS: the requirements do not carry the wide access's size"); + require(wideRequirements.functionLdsAddresses.empty(), "function LDS: a selected address must keep the lane term"); const auto [unbounded, unboundedRequirements] = build([](IrProgram&, IrBlock&, auto& emit, auto& constant, IrValue& active) { auto& user = emit(IrOpcode::GetUserData, IrType::U32, {&constant(0u)}); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PixelLdsLaneSlots.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelLdsLaneSlots.cpp new file mode 100644 index 000000000..a1aa594a1 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelLdsLaneSlots.cpp @@ -0,0 +1,165 @@ +#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 + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Sentinel = 0xdeadbeefu; +constexpr std::uint32_t Width = 64; +constexpr std::uint32_t Height = 32; +alignas(256) std::array Pixels{}; +alignas(256) std::array Results{}; + +alignas(256) constexpr std::array VertexCode{ + 0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000, +}; + +std::vector PixelCode() { + std::vector code{0x7e040f00, 0x7e060f01, 0xd5430006, 0x0409ff03, Width}; + const auto vop2 = [&](std::uint32_t opcode, std::uint32_t destination, std::uint32_t source, std::uint32_t vector) { + code.push_back(opcode | (destination << 17u) | (vector << 9u) | source); + }; + vop2(0x34000000, 8, 0x82, 6); + code.push_back(0xbefc0380); + for (std::uint32_t bank = 0; bank < 3; ++bank) { + vop2(0x4a000000, 5, 0x80 + bank, 8); + code.push_back(0xdac00000 | (bank * 256u)); + code.push_back(0x00000500); + } + code.push_back(0xbf8cc07f); + for (std::uint32_t bank = 0; bank < 3; ++bank) { + code.push_back(0xdac40000 | (((bank + 1u) % 3u) * 256u)); + code.push_back((10u + bank) << 24u); + } + code.push_back(0xbf8cc07f); + for (std::uint32_t bank = 0; bank < 3; ++bank) { + code.push_back(0xe0702000 | (bank * 4u)); + code.push_back(0x80010006 | ((10u + bank) << 8u)); + } + code.insert(code.end(), {0x7e0e02f2, 0xf800180f, 0x07070707, 0xbf810000}); + return code; +} + +std::array BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu}; +} + +constexpr std::array, 3> Triangles{{ + {-1.0f, -1.0f, 0.5f, 1.0f}, {3.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 3.0f, 0.5f, 1.0f} +}}; + +void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize) { + const auto& triangles = Triangles; + const auto pixelCode = PixelCode(); + Pixels.fill(std::byte{0}); + Results.fill(Sentinel); + const auto target = device.Target(); + + std::vector vertexUserData(4, 0u); + const auto vertexBuffer = BufferDescriptor(triangles.data(), 16u, static_cast(triangles.size())); + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {waveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertex.useCache = false; + const auto vertexResult = ShaderRecompiler::Recompile(vertex); + const auto vertexPush = static_cast(vertexResult.pushConstants.size()); + + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.wave32 = waveSize == 32u; + pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionX) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionY); + pixel.posX = true; + pixel.posY = true; + pixel.targetOutputMode[0] = 9; + pixel.targetExportMapping.fill(0xe4u); + std::vector pixelUserData(8, 0u); + const auto results = BufferDescriptor(Results.data(), 16u, Width * Height); + std::copy(results.begin(), results.end(), pixelUserData.begin() + 4); + const std::array pixelMemory{{{reinterpret_cast(pixelCode.data()), std::as_bytes(std::span(pixelCode))}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, + {waveSize, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, + target, + {0, 0, vertexPush, 128 - vertexPush} + }; + fragment.useCache = false; + const auto pixelResult = ShaderRecompiler::Recompile(fragment); + const std::array shaders{{ + {ShaderStage::Vertex, &vertexResult, 0}, + {ShaderStage::Fragment, &pixelResult, vertexPush} + }}; + + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, waveSize, waveSize, std::nullopt, std::nullopt}; + state.color = {reinterpret_cast(Pixels.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Pixels.size(), 0xe4u}; + state.colors = {state.color}; + state.hasColorTarget = true; + state.renderExtent = {Width, Height}; + state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + state.viewport = {0, static_cast(Height), static_cast(Width), -static_cast(Height), 0, 1}; + state.negativeOneToOne = false; + state.scissor = {{0, 0}, {Width, Height}}; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blend.colorWriteMask = 15; + state.blends = {state.blend}; + state.blendConstants = {}; + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(triangles.size()), 0, 1, 0, false}; + device.Draw(state, draw, shaders); + device.WaitIdle(); +} + +void Check(std::uint32_t waveSize) { + const auto what = "pixel LDS lane slots wave" + std::to_string(waveSize); + for (std::uint32_t pixel = 0; pixel < Width * Height; ++pixel) { + for (std::uint32_t channel = 0; channel < 4; ++channel) { + Require(Pixels[pixel * 4u + channel] == std::byte{255}, what + ": missing color at pixel " + std::to_string(pixel)); + } + for (std::uint32_t bank = 0; bank < 3; ++bank) { + const auto expected = pixel * 4u + (bank + 1u) % 3u; + const auto actual = Results[pixel * 4u + bank]; + Require(actual == expected, what + ": pixel " + std::to_string(pixel) + " bank " + std::to_string(bank) + " expected " + std::to_string(expected) + " got " + std::to_string(actual)); + } + Require(Results[pixel * 4u + 3u] == Sentinel, what + ": output stride guard overwritten"); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < 32u) { + std::printf("skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); + return VulkanTestSkipped; + } + for (const auto waveSize : {64u, 32u}) { + std::fprintf(stderr, "pixel LDS lane slots wave%u\n", waveSize); + for (std::uint32_t pass = 0; pass < 3; ++pass) { + Draw(*device, waveSize); + Check(waveSize); + } + } + std::puts("pixel LDS lane slot tests passed: 6 draws, 12288 pixels, 36864 LDS results"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp index 0492acf15..0b433bfeb 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp @@ -4,6 +4,7 @@ #include "IntermediateRepresentation/IrProgram.hpp" #include #include +#include #include #include @@ -18,6 +19,7 @@ struct SpirvRequirements { bool functionLds = false; bool ldsLock = false; std::uint32_t functionLdsDwords = 0; + std::unordered_map functionLdsAddresses; bool functionScratch = false; bool pixelValidMask = false; bool bufferInt64Atomics = false; @@ -31,6 +33,7 @@ struct SpirvRequirements { [[nodiscard]] SpirvRequirements AnalyzeProgramRequirements(const IrProgram& program); inline constexpr std::uint32_t FunctionLdsDwordLimit = 8192u; [[nodiscard]] std::uint32_t FunctionLdsDwords(const IrProgram& program); +[[nodiscard]] std::unordered_map FunctionLdsLaneAddresses(const IrProgram& program); [[nodiscard]] std::unordered_set WaveUniformValues(const IrProgram& program); [[nodiscard]] bool IsWaveMaskBranch(BranchCondition condition); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp index d85b226ab..82fdd645b 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp @@ -238,6 +238,67 @@ std::uint32_t FunctionLdsDwords(const IrProgram& program) { return static_cast(std::min(std::max((needed + 63u) & ~63ull, 64u), FunctionLdsDwordLimit)); } +namespace { + +struct LaneAffine { + std::uint64_t stride = 0; + std::uint64_t constant = 0; +}; + +std::optional LaneAffineOf(const IrValue* value, std::uint32_t depth = 0) { + if (value == nullptr || depth > 64u) return std::nullopt; + value = value->Resolve(); + if (value == nullptr) return std::nullopt; + if (value->HasImmediate()) return LaneAffine{0u, value->ImmediateU32()}; + const auto argument = [&](std::size_t index) { return index < value->ArgumentCount() ? LaneAffineOf(value->Argument(index), depth + 1u) : std::nullopt; }; + switch (value->Opcode()) { + case IrOpcode::LaneId: + return LaneAffine{1u, 0u}; + case IrOpcode::IAdd32: { + const auto a = argument(0), b = argument(1); + if (!a || !b) return std::nullopt; + return LaneAffine{a->stride + b->stride, a->constant + b->constant}; + } + case IrOpcode::IMul32: { + const auto a = argument(0), b = argument(1); + if (!a || !b) return std::nullopt; + if (a->stride == 0u) return LaneAffine{b->stride * a->constant, b->constant * a->constant}; + if (b->stride == 0u) return LaneAffine{a->stride * b->constant, a->constant * b->constant}; + return std::nullopt; + } + case IrOpcode::ShiftLeftLogical32: + case IrOpcode::IShiftLeft32: { + const auto a = argument(0), b = argument(1); + if (!a || !b || b->stride != 0u || b->constant >= 32u) return std::nullopt; + return LaneAffine{a->stride << b->constant, a->constant << b->constant}; + } + default: + return std::nullopt; + } +} + +} + +std::unordered_map FunctionLdsLaneAddresses(const IrProgram& program) { + std::unordered_map addresses; + if (FunctionLdsDwords(program) >= FunctionLdsDwordLimit) return {}; + std::optional stride; + for (const IrBlock* block : program.BlockOrder()) { + for (const IrValue* inst : block->Instructions()) { + if (SharedAccessOf(inst->Opcode()) == SharedAccess::None) continue; + const auto& memory = program.Resources().memoryInfo[inst->Flags().index]; + if (memory.kind != ResourceKind::Lds) continue; + const auto affine = LaneAffineOf(inst->Argument(0)); + if (!affine || affine->stride % 4u != 0u || (stride && *stride != affine->stride)) return {}; + stride = affine->stride; + if (affine->constant + memory.offset > 0xffffffffull) return {}; + addresses.emplace(inst, static_cast(affine->constant)); + } + } + if (!stride || *stride == 0u) return {}; + return addresses; +} + SpirvRequirements AnalyzeProgramRequirements(const IrProgram& program) { SpirvRequirements requirements {}; for (const IrBlock* block : program.BlockOrder()) { @@ -372,7 +433,18 @@ SpirvRequirements AnalyzeProgramRequirements(const IrProgram& program) { requirements.subgroupBallot = true; } } - if (requirements.functionLds) requirements.functionLdsDwords = FunctionLdsDwords(program); + if (requirements.functionLds) { + requirements.functionLdsDwords = FunctionLdsDwords(program); + requirements.functionLdsAddresses = FunctionLdsLaneAddresses(program); + if (!requirements.functionLdsAddresses.empty()) { + std::uint64_t needed = 0; + for (const auto& [inst, address] : requirements.functionLdsAddresses) { + const auto offset = program.Resources().memoryInfo[inst->Flags().index].offset; + needed = std::max(needed, ((std::uint64_t{address} + offset) >> 2u) + SharedAccessDwords(inst->Opcode())); + } + requirements.functionLdsDwords = static_cast(std::max((needed + 63u) & ~63ull, 64u)); + } + } return requirements; } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp index 5df19bf32..60d891f99 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp @@ -88,6 +88,11 @@ std::uint32_t ByteAddress(SpirvValueEmitContext& ctx, const IrValue& inst, const return BufferByteAddress(ctx, inst, mem, ctx.Arg(inst, 1), ctx.Arg(inst, 2), ctx.Arg(inst, 3)); case ResourceKind::Lds: case ResourceKind::Gds: + if (mem.kind == ResourceKind::Lds) { + if (const auto found = state.requirements.functionLdsAddresses.find(&inst); found != state.requirements.functionLdsAddresses.end()) { + return ConstantU32(state, found->second + mem.offset); + } + } if (mem.offset == 0u) { return ctx.Arg(inst, 0); } From 1cc11cee57ef561d32c5bef70757dd208a690f43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 08:25:35 +0200 Subject: [PATCH 092/164] feat(shader): accept tfe and lwe on MIMG loads, stores and samples Co-Authored-By: Claude --- .../tests/execution/ImageTexelStatus.cpp | 349 ++++++++++++++++++ .../RdnaDecoder/src/RdnaImageOpDecoder.cpp | 7 +- docs/dev/TechnicalDebt.md | 1 + 3 files changed, 355 insertions(+), 2 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/ImageTexelStatus.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageTexelStatus.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageTexelStatus.cpp new file mode 100644 index 000000000..d2ff375fe --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageTexelStatus.cpp @@ -0,0 +1,349 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#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; +constexpr std::uint32_t StorageResults = 4; +constexpr std::uint32_t SampleResults = 16; +constexpr std::uint32_t StorageWidth = 32; +constexpr std::uint32_t StorageHeight = 8; +constexpr std::uint32_t SampleWidth = 64; +constexpr std::uint32_t SampleHeight = 2; +constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t Format32Float = 22; +constexpr std::uint32_t Type2D = 9; +alignas(256) std::array Buffer{}; +alignas(4096) std::array StorageTexels{}; +alignas(4096) std::array SampleTexels{}; + +alignas(256) constexpr std::array StorageCode{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xbe93030f, 0xbe92030e, 0xbe91030d, + 0xbe90030c, 0xbe8f030b, 0xbe8e030a, 0xbe8d0309, 0xbe8c0308, 0xbe8b0307, 0xbe8a0306, 0xbe890305, + 0xbe880304, 0x7e0a02ff, 0x5a7a0005, 0x7e0e02ff, 0x5a7a0007, 0x7e100300, 0x7e120280, 0xf0210108, + 0x00020408, 0xbbfd0000, 0x7e140280, 0xf0442108, 0x00020a08, 0xbf8c3f70, 0x7e120281, 0xf0230108, + 0x00020608, 0xbbfd0000, 0x7e160280, 0xf0442108, 0x00020b08, 0xbf8c3f70, 0xe0701200, 0x80000a03, + 0xe0701204, 0x80000b03, 0xe0701208, 0x80000503, 0xe070120c, 0x80000703, 0xbf810000, +}; + +alignas(256) constexpr std::array SampleCode{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xbe93030f, 0xbe92030e, 0xbe91030d, + 0xbe90030c, 0xbe8f030b, 0xbe8e030a, 0xbe8d0309, 0xbe8c0308, 0xbe8b0307, 0xbe8a0306, 0xbe890305, + 0xbe880304, 0x7e1402ff, 0x5a7a000a, 0x7e1602ff, 0x5a7a000b, 0x7e1802ff, 0x5a7a000c, 0x7e1a02ff, + 0x5a7a000d, 0x7e1c02ff, 0x5a7a000e, 0x7e1e02ff, 0x5a7a000f, 0x7e2002ff, 0x5a7a0010, 0x7e2202ff, + 0x5a7a0011, 0x7e2402ff, 0x5a7a0012, 0x7e2602ff, 0x5a7a0013, 0x7e2802ff, 0x5a7a0014, 0x7e2a02ff, + 0x5a7a0015, 0x7e2c02ff, 0x5a7a0016, 0x7e2e02ff, 0x5a7a0017, 0x7e3002ff, 0x5a7a0018, 0x7e3202ff, + 0x5a7a0019, 0xf0810108, 0x00820a04, 0xf1010108, 0x00820c04, 0x7e540280, 0xf03b0308, 0x0002112a, + 0xf0910108, 0x00821404, 0x34500081, 0x7e520281, 0xf0030108, 0x00021628, 0x7e300280, 0xf0010108, + 0x80021828, 0xbf8c3f70, 0xe0701200, 0x80000a03, 0xe0701204, 0x80000b03, 0xe0701208, 0x80000c03, + 0xe070120c, 0x80000d03, 0xe0701210, 0x80000e03, 0xe0701214, 0x80000f03, 0xe0701218, 0x80001003, + 0xe070121c, 0x80001103, 0xe0701220, 0x80001203, 0xe0701224, 0x80001303, 0xe0701228, 0x80001403, + 0xe070122c, 0x80001503, 0xe0701230, 0x80001603, 0xe0701234, 0x80001703, 0xe0701238, 0x80001803, + 0xe070123c, 0x80001903, 0xbf810000, +}; + +alignas(256) constexpr std::array, 5> RefusedCode{{ + {0xf0820108u, 0x00820a04u, 0xbf810000u}, + {0xf1020108u, 0x00820a04u, 0xbf810000u}, + {0xf1820308u, 0x00820a04u, 0xbf810000u}, + {0xf0452108u, 0x00020a08u, 0xbf810000u}, + {0xf0010108u, 0x0002ff08u, 0xbf810000u}, +}}; + +constexpr std::array, Threads> StorageInputs{{ + {0x00000000u, 0x00000000u, 0xb3c4904au}, + {0x00000000u, 0x00000005u, 0x00000000u}, + {0x00000005u, 0x00000000u, 0x7be90291u}, + {0xffffffffu, 0x00000001u, 0xffffffffu}, + {0x00000001u, 0xffffffffu, 0x9e5c77e7u}, + {0x7fffffffu, 0x00000001u, 0x7fffffffu}, + {0x80000000u, 0x80000000u, 0x4285a9afu}, + {0xffffffffu, 0xffffffffu, 0xffffffffu}, + {0x7df64fcfu, 0xf6453aa7u, 0x92191efbu}, + {0xd9895985u, 0x68518d38u, 0xd9895985u}, + {0xcf273511u, 0x138c146fu, 0x57100d0au}, + {0x2bb1b013u, 0xd6e649d7u, 0x2bb1b013u}, + {0xa95d2364u, 0x3976bad3u, 0x5bc5a782u}, + {0x4bcdebb2u, 0xa6480378u, 0x4bcdebb2u}, + {0xcbad05bau, 0xa433bde1u, 0xd1c28862u}, + {0x8f217978u, 0x860b0f2au, 0x8f217978u}, + {0x871aba07u, 0xb551a6ccu, 0x661dc63eu}, + {0x9a7f8d63u, 0x6e7133fbu, 0x9a7f8d63u}, + {0xce07ea31u, 0xe31b5068u, 0x9da9bf21u}, + {0x92b8b3e9u, 0xe399ac38u, 0x92b8b3e9u}, + {0x1b9abec0u, 0x1cbb3115u, 0x8d8433aeu}, + {0x613db786u, 0x7f5a7218u, 0x613db786u}, + {0x2084fc98u, 0xa69ea4ecu, 0x74859b95u}, + {0xb5b4acfcu, 0x1191b75bu, 0xb5b4acfcu}, + {0x8bec71e7u, 0xe0f3debeu, 0x77f93ffbu}, + {0x9f1254f3u, 0x48de1c56u, 0x9f1254f3u}, + {0xef1e9ff4u, 0xc7a1d8dbu, 0xb8ab9e91u}, + {0x9bcffc0fu, 0xc7bb053fu, 0x9bcffc0fu}, + {0x3c796f72u, 0xb5943014u, 0xf08eac42u}, + {0x436b6cdeu, 0x8333e5a8u, 0x436b6cdeu}, + {0x4457feb1u, 0xeaf89aefu, 0xfdcec3b5u}, + {0xdb26bcebu, 0x5d63880eu, 0xdb26bcebu}, +}}; + +constexpr std::array, Threads> StorageExpected{{ + {0x00000000u, 0xb3c4904au, 0x5a7a0005u, 0x5a7a0007u}, + {0x00000000u, 0x00000000u, 0x5a7a0005u, 0x5a7a0007u}, + {0x00000005u, 0x7be90291u, 0x5a7a0005u, 0x5a7a0007u}, + {0xffffffffu, 0xffffffffu, 0x5a7a0005u, 0x5a7a0007u}, + {0x00000001u, 0x9e5c77e7u, 0x5a7a0005u, 0x5a7a0007u}, + {0x7fffffffu, 0x7fffffffu, 0x5a7a0005u, 0x5a7a0007u}, + {0x80000000u, 0x4285a9afu, 0x5a7a0005u, 0x5a7a0007u}, + {0xffffffffu, 0xffffffffu, 0x5a7a0005u, 0x5a7a0007u}, + {0x7df64fcfu, 0x92191efbu, 0x5a7a0005u, 0x5a7a0007u}, + {0xd9895985u, 0xd9895985u, 0x5a7a0005u, 0x5a7a0007u}, + {0xcf273511u, 0x57100d0au, 0x5a7a0005u, 0x5a7a0007u}, + {0x2bb1b013u, 0x2bb1b013u, 0x5a7a0005u, 0x5a7a0007u}, + {0xa95d2364u, 0x5bc5a782u, 0x5a7a0005u, 0x5a7a0007u}, + {0x4bcdebb2u, 0x4bcdebb2u, 0x5a7a0005u, 0x5a7a0007u}, + {0xcbad05bau, 0xd1c28862u, 0x5a7a0005u, 0x5a7a0007u}, + {0x8f217978u, 0x8f217978u, 0x5a7a0005u, 0x5a7a0007u}, + {0x871aba07u, 0x661dc63eu, 0x5a7a0005u, 0x5a7a0007u}, + {0x9a7f8d63u, 0x9a7f8d63u, 0x5a7a0005u, 0x5a7a0007u}, + {0xce07ea31u, 0x9da9bf21u, 0x5a7a0005u, 0x5a7a0007u}, + {0x92b8b3e9u, 0x92b8b3e9u, 0x5a7a0005u, 0x5a7a0007u}, + {0x1b9abec0u, 0x8d8433aeu, 0x5a7a0005u, 0x5a7a0007u}, + {0x613db786u, 0x613db786u, 0x5a7a0005u, 0x5a7a0007u}, + {0x2084fc98u, 0x74859b95u, 0x5a7a0005u, 0x5a7a0007u}, + {0xb5b4acfcu, 0xb5b4acfcu, 0x5a7a0005u, 0x5a7a0007u}, + {0x8bec71e7u, 0x77f93ffbu, 0x5a7a0005u, 0x5a7a0007u}, + {0x9f1254f3u, 0x9f1254f3u, 0x5a7a0005u, 0x5a7a0007u}, + {0xef1e9ff4u, 0xb8ab9e91u, 0x5a7a0005u, 0x5a7a0007u}, + {0x9bcffc0fu, 0x9bcffc0fu, 0x5a7a0005u, 0x5a7a0007u}, + {0x3c796f72u, 0xf08eac42u, 0x5a7a0005u, 0x5a7a0007u}, + {0x436b6cdeu, 0x436b6cdeu, 0x5a7a0005u, 0x5a7a0007u}, + {0x4457feb1u, 0xfdcec3b5u, 0x5a7a0005u, 0x5a7a0007u}, + {0xdb26bcebu, 0xdb26bcebu, 0x5a7a0005u, 0x5a7a0007u}, +}}; + +constexpr std::array, Threads> SampleInputs{{ + {0x3c000000u, 0x3e800000u, 0x00000000u}, + {0x3d200000u, 0x3e800000u, 0x00000000u}, + {0x3d900000u, 0x3e800000u, 0x00000000u}, + {0x3dd00000u, 0x3e800000u, 0x00000000u}, + {0x3e080000u, 0x3e800000u, 0x00000000u}, + {0x3e280000u, 0x3e800000u, 0x00000000u}, + {0x3e480000u, 0x3e800000u, 0x00000000u}, + {0x3e680000u, 0x3e800000u, 0x00000000u}, + {0x3e840000u, 0x3e800000u, 0x00000000u}, + {0x3e940000u, 0x3e800000u, 0x00000000u}, + {0x3ea40000u, 0x3e800000u, 0x00000000u}, + {0x3eb40000u, 0x3e800000u, 0x00000000u}, + {0x3ec40000u, 0x3e800000u, 0x00000000u}, + {0x3ed40000u, 0x3e800000u, 0x00000000u}, + {0x3ee40000u, 0x3e800000u, 0x00000000u}, + {0x3ef40000u, 0x3e800000u, 0x00000000u}, + {0x3f020000u, 0x3e800000u, 0x00000000u}, + {0x3f0a0000u, 0x3e800000u, 0x00000000u}, + {0x3f120000u, 0x3e800000u, 0x00000000u}, + {0x3f1a0000u, 0x3e800000u, 0x00000000u}, + {0x3f220000u, 0x3e800000u, 0x00000000u}, + {0x3f2a0000u, 0x3e800000u, 0x00000000u}, + {0x3f320000u, 0x3e800000u, 0x00000000u}, + {0x3f3a0000u, 0x3e800000u, 0x00000000u}, + {0x3f420000u, 0x3e800000u, 0x00000000u}, + {0x3f4a0000u, 0x3e800000u, 0x00000000u}, + {0x3f520000u, 0x3e800000u, 0x00000000u}, + {0x3f5a0000u, 0x3e800000u, 0x00000000u}, + {0x3f620000u, 0x3e800000u, 0x00000000u}, + {0x3f6a0000u, 0x3e800000u, 0x00000000u}, + {0x3f720000u, 0x3e800000u, 0x00000000u}, + {0x3f7a0000u, 0x3e800000u, 0x00000000u}, +}}; + +constexpr std::array, Threads> SampleExpected{{ + {0x00000000u, 0x5a7a000bu, 0x42c80000u, 0x42ca0000u, 0x3f800000u, 0x00000000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x00000000u, 0x5a7a0015u, 0x42c80000u, 0x5a7a0017u, 0x00005640u, 0x5a7a0019u}, + {0x40000000u, 0x5a7a000bu, 0x42cc0000u, 0x42ce0000u, 0x40400000u, 0x40000000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x40000000u, 0x5a7a0015u, 0x42cc0000u, 0x5a7a0017u, 0x00005660u, 0x5a7a0019u}, + {0x40800000u, 0x5a7a000bu, 0x42d00000u, 0x42d20000u, 0x40a00000u, 0x40800000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x40800000u, 0x5a7a0015u, 0x42d00000u, 0x5a7a0017u, 0x00005680u, 0x5a7a0019u}, + {0x40c00000u, 0x5a7a000bu, 0x42d40000u, 0x42d60000u, 0x40e00000u, 0x40c00000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x40c00000u, 0x5a7a0015u, 0x42d40000u, 0x5a7a0017u, 0x000056a0u, 0x5a7a0019u}, + {0x41000000u, 0x5a7a000bu, 0x42d80000u, 0x42da0000u, 0x41100000u, 0x41000000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41000000u, 0x5a7a0015u, 0x42d80000u, 0x5a7a0017u, 0x000056c0u, 0x5a7a0019u}, + {0x41200000u, 0x5a7a000bu, 0x42dc0000u, 0x42de0000u, 0x41300000u, 0x41200000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41200000u, 0x5a7a0015u, 0x42dc0000u, 0x5a7a0017u, 0x000056e0u, 0x5a7a0019u}, + {0x41400000u, 0x5a7a000bu, 0x42e00000u, 0x42e20000u, 0x41500000u, 0x41400000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41400000u, 0x5a7a0015u, 0x42e00000u, 0x5a7a0017u, 0x00005700u, 0x5a7a0019u}, + {0x41600000u, 0x5a7a000bu, 0x42e40000u, 0x42e60000u, 0x41700000u, 0x41600000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41600000u, 0x5a7a0015u, 0x42e40000u, 0x5a7a0017u, 0x00005720u, 0x5a7a0019u}, + {0x41800000u, 0x5a7a000bu, 0x42e80000u, 0x42ea0000u, 0x41880000u, 0x41800000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41800000u, 0x5a7a0015u, 0x42e80000u, 0x5a7a0017u, 0x00005740u, 0x5a7a0019u}, + {0x41900000u, 0x5a7a000bu, 0x42ec0000u, 0x42ee0000u, 0x41980000u, 0x41900000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41900000u, 0x5a7a0015u, 0x42ec0000u, 0x5a7a0017u, 0x00005760u, 0x5a7a0019u}, + {0x41a00000u, 0x5a7a000bu, 0x42f00000u, 0x42f20000u, 0x41a80000u, 0x41a00000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41a00000u, 0x5a7a0015u, 0x42f00000u, 0x5a7a0017u, 0x00005780u, 0x5a7a0019u}, + {0x41b00000u, 0x5a7a000bu, 0x42f40000u, 0x42f60000u, 0x41b80000u, 0x41b00000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41b00000u, 0x5a7a0015u, 0x42f40000u, 0x5a7a0017u, 0x000057a0u, 0x5a7a0019u}, + {0x41c00000u, 0x5a7a000bu, 0x42f80000u, 0x42fa0000u, 0x41c80000u, 0x41c00000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41c00000u, 0x5a7a0015u, 0x42f80000u, 0x5a7a0017u, 0x000057c0u, 0x5a7a0019u}, + {0x41d00000u, 0x5a7a000bu, 0x42fc0000u, 0x42fe0000u, 0x41d80000u, 0x41d00000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41d00000u, 0x5a7a0015u, 0x42fc0000u, 0x5a7a0017u, 0x000057e0u, 0x5a7a0019u}, + {0x41e00000u, 0x5a7a000bu, 0x43000000u, 0x43010000u, 0x41e80000u, 0x41e00000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41e00000u, 0x5a7a0015u, 0x43000000u, 0x5a7a0017u, 0x00005800u, 0x5a7a0019u}, + {0x41f00000u, 0x5a7a000bu, 0x43020000u, 0x43030000u, 0x41f80000u, 0x41f00000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41f00000u, 0x5a7a0015u, 0x43020000u, 0x5a7a0017u, 0x00005810u, 0x5a7a0019u}, + {0x42000000u, 0x5a7a000bu, 0x43040000u, 0x43050000u, 0x42040000u, 0x42000000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42000000u, 0x5a7a0015u, 0x43040000u, 0x5a7a0017u, 0x00005820u, 0x5a7a0019u}, + {0x42080000u, 0x5a7a000bu, 0x43060000u, 0x43070000u, 0x420c0000u, 0x42080000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42080000u, 0x5a7a0015u, 0x43060000u, 0x5a7a0017u, 0x00005830u, 0x5a7a0019u}, + {0x42100000u, 0x5a7a000bu, 0x43080000u, 0x43090000u, 0x42140000u, 0x42100000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42100000u, 0x5a7a0015u, 0x43080000u, 0x5a7a0017u, 0x00005840u, 0x5a7a0019u}, + {0x42180000u, 0x5a7a000bu, 0x430a0000u, 0x430b0000u, 0x421c0000u, 0x42180000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42180000u, 0x5a7a0015u, 0x430a0000u, 0x5a7a0017u, 0x00005850u, 0x5a7a0019u}, + {0x42200000u, 0x5a7a000bu, 0x430c0000u, 0x430d0000u, 0x42240000u, 0x42200000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42200000u, 0x5a7a0015u, 0x430c0000u, 0x5a7a0017u, 0x00005860u, 0x5a7a0019u}, + {0x42280000u, 0x5a7a000bu, 0x430e0000u, 0x430f0000u, 0x422c0000u, 0x42280000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42280000u, 0x5a7a0015u, 0x430e0000u, 0x5a7a0017u, 0x00005870u, 0x5a7a0019u}, + {0x42300000u, 0x5a7a000bu, 0x43100000u, 0x43110000u, 0x42340000u, 0x42300000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42300000u, 0x5a7a0015u, 0x43100000u, 0x5a7a0017u, 0x00005880u, 0x5a7a0019u}, + {0x42380000u, 0x5a7a000bu, 0x43120000u, 0x43130000u, 0x423c0000u, 0x42380000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42380000u, 0x5a7a0015u, 0x43120000u, 0x5a7a0017u, 0x00005890u, 0x5a7a0019u}, + {0x42400000u, 0x5a7a000bu, 0x43140000u, 0x43150000u, 0x42440000u, 0x42400000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42400000u, 0x5a7a0015u, 0x43140000u, 0x5a7a0017u, 0x000058a0u, 0x5a7a0019u}, + {0x42480000u, 0x5a7a000bu, 0x43160000u, 0x43170000u, 0x424c0000u, 0x42480000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42480000u, 0x5a7a0015u, 0x43160000u, 0x5a7a0017u, 0x000058b0u, 0x5a7a0019u}, + {0x42500000u, 0x5a7a000bu, 0x43180000u, 0x43190000u, 0x42540000u, 0x42500000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42500000u, 0x5a7a0015u, 0x43180000u, 0x5a7a0017u, 0x000058c0u, 0x5a7a0019u}, + {0x42580000u, 0x5a7a000bu, 0x431a0000u, 0x431b0000u, 0x425c0000u, 0x42580000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42580000u, 0x5a7a0015u, 0x431a0000u, 0x5a7a0017u, 0x000058d0u, 0x5a7a0019u}, + {0x42600000u, 0x5a7a000bu, 0x431c0000u, 0x431d0000u, 0x42640000u, 0x42600000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42600000u, 0x5a7a0015u, 0x431c0000u, 0x5a7a0017u, 0x000058e0u, 0x5a7a0019u}, + {0x42680000u, 0x5a7a000bu, 0x431e0000u, 0x431f0000u, 0x426c0000u, 0x42680000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42680000u, 0x5a7a0015u, 0x431e0000u, 0x5a7a0017u, 0x000058f0u, 0x5a7a0019u}, + {0x42700000u, 0x5a7a000bu, 0x43200000u, 0x43210000u, 0x42740000u, 0x42700000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42700000u, 0x5a7a0015u, 0x43200000u, 0x5a7a0017u, 0x00005900u, 0x5a7a0019u}, + {0x42780000u, 0x5a7a000bu, 0x43220000u, 0x43230000u, 0x427c0000u, 0x42780000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42780000u, 0x5a7a0015u, 0x43220000u, 0x5a7a0017u, 0x00005910u, 0x5a7a0019u}, +}}; + +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::array TextureDescriptor(const void* data, std::uint32_t format, std::uint32_t width, std::uint32_t height) { + const auto address = static_cast(reinterpret_cast(data)); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (format << 20u) | (((width - 1u) & 3u) << 30u), + ((width - 1u) >> 2u) | ((height - 1u) << 14u), + 0xfacu | (Type2D << 28u), + 0u, 0u, 0u, 0u, + }; +} + +std::array SamplerDescriptor() { + return {0u, 0xfffu << 12u, 1u << 26u, 0u}; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::vector& userData) { + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void FillBuffer(const std::array, Threads>& inputs) { + Buffer.fill(0xdeadbeefu); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::copy(inputs[tid].begin(), inputs[tid].end(), Buffer.begin() + tid * Inputs); + Buffer[tid * Inputs + 3u] = 0u; + } +} + +void RunStorage(AgcDriver::VulkanDevice& device) { + FillBuffer(StorageInputs); + StorageTexels.fill(0xdeadbeefu); + std::vector userData(16, 0u); + const auto buffer = BufferDescriptor(Buffer.data(), static_cast(Buffer.size() * 4u)); + const auto texture = TextureDescriptor(StorageTexels.data(), Format32UInt, StorageWidth, StorageHeight); + std::copy(buffer.begin(), buffer.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + Dispatch(device, StorageCode, userData); +} + +void RunSample(AgcDriver::VulkanDevice& device) { + FillBuffer(SampleInputs); + const auto mips = AgcDriver::Graphics::ComputeMipLayout(AgcDriver::Graphics::TextureTileMode::kLinear, Format32Float, SampleWidth, SampleHeight, 1); + Require(AgcDriver::Graphics::ComputeSurfaceSize(mips, 1) <= SampleTexels.size() * 4u, "image texel status: the texture does not fit the texel storage"); + SampleTexels.fill(0u); + for (std::uint32_t y = 0; y < SampleHeight; ++y) { + for (std::uint32_t x = 0; x < SampleWidth; ++x) { + SampleTexels[(mips[0].tiledOffset + static_cast(y) * mips[0].pitchBytes) / 4u + x] = std::bit_cast(static_cast(x + 100u * y)); + } + } + std::vector userData(16, 0u); + const auto buffer = BufferDescriptor(Buffer.data(), static_cast(Buffer.size() * 4u)); + const auto texture = TextureDescriptor(SampleTexels.data(), Format32Float, SampleWidth, SampleHeight); + const auto sampler = SamplerDescriptor(); + std::copy(buffer.begin(), buffer.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + std::copy(sampler.begin(), sampler.end(), userData.begin() + 12); + Dispatch(device, SampleCode, userData); +} + +void CheckRefused(AgcDriver::VulkanDevice& device) { + constexpr std::array names{"image_sample lwe", "image_gather4 lwe", "image_get_lod lwe", "image_atomic_add tfe", "image_load tfe status past v255"}; + std::vector userData(16, 0u); + for (std::uint32_t i = 0; i < RefusedCode.size(); ++i) { + const std::span code(RefusedCode[i]); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + bool refused = false; + try { + (void)ShaderRecompiler::Recompile(request); + } catch (const std::exception& error) { + refused = std::string(error.what()).find("MIMG") != std::string::npos; + } + Require(refused, std::string(names[i]) + " was not refused"); + } +} + +void Expect(const char* name, std::uint32_t tid, std::uint32_t actual, std::uint32_t expected) { + char message[160]; + std::snprintf(message, sizeof(message), "%s: thread %u is 0x%08x, expected 0x%08x", name, tid, actual, expected); + Require(actual == expected, message); +} + +void CheckStorage() { + constexpr std::array names{"image_store tfe", "image_store tfe lwe", "image_store tfe data status", "image_store tfe lwe data status"}; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + for (std::uint32_t j = 0; j < StorageResults; ++j) { + Expect(names[j], tid, Buffer[Threads * Inputs + tid * Results + j], StorageExpected[tid][j]); + } + } +} + +void CheckSample() { + constexpr std::array names{"image_sample tfe", "image_sample tfe status", "image_gather4 tfe x", "image_gather4 tfe y", "image_gather4 tfe z", "image_gather4 tfe w", "image_gather4 tfe status", "image_get_resinfo tfe lwe width", "image_get_resinfo tfe lwe height", "image_get_resinfo tfe lwe status", "image_sample_l tfe", "image_sample_l tfe status", "image_load tfe lwe", "image_load tfe lwe status", "image_load tfe d16", "image_load tfe d16 status"}; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + for (std::uint32_t j = 0; j < SampleResults; ++j) { + Expect(names[j], tid, Buffer[Threads * Inputs + tid * Results + j], SampleExpected[tid][j]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + RunStorage(*device); + CheckStorage(); + RunSample(*device); + CheckSample(); + CheckRefused(*device); + std::puts("image texel status tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp index a568080a1..53e957a5e 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp @@ -313,7 +313,9 @@ RdnaInstruction DecodeRdnaMimg(std::uint32_t programCounter, std::span> 18u) & 0x7Fu) | ((word0 & 1u) << 7u); const auto& info = lookupOpcode(opcode); - const auto reservedWord0 = info.sample || info.gather ? 0x00035040u : info.atomic ? 0x000340C0u : 0x00034040u; + const bool samplerOp = info.sample || info.gather || opcode == 0x60u; + const auto texelStatusBits = samplerOp ? 0x00010000u : opcode <= 0x0eu ? 0x00030000u : 0u; + const auto reservedWord0 = (info.sample || info.gather ? 0x00035040u : info.atomic ? 0x000340C0u : 0x00034040u) & ~texelStatusBits; if ((word0 & reservedWord0) != 0u || (word1 & 0x3C000000u) != 0u) { char message[96]; std::snprintf(message, sizeof(message), "unsupported or reserved MIMG control bits (words %08x %08x)", word0, word1); @@ -371,7 +373,8 @@ RdnaInstruction DecodeRdnaMimg(std::uint32_t programCounter, std::span(std::popcount(dmask)); const auto dataDwords = d16 ? (dataComponents + 1u) / 2u : dataComponents; - if (dataDwords > 256u - vdata) { + const auto statusDwords = (word0 & 0x00010000u) != 0u ? 1u : 0u; + if (dataDwords + statusDwords > 256u - vdata) { throw std::runtime_error("MIMG data register range overflow"); } const auto resource = ((word1 >> 16u) & 31u) * 4u; diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 8080a1b27..c7520ce93 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -76,6 +76,7 @@ Throughout the project, every function at every stage either **does exactly what ### Functional - [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) use preallocated host storage instead of the supplied guest work area; its existing size formula and the 512-byte object layouts remain unverified. Waiting-queue pool thread limits and waiter priority ordering are not implemented. Finalization wakes blocked queue callers with `ULT_ERROR_STATE`. +- [MIMG `tfe` and `lwe`](../../core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp) never write the status VGPR after the data. On hardware it is written only when a texel fetch hits an unmapped page of a partially resident texture, or, for `lwe` on sample/gather, when the LOD is below the T# `MIN_LOD_WARN`; texture memory is always resident here, and `lwe` on sample, gather and `image_get_lod` throws. - The shader recompiler [ignores `s_setreg_b32`](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp): writes to MODE (float rounding and denormal controls) and the other hardware registers have no effect, and `s_getreg_b32` reads of the MODE round fields report round to nearest even even after `s_setreg_b32` wrote another mode. The initial round mode from the shader's program registers is not read either. - [Shader recompilation](../../core/shader/recompiler/Recompiler.cpp) currently occurs right before it was transferred to Vulkan with caching, but should be moved to the [relinker](../../core/relinker/main.cpp) stage. For this purpose, [shader/recompiler](../../core/shader/recompiler) was written completely independently from [libs/prx](../../core/libs/prx). - [v_fma_f64](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp) is fused exactly only for normal inputs: with a subnormal input it is a separate multiply and add, which rounds twice. 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 093/164] 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 Date: Mon, 5 Oct 2026 07:12:32 +0000 Subject: [PATCH 094/164] test(agc): discard the results of recompiles expected to be refused --- .../prx/libSceAgcDriver/tests/execution/ScalarProgramEnd.cpp | 2 +- .../libs/prx/libSceAgcDriver/tests/execution/ScalarSopkMisc.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEnd.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEnd.cpp index 023bd4896..a4362f4c3 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEnd.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEnd.cpp @@ -67,7 +67,7 @@ void ExpectRefused(AgcDriver::VulkanDevice& device) { const std::vector userData(8, 0u); bool refused = false; try { - ShaderRecompiler::Recompile(Request(device, code, userData, memory)); + static_cast(ShaderRecompiler::Recompile(Request(device, code, userData, memory))); } catch (const std::exception&) { refused = true; } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSopkMisc.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSopkMisc.cpp index dd2b48530..4760ee0ea 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSopkMisc.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSopkMisc.cpp @@ -70,7 +70,7 @@ void ExpectRefused(AgcDriver::VulkanDevice& device) { const std::vector userData(8, 0u); bool refused = false; try { - ShaderRecompiler::Recompile(Request(device, code, userData, memory)); + static_cast(ShaderRecompiler::Recompile(Request(device, code, userData, memory))); } catch (const std::exception&) { refused = true; } From 7103f390cfc18af3617dd259c0ba66b9dc970c2c Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Mon, 5 Oct 2026 20:36:29 +1300 Subject: [PATCH 095/164] fix(relinker): keep the host libc.prx needed on Linux when a title bundles its own A title that ships sce_module/libc.prx makes addHost drop the host libc.prx from the executable's dependencies. Every host prx needs it, but their DT_RUNPATH is empty, so loading fails with "libc.prx: cannot open shared object file". Mirror the Windows rule from WindowsImportBuilder: when libSceLibcInternal.prx is a host dependency and no host libc.prx remains, append libc.prx as the last host dependency. The title's own imports still bind to the guest modules first. Refs #476 --- core/relinker/CMakeLists.txt | 2 + .../relinker/src/guest/GuestModuleBuilder.cpp | 1 + .../relinker/tests/test_guest_host_libc.py | 67 +++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 core/relinker/relinker/tests/test_guest_host_libc.py diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 5452874a6..a388ec7a3 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -210,6 +210,8 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_tls.py $) add_test(NAME guest_module_directories COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_module_directories.py $) + add_test(NAME guest_host_libc COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_host_libc.py $) add_test(NAME elf_header COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_elf_header.py $) add_test(NAME input_magic COMMAND ${Python3_EXECUTABLE} diff --git a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp index c14b3efae..eebb504ca 100644 --- a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp +++ b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp @@ -135,6 +135,7 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path addHost(std::string(start, end)); } for (const auto& image : images) for (const auto& dependency : image.Dependencies) addHost(dependency); + if (uniqueHosts.contains("libSceLibcInternal.prx") && uniqueHosts.insert("libc.prx").second) hostLibraries.push_back("libc.prx"); dynamic.DynamicSegmentData.clear(); const auto addNeeded = [&](const std::string& name) { Io::AppendU64(dynamic.DynamicSegmentData, 1); diff --git a/core/relinker/relinker/tests/test_guest_host_libc.py b/core/relinker/relinker/tests/test_guest_host_libc.py new file mode 100644 index 000000000..bb1c776b0 --- /dev/null +++ b/core/relinker/relinker/tests/test_guest_host_libc.py @@ -0,0 +1,67 @@ +from pathlib import Path +import struct +import subprocess +import sys +import tempfile + +from test_guest_intel_trampolines import PLAIN_SITE, guest_fixture, main_fixture +from test_guest_module_directories import needed_libraries + +DYNAMIC = 0x4800 +STRINGS = 0x4A00 + + +def importing(*libraries): + image = main_fixture() + strings = b"\0" + b"".join(name.encode() + b"\0" for name in libraries) + image[STRINGS:STRINGS + len(strings)] = strings + tags = [] + offset = 1 + for name in libraries: + tags.append((1, offset)) + offset += len(name) + 1 + tags += [(5, STRINGS), (10, len(strings)), (6, 0x4620), (11, 24), (7, 0x4700), (8, 0), (9, 24), (0, 0)] + for index, tag in enumerate(tags): + struct.pack_into(" Date: Mon, 5 Oct 2026 17:38:11 +1300 Subject: [PATCH 096/164] fix(agc): keep the guest's bytes in the padding a storage write-back stores A GPU-direct storage write-back retiles the image's elements into an uninitialised device scratch buffer and then copies whole ranges of it into the import or the import's unit shadow. The retile writes only the bytes elements occupy, so every other byte of the copied ranges was stored from uninitialised scratch memory: the padding of the tile blocks a surface covers only partly (its last block column and row), mip tail blocks, the bytes between mips and the pitch padding of linear surfaces. Another surface over the same memory, or the CPU, then read garbage there instead of what the memory held. Both the block-window path and the whole-layer path did this. Before the retile, the bytes of the copied ranges that no element holds are now copied into the scratch from where they are current (a fresh unit shadow, else the import), so the write-back stores them unchanged. Whole tile blocks every byte of which holds an element are left out (CoveredMipBytes), so only partly covered blocks pay for the extra copy. Tests: CoveredMipBytes against the XOR address equations of the four 64 KiB XOR modes at every element size; a device test that stores cleared images over patterned memory (tiled with and without mip chains, off a 64 KiB boundary, 4 KiB standard, linear; plain and watched memory) and checks every byte that is not a texel kept its pattern. --- .../Graphics/include/Texture.hpp | 21 +++ .../Graphics/include/TextureTiling.hpp | 6 + .../Graphics/include/UnitShadow.hpp | 5 +- .../libSceAgcDriver/Graphics/src/Texture.cpp | 114 +++++++++++++- .../Graphics/src/TextureTiling.cpp | 24 +++ .../Graphics/src/UnitShadow.cpp | 7 +- .../prx/libSceAgcDriver/tests/Recorder.cpp | 139 ++++++++++++++++++ .../libSceAgcDriver/tests/TextureTiling.cpp | 65 ++++++++ 8 files changed, 371 insertions(+), 10 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp index d21498788..f1ef0b0da 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp @@ -308,6 +308,27 @@ private: // The windows moving the surface-relative runs: whole tile blocks of thin tiled mips, a tail // mip whole (its block holds every tail level), in slice order. std::vector sliceWindows(std::span> runs) const; + // The bytes of the surface-relative runs no element of the surface holds (the padding of partly + // covered tile blocks and of linear rows, tail blocks, the bytes between mips; every byte of a + // thick surface), ascending: a write-back keeps the guest's bytes there. + std::vector> uncoveredBytes(std::span> runs) const; + // The seed of a write-back's tiled scratch where its copies would carry bytes no element holds: + // those bytes' current contents (a fresh unit shadow's, else the import's), copied into the + // scratch before the retile, so the copies out of the scratch store them unchanged. `copied` + // lists the surface-relative [begin, end) ranges the write-back copies out of the scratch, each + // with the scratch offset of its begin. + struct CopiedBytes { + std::uint64_t begin; + std::uint64_t end; + std::uint64_t scratch; + }; + struct PaddingSeeds { + std::vector>> copies; + std::vector> slabs; + // Guest ranges read from the import. + std::vector> importReads; + }; + PaddingSeeds paddingSeeds(const HostImport& import, std::vector copied) const; // The direct upload and GPU-direct write-back of the runs through their windows (block units); // both return the bytes moved. The upload reads each unit from its unit shadow while fresh, // else from the import (`discard`: the image has no layout yet, a whole-surface first upload). diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp index 691baa8aa..adc0bc343 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include namespace AgcDriver::Graphics { @@ -34,6 +35,11 @@ std::array ThickBlockExtent(TextureTileMode tileMode, std::uin // (the detiler's blockExtent). A mip's tiled bytes are its blocks in row-major order, blocksPerRow per // row (TileMipLayout), except the tail mips, which share one block. Throws for linear tiling. std::array ThinBlockLayout(TextureTileMode tileMode, std::uint32_t bytesPerElement); +// The bytes of a thin mip (mip-relative [begin, end), ascending and merged) that hold one of its +// elements each: its whole tile blocks (every element of the block inside the mip), or its rows +// without the pitch padding when linear. A tail mip has none (its block also holds the other tail +// levels and their padding). A retile of the mip writes every byte of these and no other. +std::vector> CoveredMipBytes(TextureTileMode tileMode, std::uint32_t bytesPerElement, const TileMipLayout& mip); // 3D surface. Each slab holds the whole mip chain, smallest level first. Depth slice z of a level is detiled // from slab z / blockDepth (slabBytes apart) with the swizzle's slice input set to z, into linear diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp index b69b285fc..7748a0e9d 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp @@ -100,7 +100,8 @@ bool AnyShadowedOverlaps(std::span // from one buffer (a slab while the unit is fresh, else the import) at `offset` (byte x of the run // is at offset + (x - begin)), split at unit freshness and slab boundaries with one tracker query // over the span. A tail block (surface-relative, every unit of it) must have one source: a mixed -// one is published (TailMip) and read from the import. +// one is published (TailMip) and read from the import. `countReads` false: not counted as detiled +// bytes (a write-back reading the current bytes of its padding). struct ShadowRun { std::uint64_t begin; std::uint64_t end; @@ -109,7 +110,7 @@ struct ShadowRun { bool shadow; std::shared_ptr slab; }; -std::vector ShadowSources(const Context& context, const HostImport& import, std::uint64_t surfaceBase, std::span> runs, std::span> tailBlocks); +std::vector ShadowSources(const Context& context, const HostImport& import, std::uint64_t surfaceBase, std::span> runs, std::span> tailBlocks, bool countReads = true); // The slab destination of a retile copy [begin, end) (guest addresses inside one slab: the caller // splits at SlabBoundary), making the slab under the budget; nullopt when refused (the caller writes diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index ea69132a0..777529fa5 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -1617,6 +1617,83 @@ std::vector StorageTexture::sliceWindows(std::span< return windows; } +std::vector> StorageTexture::uncoveredBytes(std::span> runs) const { + std::vector> covered; + if (!geometry.thick) { + const auto elementBytes = BytesPerElement(descriptor.format); + for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) { + for (std::uint32_t level = 0; level < descriptor.mipCount; ++level) { + if (!geometry.HasLayer(level, layer)) continue; + const auto sliceBegin = geometry.GuestLayerOffset(layer) + mips[level].tiledOffset; + for (const auto& [begin, end] : CoveredMipBytes(descriptor.tileMode, elementBytes, mips[level])) covered.emplace_back(sliceBegin + begin, sliceBegin + end); + } + } + std::sort(covered.begin(), covered.end()); + } + std::vector> sorted(runs.begin(), runs.end()); + std::sort(sorted.begin(), sorted.end()); + std::vector> uncovered; + const auto add = [&](std::uint64_t begin, std::uint64_t end) { + if (begin >= end) return; + if (!uncovered.empty() && uncovered.back().second >= begin) uncovered.back().second = std::max(uncovered.back().second, end); + else uncovered.emplace_back(begin, end); + }; + std::size_t next = 0; + for (const auto& [runBegin, runEnd] : sorted) { + while (next < covered.size() && covered[next].second <= runBegin) ++next; + auto at = runBegin; + for (auto i = next; i < covered.size() && covered[i].first < runEnd; ++i) { + add(at, std::min(covered[i].first, runEnd)); + at = std::max(at, covered[i].second); + } + add(at, runEnd); + } + return uncovered; +} + +StorageTexture::PaddingSeeds StorageTexture::paddingSeeds(const HostImport& import, std::vector copied) const { + PaddingSeeds seeds; + std::sort(copied.begin(), copied.end(), [](const CopiedBytes& a, const CopiedBytes& b) { return a.begin < b.begin; }); + std::vector> runs; + for (const auto& range : copied) runs.emplace_back(range.begin, range.end); + const auto uncovered = uncoveredBytes(runs); + if (uncovered.empty()) return seeds; + std::vector pieces; + std::vector> pieceRuns; + std::size_t next = 0; + for (const auto& [begin, end] : uncovered) { + while (next < copied.size() && copied[next].end <= begin) ++next; + for (auto i = next; i < copied.size() && copied[i].begin < end; ++i) { + const auto from = std::max(begin, copied[i].begin); + const auto to = std::min(end, copied[i].end); + if (from >= to) continue; + pieces.push_back({from, to, copied[i].scratch + (from - copied[i].begin)}); + pieceRuns.emplace_back(from, to); + } + } + if (pieces.empty()) return seeds; + const auto sources = ShadowSources(context, import, descriptor.baseAddress, pieceRuns, {}, false); + std::size_t first = 0; + for (const auto& source : sources) { + while (first < pieces.size() && pieces[first].end <= source.begin) ++first; + for (auto i = first; i < pieces.size() && pieces[i].begin < source.end; ++i) { + const auto from = std::max(source.begin, pieces[i].begin); + const auto to = std::min(source.end, pieces[i].end); + if (from >= to) continue; + auto target = std::find_if(seeds.copies.begin(), seeds.copies.end(), [&](const auto& entry) { return entry.first == source.buffer; }); + if (target == seeds.copies.end()) target = seeds.copies.insert(seeds.copies.end(), {source.buffer, {}}); + target->second.push_back({source.offset + (from - source.begin), pieces[i].scratch + (from - pieces[i].begin), to - from}); + if (!source.shadow) { + const auto guestBegin = descriptor.baseAddress + from; + if (!seeds.importReads.empty() && seeds.importReads.back().second == guestBegin) seeds.importReads.back().second = descriptor.baseAddress + to; + else seeds.importReads.emplace_back(guestBegin, descriptor.baseAddress + to); + } + } + if (source.shadow && std::find(seeds.slabs.begin(), seeds.slabs.end(), source.slab) == seeds.slabs.end()) seeds.slabs.push_back(source.slab); + } + return seeds; +} + std::uint64_t StorageTexture::uploadWindows(const HostImport& import, std::span> runs, bool discard) { const auto elementBytes = BytesPerElement(descriptor.format); // Each run's pieces by source (a unit shadow's slab while fresh, else the import), the tail @@ -1808,6 +1885,17 @@ std::uint64_t StorageTexture::writeBackWindows(const HostImport& import, std::sp } } std::sort(seeds.begin(), seeds.end(), [](const ShadowedRange& a, const ShadowedRange& b) { return a.begin < b.begin; }); + std::vector copied; + for (std::size_t i = 0; i < windows.size(); ++i) { + const auto& window = windows[i]; + if (i != 0 && scratchPositions[i] == scratchPositions[i - 1]) continue; + for (const auto& [from, to] : runs) { + const auto begin = std::max(from, window.tiledBegin); + const auto end = std::min(to, window.tiledEnd); + if (begin < end) copied.push_back({begin, end, scratchPositions[i] + (begin - window.tiledBegin)}); + } + } + const auto padding = paddingSeeds(import, std::move(copied)); // A queued label or key store inside a seeded unit lands before the seed copies the import. auto flushBegin = firstStored; auto flushEnd = lastStored; @@ -1816,7 +1904,7 @@ std::uint64_t StorageTexture::writeBackWindows(const HostImport& import, std::sp flushEnd = std::max(flushEnd, seed.end); } auto linear = std::make_shared(context, static_cast(linearTotal), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); - auto tiledScratch = std::make_shared(context, static_cast(scratchTotal), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT); + auto tiledScratch = std::make_shared(context, static_cast(scratchTotal), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); detiler.BeginBatch(); auto* recorder = Recorder::Active(); std::unique_ptr batch; @@ -1831,12 +1919,14 @@ std::uint64_t StorageTexture::writeBackWindows(const HostImport& import, std::sp recorder->Keep(linear); recorder->Keep(tiledScratch); for (const auto& pieces : slabPieces) recorder->Keep(pieces.slab); + for (const auto& slab : padding.slabs) recorder->Keep(slab); if (auto self = weak_from_this().lock()) recorder->Keep(std::move(self)); Recorder::CountBarriers(Recorder::CommandClass::StorageWriteBack, 4); if (Recorder::BarrierValidate()) { const std::pair read{image, false}; std::vector> seedReads; for (const auto& seed : seeds) seedReads.emplace_back(seed.begin, seed.end); + seedReads.insert(seedReads.end(), padding.importReads.begin(), padding.importReads.end()); recorder->NoteAccess(Recorder::CommandClass::StorageWriteBack, Recorder::Access{seedReads, imported, std::span(&read, 1), VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT}); } } else { @@ -1853,7 +1943,7 @@ std::uint64_t StorageTexture::writeBackWindows(const HostImport& import, std::sp toSource.image = image; toSource.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, descriptor.mipCount, 0, geometry.imageLayers}; context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toSource); - if (!seeds.empty()) { + if (!seeds.empty() || !padding.copies.empty()) { // The seeded units' import bytes (every earlier writer of them, host stores included) // precede the seed copies; the previous command's trailing barrier may have covered that. constexpr VkAccessFlags transferAccess = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; @@ -1869,6 +1959,9 @@ std::uint64_t StorageTexture::writeBackWindows(const HostImport& import, std::sp context.Resolved(&DeviceFunctions::cmdCopyBuffer, "vkCmdCopyBuffer")(commands, import.buffer, slab->buffer, 1, &seed); NoteShadowSeed(begin, end); } + // The padding's current bytes, under the retile that follows (the import-ready barrier + // orders the scratch writes). + for (const auto& [source, copies] : padding.copies) context.Resolved(&DeviceFunctions::cmdCopyBuffer, "vkCmdCopyBuffer")(commands, source, tiledScratch->Handle(), static_cast(copies.size()), copies.data()); } std::vector regions; for (std::size_t i = 0; i < windows.size(); ++i) { @@ -3224,7 +3317,10 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { // untouched blocks are copied into the imported bytes in place, recorded behind the work that // produced the image; nothing crosses to the CPU. auto linear = std::make_shared(context, static_cast(sliceLinearBytes * arrayLayers), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); - auto tiledScratch = std::make_shared(context, static_cast(guestBytes), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT); + auto tiledScratch = std::make_shared(context, static_cast(guestBytes), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); + std::vector copied; + for (const auto& [from, to] : keep) copied.push_back({from - descriptor.baseAddress, to - descriptor.baseAddress, from - descriptor.baseAddress}); + const auto padding = paddingSeeds(*import, std::move(copied)); detiler.BeginBatch(); auto* recorder = Recorder::Active(); std::unique_ptr batch; @@ -3239,12 +3335,13 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { timing = recorder->BeginGpuTiming(Recorder::CommandClass::StorageWriteBack); recorder->Keep(linear); recorder->Keep(tiledScratch); + for (const auto& slab : padding.slabs) recorder->Keep(slab); // The image itself must outlive the recorded retile: the cache may evict it right after. if (auto self = weak_from_this().lock()) recorder->Keep(std::move(self)); - Recorder::CountBarriers(Recorder::CommandClass::StorageWriteBack, 4); + Recorder::CountBarriers(Recorder::CommandClass::StorageWriteBack, padding.copies.empty() ? 4 : 5); if (Recorder::BarrierValidate()) { const std::pair read{image, false}; - recorder->NoteAccess(Recorder::CommandClass::StorageWriteBack, Recorder::Access{{}, keep, std::span(&read, 1), VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT}); + recorder->NoteAccess(Recorder::CommandClass::StorageWriteBack, Recorder::Access{padding.importReads, keep, std::span(&read, 1), VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT}); } } else { batch = std::make_unique(context); @@ -3264,6 +3361,13 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { toSource.image = image; toSource.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, descriptor.mipCount, 0, geometry.imageLayers}; context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toSource); + if (!padding.copies.empty()) { + // The current bytes of the copied ranges no element holds go under the retile (every + // earlier writer of the import, host stores included, first; the import-ready barrier + // below orders the scratch writes). + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); + for (const auto& [source, copies] : padding.copies) context.Function("vkCmdCopyBuffer")(commands, source, tiledScratch->Handle(), static_cast(copies.size()), copies.data()); + } const auto regions = CopyRegions(storedLayers); context.Function("vkCmdCopyImageToBuffer")(commands, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, linear->Handle(), static_cast(regions.size()), regions.data()); const auto linearRead = WholeBufferBarrier(linear->Handle(), VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp index 0de68a1d5..14e44f047 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp @@ -255,6 +255,30 @@ std::array ThinBlockLayout(TextureTileMode tileMode, std::uint return {block.blockSize, block.blockWidth, block.blockHeight}; } +std::vector> CoveredMipBytes(TextureTileMode tileMode, std::uint32_t bytesPerElement, const TileMipLayout& mip) { + std::vector> covered; + if (mip.tail || mip.width == 0 || mip.height == 0) return covered; + const auto add = [&](std::uint64_t begin, std::uint64_t end) { + end = std::min(end, mip.tiledSize); + if (begin >= end) return; + if (!covered.empty() && covered.back().second == begin) covered.back().second = end; + else covered.emplace_back(begin, end); + }; + if (tileMode == TextureTileMode::kLinear) { + const auto rowBytes = static_cast(mip.width) * bytesPerElement; + for (std::uint32_t row = 0; row < mip.height; ++row) add(static_cast(row) * mip.pitchBytes, static_cast(row) * mip.pitchBytes + rowBytes); + return covered; + } + const auto block = ThinBlockLayout(tileMode, bytesPerElement); + const auto wholeColumns = std::min(mip.width / block[1], mip.blocksPerRow); + const auto wholeRows = mip.height / block[2]; + for (std::uint32_t row = 0; row < wholeRows && wholeColumns != 0; ++row) { + const auto first = static_cast(row) * mip.blocksPerRow; + add(first * block[0], (first + wholeColumns) * block[0]); + } + return covered; +} + ThickLayout ComputeThickLayout(TextureTileMode tileMode, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t depth, std::uint32_t mipCount) { Require(width != 0 && height != 0 && depth != 0, "cannot compute layout for a zero-sized 3D texture"); Require(mipCount != 0 && mipCount <= 16u, "3D texture mip count is out of range"); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp index efa6a44bf..f15aaf502 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp @@ -592,12 +592,12 @@ std::optional ShadowDestinationFor(const Context& context, co return destination; } -std::vector ShadowSources(const Context& context, const HostImport& import, std::uint64_t surfaceBase, std::span> runs, std::span> tailBlocks) { +std::vector ShadowSources(const Context& context, const HostImport& import, std::uint64_t surfaceBase, std::span> runs, std::span> tailBlocks, bool countReads) { std::vector result; if (runs.empty()) return result; const auto importPiece = [&](std::uint64_t begin, std::uint64_t end) { result.push_back({begin, end, import.buffer, surfaceBase + begin - import.base, false, nullptr}); - Stats().detiledImportBytes.fetch_add(end - begin, std::memory_order_relaxed); + if (countReads) Stats().detiledImportBytes.fetch_add(end - begin, std::memory_order_relaxed); }; std::shared_ptr shadow; if (UnitShadowEnabled()) { @@ -608,7 +608,7 @@ std::vector ShadowSources(const Context& context, const HostImport& i } if (shadow == nullptr) { for (const auto& [begin, end] : runs) importPiece(begin, end); - Stats().detiledImport.fetch_add(1, std::memory_order_relaxed); + if (countReads) Stats().detiledImport.fetch_add(1, std::memory_order_relaxed); return result; } const auto first = shadow->UnitOf(surfaceBase + runs.front().first); @@ -663,6 +663,7 @@ std::vector ShadowSources(const Context& context, const HostImport& i (isFresh ? shadowBytes : importBytes) += pieceEnd - pieceBegin; } } + if (!countReads) shadowBytes = importBytes = 0; if (shadowBytes != 0) { Stats().detiledShadow.fetch_add(1, std::memory_order_relaxed); Stats().detiledShadowBytes.fetch_add(shadowBytes, std::memory_order_relaxed); diff --git a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp index 732e684a1..8e07fcb18 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp @@ -1700,6 +1700,143 @@ void staleGenerationTests(const Device& device, Recorder& recorder) { #endif } +// A write-back stores the texels of an image and leaves every other byte of the surface's memory +// as it was: the padding of the tile blocks the surface covers only partly (its last block column +// and row), the tail block of a mip chain, the bytes between the mips and the pitch padding of a +// linear surface. Another surface over the memory, or the CPU, reads those bytes. Each case clears +// every mip of an image over memory holding a pattern no texel equals, stores it and reads the +// memory back (watched memory through the flush hook, after a store into the import's unit shadow): +// each 4-byte word is the clear texel or the pattern, and the texels are exactly the surface's. +// The 64 KiB-aligned tiled cases store through block windows, the others whole layers. +void writeBackPaddingTests(const Device& device, Recorder& recorder, bool watched) { + const auto& base = device.GetContext(); + if (base.hostImportAlignment == 0) { + std::cout << "host imports unavailable: write-back padding not tested\n"; + return; + } + struct Case { + const char* name; + TextureTileMode tileMode; + std::uint32_t width; + std::uint32_t height; + std::uint32_t mipCount; + std::uint64_t offset; + }; + constexpr Case cases[] = { + {"64 KiB R_X 200x150", TextureTileMode::kR64KBX, 200, 150, 1, 0}, + {"64 KiB R_X 200x150, 3 mips", TextureTileMode::kR64KBX, 200, 150, 3, 0}, + {"64 KiB R_X 520x260, 6 mips", TextureTileMode::kR64KBX, 520, 260, 6, 0}, + {"64 KiB R_X 200x150 off a 64 KiB boundary, 3 mips", TextureTileMode::kR64KBX, 200, 150, 3, 4096}, + {"4 KiB S 100x70, 2 mips", TextureTileMode::kStandard4KB, 100, 70, 2, 0}, + {"linear 200x150", TextureTileMode::kLinear, 200, 150, 1, 0}, + }; + const auto pattern = [](std::size_t i) { return static_cast(i * 131u + 7u); }; + constexpr std::array red{255, 0, 0, 255}; + std::string failures; + for (const auto& test : cases) { + const std::string what = std::string(watched ? "(watched) " : "") + test.name; + GuestTextureResource resource{}; + resource.width = test.width; + resource.height = test.height; + resource.mipCount = test.mipCount; + resource.tileMode = test.tileMode; + resource.dimension = TextureDimension::k2D; + resource.format = 56; + resource.dstSelX = 4; + resource.dstSelY = 5; + resource.dstSelZ = 6; + resource.dstSelW = 7; + const auto geometry = DescribeSurface(resource); + const auto surfaceBytes = static_cast(geometry.guestBytes); + std::size_t texelCount = 0; + for (const auto& mip : geometry.mips) texelCount += static_cast(mip.width) * mip.height; + // The surface, then a 64 KiB unit past it that no store may reach. + const auto bytes = (static_cast(test.offset) + surfaceBytes + 65535) / 65536 * 65536 + 65536; + void* block = nullptr; + if (watched) { + block = AllocateWatched(bytes, 65536); + if (block == nullptr) { + std::cout << "no write watching: write-back padding in watched memory not tested\n"; + return; + } + } else { +#ifdef _WIN32 + block = VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); +#else + block = std::aligned_alloc(65536, bytes); +#endif + } + Require(block != nullptr, "cannot allocate the write-back padding block"); + auto* memory = static_cast(block); + for (std::size_t i = 0; i < bytes; ++i) memory[i] = pattern(i); + const auto blockAddress = reinterpret_cast(block); + const auto address = blockAddress + test.offset; + { + GuestAllocations::Mutation mutation; + mutation.Add(block, bytes, true, true); + } + struct Unregister { + const Context& context; + void* block; + std::uint64_t address; + std::size_t bytes; + bool watched; + ~Unregister() { + { + GuestAllocations::Mutation mutation; + mutation.Remove(block); + } + HostImportFor(context, address, bytes); + if (watched) ReleaseWatched(block, bytes); + } + } unregister{base, block, blockAddress, bytes, watched}; + if (HostImportFor(base, blockAddress, bytes) == nullptr) { + std::cout << "host import of the write-back padding block refused: write-back padding not tested\n"; + return; + } + TextureDetiler detiler(base); + auto context = base; + context.detiler = &detiler; + resource.baseAddress = address; + { + auto image = std::make_shared(context, detiler, resource, 0); + const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, test.mipCount, 0, 1}; + const auto commands = recorder.Commands(); + recorder.Keep(image); + const VkClearColorValue value{{1.0f, 0.0f, 0.0f, 1.0f}}; + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); + context.Function("vkCmdClearColorImage")(commands, image->Image(), VK_IMAGE_LAYOUT_GENERAL, &value, 1, &range); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); + image->MarkDirty(); + image->WriteBack(); + recorder.Submit(); + device.WaitQueue(); + recorder.Sync(); + std::vector read(bytes); + if (watched) AgcDriver::GuestMemory::Read(blockAddress, read); + else std::memcpy(read.data(), memory, bytes); + std::size_t texels = 0, garbage = 0, outside = 0; + for (std::size_t i = 0; i < bytes; i += 4) { + const bool inside = i >= test.offset && i < test.offset + surfaceBytes; + bool isTexel = inside, isPattern = true; + for (std::size_t c = 0; c < 4; ++c) { + const auto byte = std::to_integer(read[i + c]); + isTexel = isTexel && byte == red[c]; + isPattern = isPattern && byte == pattern(i + c); + } + if (isTexel) ++texels; + else if (!isPattern) ++(inside ? garbage : outside); + } + if (garbage != 0 || outside != 0 || texels != texelCount) { + failures += what + ": the write-back stored " + std::to_string(texels) + " texels of " + std::to_string(texelCount) + " and changed " + std::to_string(garbage) + " words no texel holds (" + std::to_string(outside) + " past the surface)\n"; + } + } + recorder.Sync(); + } + if (!failures.empty()) throw std::runtime_error("write-back padding:\n" + failures); + std::cout << "write-back padding" << (watched ? " (watched)" : "") << ": ok\n"; +} + void importWindowTests(const Device& device, Recorder& recorder) { using namespace AgcDriver::GuestMemory; const auto& base = device.GetContext(); @@ -2459,6 +2596,8 @@ int main() { unitShadowTests(device, recorder); storageRefreshTests(device, recorder, false); storageRefreshTests(device, recorder, true); + writeBackPaddingTests(device, recorder, false); + writeBackPaddingTests(device, recorder, true); importWatchTests(device); staleGenerationTests(device, recorder); importWindowTests(device, recorder); diff --git a/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp b/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp index 37b3b1a95..38324bf69 100644 --- a/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp @@ -1,7 +1,9 @@ #include "GraphicsTests.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureSwizzleEquations.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" #include #include +#include #include #include #include @@ -22,6 +24,16 @@ void reject(TAction action, std::string_view reason) { throw std::runtime_error(std::string("expected texture tiling rejection: ") + std::string(reason)); } +std::uint64_t equationOffset(const TextureSwizzleEquation& equation, std::uint32_t x, std::uint32_t y, std::uint32_t z) { + std::uint64_t offset = 0; + for (std::uint32_t bit = 0; bit < 16; ++bit) { + const auto mask = equation.bits[bit]; + const auto selected = (x & (mask & 0xfffu)) ^ ((y << 12) & (mask & 0xfff000u)) ^ ((z << 24) & (mask & 0xff000000u)); + offset |= static_cast(std::popcount(selected) & 1u) << bit; + } + return offset; +} + } void RunTextureTilingTests() { @@ -149,6 +161,59 @@ void RunTextureTilingTests() { reject([&] { DescribeSurface(volume); }, "3D texture mip tails are not implemented"); } + { + // CoveredMipBytes against the XOR address equations: every element slot of a covered range + // holds exactly one element of the mip, and every tile block left out holds a slot no + // element does (the bytes a write-back must keep). + constexpr std::array modes{TextureTileMode::kZ64KBX, TextureTileMode::kS64KBX, TextureTileMode::kD64KBX, TextureTileMode::kR64KBX}; + constexpr std::array, 6> sizes{{{200, 150}, {1920, 1080}, {2432, 1368}, {960, 540}, {256, 128}, {300, 1}}}; + for (const auto mode : modes) { + for (std::uint32_t bytesPerElement = 1; bytesPerElement <= 16; bytesPerElement *= 2) { + const auto* equation = FindTextureSwizzleEquation(XorSwizzleMode(mode), bytesPerElement); + Require(equation != nullptr, "missing XOR swizzle equation"); + const auto block = ThinBlockLayout(mode, bytesPerElement); + for (const auto& [width, height] : sizes) { + if (static_cast(width) * height > (1u << 20) && mode != TextureTileMode::kR64KBX) continue; + const auto what = "XOR mode " + std::to_string(XorSwizzleMode(mode)) + ", " + std::to_string(bytesPerElement) + " bytes, " + std::to_string(width) + "x" + std::to_string(height); + for (const auto& mip : ComputeElementMipLayout(mode, bytesPerElement, width, height, 3)) { + const auto covered = CoveredMipBytes(mode, bytesPerElement, mip); + if (mip.tail) { + Require(covered.empty(), what + ": a tail mip has covered bytes"); + continue; + } + std::vector held(static_cast(mip.tiledSize / bytesPerElement), 0); + for (std::uint32_t y = 0; y < mip.height; ++y) { + for (std::uint32_t x = 0; x < mip.width; ++x) { + const auto offset = (static_cast(y / block[2]) * mip.blocksPerRow + x / block[1]) * block[0] + equationOffset(*equation, x, y, 0); + Require(offset % bytesPerElement == 0 && offset < mip.tiledSize, what + ": an element lies outside the mip"); + held[static_cast(offset / bytesPerElement)] += 1; + } + } + std::vector inCovered(static_cast(mip.tiledSize / block[0]), false); + std::uint64_t previous = 0; + for (const auto& [begin, end] : covered) { + Require(begin >= previous && begin < end && end <= mip.tiledSize && begin % block[0] == 0 && end % block[0] == 0, what + ": covered ranges are not ascending whole blocks"); + previous = end; + for (auto slot = begin / bytesPerElement; slot < end / bytesPerElement; ++slot) Require(held[static_cast(slot)] == 1, what + ": a covered byte holds no element or several"); + for (auto at = begin; at < end; at += block[0]) inCovered[static_cast(at / block[0])] = true; + } + for (std::size_t index = 0; index < inCovered.size(); ++index) { + if (inCovered[index]) continue; + const auto first = index * block[0] / bytesPerElement; + const auto last = (index + 1) * block[0] / bytesPerElement; + Require(std::any_of(held.begin() + static_cast(first), held.begin() + static_cast(last), [](std::uint8_t count) { return count == 0; }), what + ": a block every byte of which holds an element is left out"); + } + } + } + } + } + const auto linear = ComputeElementMipLayout(TextureTileMode::kLinear, 4, 200, 3, 1).front(); + const auto rows = CoveredMipBytes(TextureTileMode::kLinear, 4, linear); + Require(linear.pitchBytes > 800 && rows.size() == 3 && rows[1].first == linear.pitchBytes && rows[1].second == linear.pitchBytes + 800, "linear covered bytes are not the rows without their pitch padding"); + const auto dense = ComputeElementMipLayout(TextureTileMode::kLinear, 4, 256, 3, 1).front(); + Require(dense.pitchBytes == 1024 && CoveredMipBytes(TextureTileMode::kLinear, 4, dense) == std::vector>{{0, 3072}}, "linear rows without padding are not one range"); + } + reject([] { ComputeSurfaceSize({}, 1); }, "empty mip chain"); reject([] { ComputeSurfaceSize(ComputeMipLayout(TextureTileMode::kLinear, 1, 4, 4, 1), 0); }, "zero array layers"); } From 992933b95c593543b39516c8d0ddec265450e081 Mon Sep 17 00:00:00 2001 From: DotDebian Date: Mon, 5 Oct 2026 09:59:30 +0200 Subject: [PATCH 097/164] feat(libc): implement snwprintf_s Format through the shared wide formatter, truncate to the buffer and return the full length, as C11 K.3.9.1.3 specifies. A null buffer or format, a zero or oversized n, %n and a null %s argument are runtime-constraint violations that return a negative value. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/src/FormattingWide.cpp | 26 ++++++++++- core/libs/prx/libc/src/RuntimeSupport.cpp | 5 --- core/libs/tests/GuestWideFormatting.cpp | 55 +++++++++++++++++++++++ docs/dev/TechnicalDebt.md | 2 +- 4 files changed, 81 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libc/src/FormattingWide.cpp b/core/libs/prx/libc/src/FormattingWide.cpp index 6684ffc68..ad53b9862 100644 --- a/core/libs/prx/libc/src/FormattingWide.cpp +++ b/core/libs/prx/libc/src/FormattingWide.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -88,7 +89,7 @@ void AppendPadded(std::u16string& out, const std::u16string& text, bool left, in if (left) out.append(pad, u' '); } -std::u16string FormatWide(const char16_t* format, VaList* source) { +std::u16string FormatWide(const char16_t* format, VaList* source, bool rejectNullStrings = false) { if (format == nullptr || source == nullptr) throw std::invalid_argument("Null formatting argument"); LibcDetail::FormatArguments args(source); std::u16string out; @@ -171,10 +172,12 @@ std::u16string FormatWide(const char16_t* format, VaList* source) { const std::size_t limit = precision < 0 ? SIZE_MAX : static_cast(precision); if (conversion == u'S' || length == "l") { const char16_t* value = args.Next(); + if (value == nullptr && rejectNullStrings) throw std::invalid_argument("Null string argument"); if (value == nullptr) value = u"(null)"; for (std::size_t index = 0; index < limit && value[index] != 0; ++index) text.push_back(value[index]); } else { const char* value = args.Next(); + if (value == nullptr && rejectNullStrings) throw std::invalid_argument("Null string argument"); AppendUtf16(text, value != nullptr ? value : "(null)", limit); } AppendPadded(out, text, left, width); @@ -204,6 +207,27 @@ int APS5_VABI vswprintf_nid_postfix(char16_t* buffer, std::size_t size, const ch } } +int APS5_VABI snwprintf_s_nid_postfix(char16_t* buffer, std::size_t size, const char16_t* format, ...) { + constexpr std::size_t RsizeMax = SIZE_MAX >> 1; + int result = -1; + if (buffer != nullptr && format != nullptr && size != 0 && size <= RsizeMax) { + APS5_VA_BEGIN(format); + try { + const std::u16string text = FormatWide(format, reinterpret_cast(args), true); + if (text.size() <= static_cast(INT_MAX)) { + const std::size_t copied = text.size() < size - 1 ? text.size() : size - 1; + std::memcpy(buffer, text.data(), copied * sizeof(char16_t)); + buffer[copied] = 0; + result = static_cast(text.size()); + } + } catch (const std::invalid_argument&) { + } + APS5_VA_END(); + } + if (result < 0 && buffer != nullptr && size != 0 && size < RsizeMax) buffer[0] = 0; + return result; +} + int APS5_VABI wprintf_nid_postfix(const char16_t* format, ...) { APS5_VA_BEGIN(format); int result = -1; diff --git a/core/libs/prx/libc/src/RuntimeSupport.cpp b/core/libs/prx/libc/src/RuntimeSupport.cpp index 3fe661094..ef00dd455 100644 --- a/core/libs/prx/libc/src/RuntimeSupport.cpp +++ b/core/libs/prx/libc/src/RuntimeSupport.cpp @@ -138,9 +138,4 @@ void APS5_VABI _Unlocksyslock_nid_postfix() { g_sysLock.unlock(); } -int APS5_VABI snwprintf_s() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - } diff --git a/core/libs/tests/GuestWideFormatting.cpp b/core/libs/tests/GuestWideFormatting.cpp index 2aea154dd..343ef0e51 100644 --- a/core/libs/tests/GuestWideFormatting.cpp +++ b/core/libs/tests/GuestWideFormatting.cpp @@ -1,11 +1,13 @@ #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include +#include #include #include #include extern "C" int APS5_VABI vswprintf_nid_postfix(char16_t*, std::size_t, const char16_t*, VaList*); +extern "C" int APS5_VABI snwprintf_s_nid_postfix(char16_t*, std::size_t, const char16_t*, ...); static int APS5_VABI Format(char16_t* buffer, std::size_t size, const char16_t* format, ...) { #ifdef _WIN32 @@ -34,7 +36,60 @@ static void Check(const char16_t* format, const char* input, const char16_t* exp } } +static void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "snwprintf_s: %s\n", message); + std::abort(); + } +} + +static void CheckBounded() { + constexpr std::size_t rsizeMax = SIZE_MAX >> 1; + const char16_t* const nullWide = nullptr; + const char* const nullNarrow = nullptr; + char16_t buffer[16]; + + buffer[0] = u'x'; + Require(snwprintf_s_nid_postfix(buffer, 16, u"%d-%ls-%s", 42, u"ab", "\xc3\xa9") == 7, "complete output length"); + Require(buffer == std::u16string(u"42-ab-\u00e9"), "complete output"); + + buffer[5] = u'x'; + Require(snwprintf_s_nid_postfix(buffer, 5, u"%d-%ls", 42, u"abcd") == 7, "truncated output length"); + Require(buffer == std::u16string(u"42-a") && buffer[5] == u'x', "truncated output"); + + buffer[0] = u'x'; + buffer[1] = u'y'; + Require(snwprintf_s_nid_postfix(buffer, 1, u"%x", 0xabcu) == 3, "one unit buffer length"); + Require(buffer[0] == 0 && buffer[1] == u'y', "one unit buffer"); + + buffer[0] = u'x'; + Require(snwprintf_s_nid_postfix(buffer, 16, u"") == 0 && buffer[0] == 0, "empty format"); + + buffer[0] = u'x'; + Require(snwprintf_s_nid_postfix(buffer, 16, nullptr) < 0 && buffer[0] == 0, "null format"); + Require(snwprintf_s_nid_postfix(nullptr, 16, u"a") < 0, "null buffer"); + + buffer[0] = u'x'; + Require(snwprintf_s_nid_postfix(buffer, 0, u"a") < 0 && buffer[0] == u'x', "zero size"); + + buffer[0] = u'x'; + Require(snwprintf_s_nid_postfix(buffer, rsizeMax + 1, u"a") < 0 && buffer[0] == u'x', "size above RSIZE_MAX"); + + int written = -7; + buffer[0] = u'x'; + Require(snwprintf_s_nid_postfix(buffer, 16, u"ab%n", &written) < 0 && buffer[0] == 0 && written == -7, "%n"); + + buffer[0] = u'x'; + Require(snwprintf_s_nid_postfix(buffer, 16, u"a%ls", nullWide) < 0 && buffer[0] == 0, "null %ls argument"); + + buffer[0] = u'x'; + Require(snwprintf_s_nid_postfix(buffer, 16, u"a%s", nullNarrow) < 0 && buffer[0] == 0, "null %s argument"); + + Require(Format(buffer, 16, u"%s", nullNarrow) == 6 && buffer == std::u16string(u"(null)"), "vswprintf null %s argument"); +} + int main() { + CheckBounded(); Check(u"%.2s", "\xc3\xa9\xc3\xa8", u"\u00e9\u00e8"); Check(u"%.1s", "\xc3\xa9\xc3\xa8", u"\u00e9"); Check(u"%.0s", "\xc3\xa9", u""); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index c7520ce93..4c6ab2bef 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -70,7 +70,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceSystemServiceDisableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature - [sceSystemServiceReenableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature - [sceVoiceSetMuteFlag](../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature -- [snwprintf_s](../../core/libs/prx/libc/src/RuntimeSupport.cpp) (libc) - unknown signature +- [snwprintf_s](../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follows C11 K.3.9.1.3 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`. A runtime-constraint violation only returns a negative value: `set_constraint_handler_s` is not exported and the console's default handler is unknown. An invalid multibyte `%s` argument is copied byte by byte instead of being reported as an encoding error, as in `vswprintf` - [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) - the added `sceUltQueuePop`, `sceUltQueueTryPush`, `sceUltQueueDestroy` and `sceUltQueueDataResourcePoolDestroy` signatures are inferred from the existing push/pop and object APIs. Queue destruction is assumed to reject blocked callers with `ULT_ERROR_BUSY` and otherwise discard queued data; pool exhaustion returns `ULT_ERROR_AGAIN`. These rules, alignment checks and shared slot limits have not been confirmed on PS5 hardware or a title. Non-null queue and data-pool option parameters throw because their layouts are unknown. ### Functional From 9837c69f0d5a725a9768bb66ec1fac56eee76452 Mon Sep 17 00:00:00 2001 From: DotDebian Date: Mon, 5 Oct 2026 10:05:28 +0200 Subject: [PATCH 098/164] feat(agc): implement sceAgcDcbPrimeUtcl2 and its size query The DCB variant threw "not implemented" while the ACB one wrote a 5-dword packet. Both now go through Agc::Command::WritePrimeUtcl2 and the size query returns 20 bytes. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgc/Acb/src/Memory.cpp | 9 +- core/libs/prx/libSceAgc/CMakeLists.txt | 20 ++++ .../prx/libSceAgc/Command/include/Memory.hpp | 1 + .../libs/prx/libSceAgc/Command/src/Memory.cpp | 5 + .../prx/libSceAgc/DcbState/src/Display.cpp | 10 +- core/libs/prx/libSceAgc/tests/PrimeUtcl2.cpp | 99 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 7 files changed, 130 insertions(+), 15 deletions(-) create mode 100644 core/libs/prx/libSceAgc/tests/PrimeUtcl2.cpp diff --git a/core/libs/prx/libSceAgc/Acb/src/Memory.cpp b/core/libs/prx/libSceAgc/Acb/src/Memory.cpp index c2b181de8..1a93bdbc2 100644 --- a/core/libs/prx/libSceAgc/Acb/src/Memory.cpp +++ b/core/libs/prx/libSceAgc/Acb/src/Memory.cpp @@ -47,14 +47,7 @@ std::uint32_t APS5_VABI sceAgcAcbAtomicGdsGetSize() { } std::uint32_t* APS5_VABI sceAgcAcbPrimeUtcl2(CommandBuffer* buf, const volatile void* address, std::uint32_t sizeInBytes) { - const auto guestAddress = reinterpret_cast(address); - auto* packet = Agc::Command::Allocate(buf, 5, __func__); - packet[0] = Agc::Command::Header(0x10u, 5); - packet[1] = 0; - packet[2] = static_cast(guestAddress); - packet[3] = static_cast(guestAddress >> 32u); - packet[4] = sizeInBytes; - return packet; + return Agc::Command::WritePrimeUtcl2(buf, address, sizeInBytes, __func__); } std::uint32_t APS5_VABI sceAgcAcbPrimeUtcl2GetSize() { diff --git a/core/libs/prx/libSceAgc/CMakeLists.txt b/core/libs/prx/libSceAgc/CMakeLists.txt index e3cf070b9..4e9a781f8 100644 --- a/core/libs/prx/libSceAgc/CMakeLists.txt +++ b/core/libs/prx/libSceAgc/CMakeLists.txt @@ -151,3 +151,23 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +add_test_executable(agc_prime_utcl2_tests EXCLUDE_FROM_ALL + tests/PrimeUtcl2.cpp + Command/src/Packet.cpp + Command/src/Memory.cpp + Acb/src/Memory.cpp + DcbFlow/src/Memory.cpp + DcbState/src/Display.cpp +) +target_include_directories(agc_prime_utcl2_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_prime_utcl2_tests PRIVATE libc) +configure_windows_unwind(agc_prime_utcl2_tests) +if(BUILD_TESTING) + set_target_properties(agc_prime_utcl2_tests PROPERTIES EXCLUDE_FROM_ALL FALSE) + add_test(NAME agc_prime_utcl2_tests COMMAND agc_prime_utcl2_tests) + if(WIN32) + set_tests_properties(agc_prime_utcl2_tests PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceAgc/Command/include/Memory.hpp b/core/libs/prx/libSceAgc/Command/include/Memory.hpp index 9c19b6599..9c5fe2edf 100644 --- a/core/libs/prx/libSceAgc/Command/include/Memory.hpp +++ b/core/libs/prx/libSceAgc/Command/include/Memory.hpp @@ -8,6 +8,7 @@ namespace Agc::Command { std::uint32_t* WriteDma(CommandBuffer* buffer, bool compute, std::uint8_t engine, std::uint8_t dst, std::uint8_t dstCachePolicy, std::uint64_t dstAddress, std::uint8_t src, std::uint8_t srcCachePolicy, std::uint64_t srcAddress, std::uint32_t numBytes, std::uint8_t waitForPrevious, std::uint8_t writeConfirm, std::uint8_t blockEngine, const char* function); std::uint32_t* WriteCopyData(CommandBuffer* buffer, bool compute, std::uint8_t dst, std::uint8_t dstCachePolicy, std::uint64_t dstAddress, std::uint8_t src, std::uint8_t srcCachePolicy, std::uint64_t srcAddressOrImmediate, std::uint8_t itemSize, std::uint8_t writeConfirm, const char* function); std::uint32_t* WriteAtomicMem(CommandBuffer* buffer, std::uint8_t atomicOp, std::uint8_t command, std::uint8_t cachePolicy, const volatile void* address, std::uint64_t srcData, std::uint64_t compareData, std::uint16_t loopInterval, const char* function); +std::uint32_t* WritePrimeUtcl2(CommandBuffer* buffer, const volatile void* address, std::uint32_t sizeInBytes, const char* function); std::uint32_t* WriteData(CommandBuffer* buffer, bool compute, std::uint8_t dst, std::uint8_t cachePolicy, std::uint64_t address, const void* data, std::uint32_t count, std::uint8_t increment, std::uint8_t writeConfirm, const char* function); std::uint32_t* WriteWait(CommandBuffer* buffer, std::uint8_t size, std::uint8_t compareFunction, std::uint8_t operation, std::uint8_t cachePolicy, const volatile void* address, std::uint64_t reference, std::uint64_t mask, std::uint32_t pollCycles, const char* function); std::uint32_t* ValidateWait(std::uint32_t* packet, const char* function); diff --git a/core/libs/prx/libSceAgc/Command/src/Memory.cpp b/core/libs/prx/libSceAgc/Command/src/Memory.cpp index 86d799379..15d897300 100644 --- a/core/libs/prx/libSceAgc/Command/src/Memory.cpp +++ b/core/libs/prx/libSceAgc/Command/src/Memory.cpp @@ -49,6 +49,11 @@ std::uint32_t* WriteAtomicMem(CommandBuffer* buffer, std::uint8_t atomicOp, std: return Emit(buffer, 0x1eu, {atomicOp | (static_cast(command) << 8u) | (static_cast(cachePolicy) << 25u), static_cast(guestAddress), static_cast(guestAddress >> 32u), static_cast(srcData), static_cast(srcData >> 32u), static_cast(compareData), static_cast(compareData >> 32u), loopInterval}, function); } +std::uint32_t* WritePrimeUtcl2(CommandBuffer* buffer, const volatile void* address, std::uint32_t sizeInBytes, const char* function) { + const auto guestAddress = reinterpret_cast(address); + return Emit(buffer, 0x10u, {0, static_cast(guestAddress), static_cast(guestAddress >> 32u), sizeInBytes}, function); +} + std::uint32_t* WriteData(CommandBuffer* buffer, bool compute, std::uint8_t dst, std::uint8_t cachePolicy, std::uint64_t address, const void* data, std::uint32_t count, std::uint8_t increment, std::uint8_t writeConfirm, const char* function) { Require(data != nullptr && count != 0 && count <= 0x3ffdu, function, "invalid write data payload"); CheckBits(dst, compute ? 0xfu : 0x1fu, function); diff --git a/core/libs/prx/libSceAgc/DcbState/src/Display.cpp b/core/libs/prx/libSceAgc/DcbState/src/Display.cpp index 9b379b97d..f9594cfe8 100644 --- a/core/libs/prx/libSceAgc/DcbState/src/Display.cpp +++ b/core/libs/prx/libSceAgc/DcbState/src/Display.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgc/DcbState/include/Display.hpp" +#include "prx/libSceAgc/Command/include/Memory.hpp" #include "prx/libSceAgc/Command/include/Packet.hpp" #include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp" #include @@ -21,16 +22,11 @@ uint32_t* APS5_VABI sceAgcDcbSetFlip(CommandBuffer* buf, uint32_t video_out_hand } uint32_t* APS5_VABI sceAgcDcbPrimeUtcl2(CommandBuffer* buf, const volatile void* address, uint32_t size_in_bytes) { - (void)buf; - (void)address; - (void)size_in_bytes; - NotImplemented_nid_no_patch(__func__); - return nullptr; + return Agc::Command::WritePrimeUtcl2(buf, address, size_in_bytes, __func__); } std::uint32_t APS5_VABI sceAgcDcbPrimeUtcl2GetSize() { - NotImplemented_nid_no_patch(__func__); - return 0; + return 20; } } diff --git a/core/libs/prx/libSceAgc/tests/PrimeUtcl2.cpp b/core/libs/prx/libSceAgc/tests/PrimeUtcl2.cpp new file mode 100644 index 000000000..b1e702d24 --- /dev/null +++ b/core/libs/prx/libSceAgc/tests/PrimeUtcl2.cpp @@ -0,0 +1,99 @@ +#include "prx/libSceAgc/Command/include/Packet.hpp" + +#include +#include +#include +#include +#include +#include + +extern "C" { +std::uint32_t* APS5_VABI sceAgcAcbPrimeUtcl2(CommandBuffer*, const volatile void*, std::uint32_t); +std::uint32_t APS5_VABI sceAgcAcbPrimeUtcl2GetSize(); +std::uint32_t* APS5_VABI sceAgcDcbPrimeUtcl2(CommandBuffer*, const volatile void*, std::uint32_t); +std::uint32_t APS5_VABI sceAgcDcbPrimeUtcl2GetSize(); +} + +namespace { + +constexpr std::uint32_t Sentinel = 0xabcdef01u; +constexpr std::uint64_t Address = 0x0000112233440000ull; +constexpr std::uint32_t SizeInBytes = 0x00345000u; + +void check(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +template +void expectFailure(TAction action) { + try { + action(); + } catch (const std::runtime_error&) { + return; + } + throw std::runtime_error("expected invalid input to fail"); +} + +struct Storage { + std::array words{}; + CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; + + Storage() { + words.fill(Sentinel); + } + + void limitTo(std::uint32_t count) { + buffer.cursor_down = buffer.cursor_up + count; + } +}; + +std::uint32_t* writeCompute(CommandBuffer* buffer) { + return sceAgcAcbPrimeUtcl2(buffer, reinterpret_cast(Address), SizeInBytes); +} + +std::uint32_t* writeDraw(CommandBuffer* buffer) { + return sceAgcDcbPrimeUtcl2(buffer, reinterpret_cast(Address), SizeInBytes); +} + +template +void testCommand(TWriter writer, TSize size) { + const std::array expected{0xc0031000u, 0u, 0x33440000u, 0x1122u, SizeInBytes}; + const auto count = static_cast(expected.size()); + check(size() == count * sizeof(std::uint32_t), "size query does not match the prime packet"); + + Storage storage; + auto* packet = writer(&storage.buffer); + check(packet == storage.words.data() && std::equal(expected.begin(), expected.end(), packet), "incorrect prime packet"); + check(storage.buffer.cursor_up == packet + size() / sizeof(std::uint32_t), "cursor advance does not match the size query"); + check(std::all_of(storage.words.begin() + count, storage.words.end(), [](std::uint32_t word) { return word == Sentinel; }), "prime command overwrote following words"); + + auto* second = writer(&storage.buffer); + check(second == packet + count && std::equal(expected.begin(), expected.end(), second), "second prime packet does not follow the first"); + + Storage exact; + exact.limitTo(size() / sizeof(std::uint32_t)); + check(writer(&exact.buffer) == exact.words.data() && exact.buffer.cursor_up == exact.buffer.cursor_down, "prime packet does not fill the queried size"); + + Storage shortBuffer; + shortBuffer.limitTo(size() / sizeof(std::uint32_t) - 1u); + const auto before = shortBuffer.words; + expectFailure([&] { writer(&shortBuffer.buffer); }); + check(shortBuffer.words == before && shortBuffer.buffer.cursor_up == shortBuffer.words.data(), "failed prime write modified the buffer"); + + expectFailure([&] { writer(nullptr); }); +} + +} + +int main() { + try { + testCommand(writeCompute, sceAgcAcbPrimeUtcl2GetSize); + testCommand(writeDraw, sceAgcDcbPrimeUtcl2GetSize); + check(sceAgcAcbPrimeUtcl2GetSize() == sceAgcDcbPrimeUtcl2GetSize(), "compute and draw prime sizes differ"); + std::puts("AGC prime UTCL2 tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index c7520ce93..ddd0b29ef 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -37,6 +37,7 @@ Throughout the project, every function at every stage either **does exactly what - [dolOmWH+huQ](../../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name - [V++UgBtQhn0](../../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc) - unknown name - [gQkqkLttcpw](../../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - unknown name, signature +- [sceAgcDcbPrimeUtcl2](../../core/libs/prx/libSceAgc/DcbState/src/Display.cpp) (libSceAgc) - assumed to write the packet `sceAgcAcbPrimeUtcl2` writes: PRIME_UTCL2 has the same 5 dwords on the graphics and compute rings. Not confirmed on a title. Both write a NOP of that size, as there is no TLB to prime - [sceKernelInternalMemoryGetModuleSegmentInfo](../../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) - unknown signature - [sceKernelSyncOnAddressWait](../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - the only known caller passes a null timeout and a name string as the fourth argument; the timeout is assumed to point to microseconds like the other kernel waits, and the name is ignored - [sceKernelMapNamedFlexibleMemoryInternal](../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - flag 0x8000 unknown; only the sceKernelMapNamedFlexibleMemory flags are accepted From c0d293ff7b8d693ec7de201513ac9543f3da9a85 Mon Sep 17 00:00:00 2001 From: DotDebian Date: Mon, 5 Oct 2026 10:05:31 +0200 Subject: [PATCH 099/164] feat(font): implement sceFontGetPixelResolution Report the sub-pixel resolution of the library's driver. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceFont/src/Library.cpp | 9 +++++++ .../libs/prx/libSceFont/src/Unimplemented.cpp | 5 ---- core/libs/tests/GuestFont.cpp | 26 ++++++++++++++++++- docs/dev/TechnicalDebt.md | 1 + 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libSceFont/src/Library.cpp b/core/libs/prx/libSceFont/src/Library.cpp index 46b421dcb..90f5cc8b1 100644 --- a/core/libs/prx/libSceFont/src/Library.cpp +++ b/core/libs/prx/libSceFont/src/Library.cpp @@ -297,6 +297,15 @@ int APS5_VABI sceFontRendererSetOutlineBufferPolicy(FontRenderer fontRenderer, s return SCE_FONT_OK; } +int APS5_VABI sceFontGetPixelResolution(FontLibrary library, std::uint32_t* subPixelCount) { + if (!subPixelCount) return SCE_FONT_ERROR_INVALID_PARAMETER; + *subPixelCount = 0; + const auto* lib = static_cast(library); + if (!lib || lib->magic != LIBRARY_MAGIC || !lib->sys_driver || !lib->sys_driver->pixel_resolution) return SCE_FONT_ERROR_INVALID_LIBRARY; + *subPixelCount = lib->sys_driver->pixel_resolution(); + return SCE_FONT_OK; +} + int APS5_VABI sceFontAttachDeviceCacheBuffer(FontLibrary library, void* buffer, std::uint32_t size) { auto* lib = static_cast(library); if (!lib || lib->magic != LIBRARY_MAGIC) return SCE_FONT_ERROR_INVALID_LIBRARY; diff --git a/core/libs/prx/libSceFont/src/Unimplemented.cpp b/core/libs/prx/libSceFont/src/Unimplemented.cpp index 10960e239..c1b833af5 100644 --- a/core/libs/prx/libSceFont/src/Unimplemented.cpp +++ b/core/libs/prx/libSceFont/src/Unimplemented.cpp @@ -117,11 +117,6 @@ int APS5_VABI sceFontGetGlyphExpandBufferState() { return 0; } -int APS5_VABI sceFontGetPixelResolution() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFontGetScriptLanguage() { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/tests/GuestFont.cpp b/core/libs/tests/GuestFont.cpp index de875228f..74ebc5694 100644 --- a/core/libs/tests/GuestFont.cpp +++ b/core/libs/tests/GuestFont.cpp @@ -1,4 +1,4 @@ -#include "prx/libSceFont/include/FontTypes.hpp" +#include "prx/libSceFont/include/FontDriver.hpp" #include #include #include @@ -15,6 +15,7 @@ int APS5_VABI sceFontCreateLibrary(const FontMemory*, const void*, FontLibrary*) int APS5_VABI sceFontDestroyLibrary(FontLibrary*); int APS5_VABI sceFontCreateRenderer(const FontMemory*, const void*, FontRenderer*); int APS5_VABI sceFontDestroyRenderer(FontRenderer*); +int APS5_VABI sceFontGetPixelResolution(FontLibrary, std::uint32_t*); int APS5_VABI sceFontSupportSystemFonts(FontLibrary); int APS5_VABI sceFontSupportExternalFonts(FontLibrary, std::uint32_t, std::uint32_t); int APS5_VABI sceFontOpenFontSet(FontLibrary, std::uint32_t, std::uint32_t, const FontOpenDetail*, FontHandle*); @@ -51,6 +52,10 @@ static void APS5_VABI Release(void*, void* pointer) { std::free(pointer); } +static std::uint32_t APS5_VABI CoarsePixelResolution() { + return 16; +} + static void Put16(std::vector& out, int value) { out.push_back(static_cast((value >> 8) & 0xFF)); out.push_back(static_cast(value & 0xFF)); @@ -114,6 +119,25 @@ int main() { Require(sceFontCreateLibrary(&memory, nullptr, &library) == SCE_FONT_ERROR_INVALID_PARAMETER && library == nullptr); Require(sceFontCreateLibrary(&memory, sceFontSelectLibraryFt(0), &library) == SCE_FONT_OK && library != nullptr); + std::uint32_t subPixelCount = 1; + Require(sceFontGetPixelResolution(library, &subPixelCount) == SCE_FONT_OK && subPixelCount == 64); + Require(sceFontGetPixelResolution(library, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontGetPixelResolution(nullptr, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + subPixelCount = 1; + Require(sceFontGetPixelResolution(nullptr, &subPixelCount) == SCE_FONT_ERROR_INVALID_LIBRARY && subPixelCount == 0); + FontHandleOpaque notALibrary{}; + subPixelCount = 1; + Require(sceFontGetPixelResolution(¬ALibrary, &subPixelCount) == SCE_FONT_ERROR_INVALID_LIBRARY && subPixelCount == 0); + Font::SysDriver coarseDriver = *static_cast(sceFontSelectLibraryFt(0)); + coarseDriver.pixel_resolution = CoarsePixelResolution; + FontLibrary coarseLibrary = nullptr; + Require(sceFontCreateLibrary(&memory, &coarseDriver, &coarseLibrary) == SCE_FONT_OK && coarseLibrary != nullptr); + Require(sceFontGetPixelResolution(coarseLibrary, &subPixelCount) == SCE_FONT_OK && subPixelCount == 16); + Require(sceFontGetPixelResolution(library, &subPixelCount) == SCE_FONT_OK && subPixelCount == 64); + coarseDriver.pixel_resolution = nullptr; + Require(sceFontGetPixelResolution(coarseLibrary, &subPixelCount) == SCE_FONT_ERROR_INVALID_LIBRARY && subPixelCount == 0); + Require(sceFontDestroyLibrary(&coarseLibrary) == SCE_FONT_OK && coarseLibrary == nullptr); + FontHandle font = reinterpret_cast(&memory); Require(sceFontOpenFontSet(library, SystemFontSet, 1, nullptr, &font) == SCE_FONT_ERROR_NO_SUPPORT_FUNCTION && font == nullptr); Require(sceFontSupportSystemFonts(library) == SCE_FONT_OK); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index c7520ce93..d64c94f9c 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -30,6 +30,7 @@ Throughout the project, every function at every stage either **does exactly what - [AMPR WriteAddressFromCounterPairOnCompletion](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - the pair of an even counter n is assumed to give counter n in the low 32 bits and n + 1 in the high 32 bits. Wait compare values, wait flush and counter index ranges follow the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation; the flush is accepted and has no effect, as commands run in order - [AMPR markers](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - signatures taken from the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation, not confirmed on a title: `sceAmprCommandBufferSetMarkerWithColor` takes the color by pointer, the push and measure variants by value. Colors are not recorded - [sceFontGetRenderScaledKerning](../../core/libs/prx/libSceFont/src/Render.cpp) (libSceFont) - arguments, the cleared output and the bound renderer check taken from `cellFontGetRenderScaledKerning` in [RPCS3](https://github.com/RPCS3/rpcs3/blob/master/rpcs3/Emu/Cell/Modules/cellFont.cpp) (PS3 predecessor of the library), not confirmed on a title. The kerning is the `sceFontGetKerning` one at the render scale +- [sceFontGetPixelResolution](../../core/libs/prx/libSceFont/src/Library.cpp) (libSceFont) - signature taken from the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json), not confirmed on a title. The value is the one the library's driver reports (64 for FreeType); the argument checks and the cleared output follow `sceFontGetLibrary` - [sceAgcSetSubmitMode](../../core/libs/prx/libSceAgc/Misc/src/Suspend.cpp) (libSceAgc) - mode values unknown; only 0 is accepted - [zARR5aCmkoY](../../core/libs/prx/libSceAgc/DcbFlow/src/Control.cpp) (libSceAgc) - unknown name, signature - [qj7QZpgr9Uw](../../core/libs/prx/libSceAgc/DcbState/src/ContextState.cpp) (libSceAgc) - unknown name From 096bdb38c240befc9cef86067dc280a287a481af Mon Sep 17 00:00:00 2001 From: DotDebian Date: Mon, 5 Oct 2026 10:24:22 +0200 Subject: [PATCH 100/164] fix(libkernel): give CLOCK_VIRTUAL, CLOCK_PROF and CLOCK_SECOND their FreeBSD meaning clock_gettime and clock_getres served three FreeBSD clock ids with the wrong clock or not at all: - CLOCK_VIRTUAL (1) was the monotonic clock on Windows and threw on Linux; it is the user CPU time of the process. - CLOCK_PROF (2) threw on both hosts; it is the user plus system CPU time of the process. - CLOCK_SECOND (13) returned the coarse realtime clock with nanoseconds and a sub-second resolution; it is the current second with tv_nsec 0 and a resolution of one second. Behaviour follows FreeBSD 11 clock_gettime(2) and kern_clock_gettime / kern_clock_getres in sys/kern/kern_time.c. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libkernel/CMakeLists.txt | 10 ++ core/libs/prx/libkernel/Time/Time.cpp | 64 ++++++++++-- core/libs/tests/GuestFreebsdClocks.cpp | 138 +++++++++++++++++++++++++ 3 files changed, 204 insertions(+), 8 deletions(-) create mode 100644 core/libs/tests/GuestFreebsdClocks.cpp diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 9127d300d..2e5364b1b 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -176,6 +176,16 @@ if(BUILD_TESTING) set_tests_properties(guest_cpu_time_clocks PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_freebsd_clocks_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreebsdClocks.cpp) + target_include_directories(guest_freebsd_clocks_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freebsd_clocks_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_freebsd_clocks_tests) + add_test(NAME guest_freebsd_clocks COMMAND guest_freebsd_clocks_tests) + set_tests_properties(guest_freebsd_clocks PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_freebsd_clocks PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_nanosleep_tests ${LIBS_INCLUDE_DIR}/tests/GuestNanosleep.cpp) target_include_directories(guest_nanosleep_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_nanosleep_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libkernel/Time/Time.cpp b/core/libs/prx/libkernel/Time/Time.cpp index 8cde1fc97..395f85829 100644 --- a/core/libs/prx/libkernel/Time/Time.cpp +++ b/core/libs/prx/libkernel/Time/Time.cpp @@ -23,6 +23,8 @@ #else #include #include +#include +#include #endif extern "C" int* APS5_VABI __error_nid_postfix(); @@ -263,7 +265,7 @@ int APS5_VABI usleep_nid_postfix(KernelUseconds microseconds) { } #ifdef _WIN32 -static std::uint64_t CpuTimeNanos(bool callingThread) { +static std::uint64_t CpuTimeNanos(bool callingThread, bool countKernel) { FILETIME creation{}; FILETIME exitTime{}; FILETIME kernel{}; @@ -278,7 +280,7 @@ static std::uint64_t CpuTimeNanos(bool callingThread) { const auto toTicks = [](const FILETIME& time) { return (static_cast(time.dwHighDateTime) << 32) | time.dwLowDateTime; }; - return (toTicks(kernel) + toTicks(user)) * 100ULL; + return (toTicks(user) + (countKernel ? toTicks(kernel) : 0ULL)) * 100ULL; } static std::uint64_t CpuTimeResolutionNanos() { @@ -291,12 +293,48 @@ static std::uint64_t CpuTimeResolutionNanos() { throw std::runtime_error("clock_getres: GetSystemTimeAdjustment reported no clock interrupt period"); return static_cast(increment) * 100ULL; } + +static std::uint64_t ProcessCpuNanos(bool countSystem) { + return CpuTimeNanos(false, countSystem); +} + +static std::uint64_t ProcessCpuResolutionNanos() { + return CpuTimeResolutionNanos(); +} +#else +static std::uint64_t ClockTicksPerSecond() { + const long rate = sysconf(_SC_CLK_TCK); + if (rate <= 0) + throw std::runtime_error("clock_gettime: sysconf(_SC_CLK_TCK) reported no clock tick rate"); + return static_cast(rate); +} + +static std::uint64_t ProcessCpuNanos(bool countSystem) { + struct tms usage{}; + errno = 0; + if (times(&usage) == static_cast(-1) && errno != 0) + throw std::system_error(errno, std::generic_category(), "clock_gettime: times failed"); + const std::uint64_t rate = ClockTicksPerSecond(); + const std::uint64_t ticks = static_cast(usage.tms_utime) + (countSystem ? static_cast(usage.tms_stime) : 0ULL); + return ticks / rate * 1000000000ULL + ticks % rate * 1000000000ULL / rate; +} + +static std::uint64_t ProcessCpuResolutionNanos() { + const std::uint64_t rate = ClockTicksPerSecond(); + return (1000000000ULL + rate - 1ULL) / rate; +} #endif int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) { if (tp == nullptr) { APS5_INVALID_ARG_EX; } + if (clockId == 1 || clockId == 2) { + const std::uint64_t nanos = ProcessCpuNanos(clockId == 2); + tp->tv_sec = static_cast(nanos / 1000000000ULL); + tp->tv_nsec = static_cast(nanos % 1000000000ULL); + return 0; + } #ifdef _WIN32 if (clockId == 0 || clockId == 9) { FILETIME ft{}; @@ -315,17 +353,17 @@ int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) { t -= 116444736000000000ULL; t *= 100ULL; tp->tv_sec = static_cast(t / 1000000000ULL); - tp->tv_nsec = static_cast(t % 1000000000ULL); + tp->tv_nsec = clockId == 13 ? 0 : static_cast(t % 1000000000ULL); return 0; } - if (clockId == 4 || clockId == 1 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 11 || clockId == 12) { + if (clockId == 4 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 11 || clockId == 12) { std::uint64_t nanos = GetMonotonicNanos(); tp->tv_sec = static_cast(nanos / 1000000000ULL); tp->tv_nsec = static_cast(nanos % 1000000000ULL); return 0; } if (clockId == 14 || clockId == 15) { - const std::uint64_t nanos = CpuTimeNanos(clockId == 14); + const std::uint64_t nanos = CpuTimeNanos(clockId == 14, true); tp->tv_sec = static_cast(nanos / 1000000000ULL); tp->tv_nsec = static_cast(nanos % 1000000000ULL); return 0; @@ -370,7 +408,7 @@ int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) { return -1; } tp->tv_sec = static_cast(ts.tv_sec); - tp->tv_nsec = static_cast(ts.tv_nsec); + tp->tv_nsec = clockId == 13 ? 0 : static_cast(ts.tv_nsec); return 0; #endif } @@ -405,13 +443,24 @@ int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res) { if (res == nullptr) { APS5_INVALID_ARG_EX; } + if (clockId == 1 || clockId == 2) { + const std::uint64_t nanos = ProcessCpuResolutionNanos(); + res->tv_sec = static_cast(nanos / 1000000000ULL); + res->tv_nsec = static_cast(nanos % 1000000000ULL); + return 0; + } + if (clockId == 13) { + res->tv_sec = 1; + res->tv_nsec = 0; + return 0; + } #ifdef _WIN32 if (clockId == 0 || clockId == 9) { res->tv_sec = 0; res->tv_nsec = 100LL; return 0; } - if (clockId == 4 || clockId == 1 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 10 || clockId == 11 || clockId == 12 || clockId == 13) { + if (clockId == 4 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 10 || clockId == 11 || clockId == 12) { static const std::uint64_t freq = [] { LARGE_INTEGER f{}; QueryPerformanceFrequency(&f); @@ -437,7 +486,6 @@ int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res) { nativeId = CLOCK_REALTIME; break; case 10: - case 13: #ifdef CLOCK_REALTIME_COARSE nativeId = CLOCK_REALTIME_COARSE; #else diff --git a/core/libs/tests/GuestFreebsdClocks.cpp b/core/libs/tests/GuestFreebsdClocks.cpp new file mode 100644 index 000000000..6c7f959f0 --- /dev/null +++ b/core/libs/tests/GuestFreebsdClocks.cpp @@ -0,0 +1,138 @@ +#include "SceTypes.hpp" +#include +#include + +extern "C" { +int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp); +int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res); +int APS5_VABI sceKernelClockGettime(KernelClockid clockId, KernelTimespec* tp); +int APS5_VABI sceKernelClockGetres(KernelClockid clockId, KernelTimespec* tp); +int APS5_VABI sceKernelUsleep_nid_postfix(KernelUseconds microseconds); +} + +static constexpr int SCE_OK = 0; + +static constexpr int GUEST_CLOCK_REALTIME = 0; +static constexpr int GUEST_CLOCK_VIRTUAL = 1; +static constexpr int GUEST_CLOCK_PROF = 2; +static constexpr int GUEST_CLOCK_MONOTONIC = 4; +static constexpr int GUEST_CLOCK_SECOND = 13; +static constexpr int GUEST_CLOCK_PROCESS_CPUTIME_ID = 15; + +static constexpr std::int64_t NANOS_PER_SECOND = 1000000000LL; +static constexpr std::int64_t NANOS_PER_MILLISECOND = 1000000LL; +static constexpr std::int64_t BURN_NANOS = 200 * NANOS_PER_MILLISECOND; +static constexpr std::int64_t IDLE_LIMIT_NANOS = 100 * NANOS_PER_MILLISECOND; +static constexpr std::int64_t GIVE_UP_NANOS = 10 * NANOS_PER_SECOND; + +static void Require(bool value) { if (!value) std::abort(); } + +static KernelTimespec Read(int clockId) { + KernelTimespec time{-1, -1}; + Require(clock_gettime_nid_postfix(clockId, &time) == SCE_OK); + Require(time.tv_sec >= 0); + Require(time.tv_nsec >= 0 && time.tv_nsec < NANOS_PER_SECOND); + return time; +} + +static std::int64_t Nanos(int clockId) { + const KernelTimespec time = Read(clockId); + return time.tv_sec * NANOS_PER_SECOND + time.tv_nsec; +} + +static std::int64_t ResolutionNanos(int clockId) { + KernelTimespec resolution{-1, -1}; + Require(clock_getres_nid_postfix(clockId, &resolution) == SCE_OK); + Require(resolution.tv_sec == 0); + Require(resolution.tv_nsec > 0 && resolution.tv_nsec < NANOS_PER_SECOND); + KernelTimespec sceResolution{-1, -1}; + Require(sceKernelClockGetres(clockId, &sceResolution) == SCE_OK); + Require(sceResolution.tv_sec == resolution.tv_sec && sceResolution.tv_nsec == resolution.tv_nsec); + return resolution.tv_nsec; +} + +static void RequireNeverDecreases(int clockId) { + std::int64_t previous = Nanos(clockId); + for (int i = 0; i < 10000; ++i) { + const std::int64_t current = Nanos(clockId); + Require(current >= previous); + previous = current; + } +} + +static void SecondClockReportsWholeSeconds() { + KernelTimespec resolution{-1, -1}; + Require(clock_getres_nid_postfix(GUEST_CLOCK_SECOND, &resolution) == SCE_OK); + Require(resolution.tv_sec == 1 && resolution.tv_nsec == 0); + resolution = {-1, -1}; + Require(sceKernelClockGetres(GUEST_CLOCK_SECOND, &resolution) == SCE_OK); + Require(resolution.tv_sec == 1 && resolution.tv_nsec == 0); + + KernelTimespec sceSecond{-1, -1}; + Require(sceKernelClockGettime(GUEST_CLOCK_SECOND, &sceSecond) == SCE_OK); + Require(sceSecond.tv_sec > 0 && sceSecond.tv_nsec == 0); + + std::int64_t previous = 0; + for (int i = 0; i < 1000; ++i) { + const KernelTimespec before = Read(GUEST_CLOCK_REALTIME); + const KernelTimespec second = Read(GUEST_CLOCK_SECOND); + const KernelTimespec after = Read(GUEST_CLOCK_REALTIME); + Require(second.tv_nsec == 0); + Require(second.tv_sec >= before.tv_sec - 1 && second.tv_sec <= after.tv_sec); + Require(second.tv_sec >= previous); + previous = second.tv_sec; + } +} + +static void ProcessClocksAreOrdered() { + const std::int64_t profResolution = ResolutionNanos(GUEST_CLOCK_PROF); + Require(ResolutionNanos(GUEST_CLOCK_VIRTUAL) == profResolution); + for (int i = 0; i < 1000; ++i) { + const std::int64_t user = Nanos(GUEST_CLOCK_VIRTUAL); + const std::int64_t userAndSystem = Nanos(GUEST_CLOCK_PROF); + const std::int64_t execution = Nanos(GUEST_CLOCK_PROCESS_CPUTIME_ID); + Require(user <= userAndSystem); + Require(userAndSystem <= execution + profResolution); + } +} + +static void BusyProcessAccumulatesUserTime() { + const std::int64_t wallStart = Nanos(GUEST_CLOCK_MONOTONIC); + const std::int64_t profStart = Nanos(GUEST_CLOCK_PROF); + const std::int64_t userStart = Nanos(GUEST_CLOCK_VIRTUAL); + std::int64_t user = userStart; + volatile std::uint64_t work = 0; + while (user - userStart < BURN_NANOS) { + Require(Nanos(GUEST_CLOCK_MONOTONIC) - wallStart < GIVE_UP_NANOS); + for (int i = 0; i < 1000000; ++i) work = work + 1; + const std::int64_t current = Nanos(GUEST_CLOCK_VIRTUAL); + Require(current >= user); + user = current; + } + Require(Nanos(GUEST_CLOCK_PROF) - profStart >= user - userStart); +} + +static void IdleProcessAccumulatesNone() { + const std::int64_t userStart = Nanos(GUEST_CLOCK_VIRTUAL); + const std::int64_t profStart = Nanos(GUEST_CLOCK_PROF); + Require(sceKernelUsleep_nid_postfix(200000) == SCE_OK); + Require(Nanos(GUEST_CLOCK_VIRTUAL) - userStart <= IDLE_LIMIT_NANOS); + Require(Nanos(GUEST_CLOCK_PROF) - profStart <= IDLE_LIMIT_NANOS); +} + +int main() { + SecondClockReportsWholeSeconds(); + + KernelTimespec time{-1, -1}; + Require(sceKernelClockGettime(GUEST_CLOCK_VIRTUAL, &time) == SCE_OK); + Require(time.tv_sec >= 0 && time.tv_nsec >= 0 && time.tv_nsec < NANOS_PER_SECOND); + time = {-1, -1}; + Require(sceKernelClockGettime(GUEST_CLOCK_PROF, &time) == SCE_OK); + Require(time.tv_sec >= 0 && time.tv_nsec >= 0 && time.tv_nsec < NANOS_PER_SECOND); + + RequireNeverDecreases(GUEST_CLOCK_VIRTUAL); + RequireNeverDecreases(GUEST_CLOCK_PROF); + ProcessClocksAreOrdered(); + BusyProcessAccumulatesUserTime(); + IdleProcessAccumulatesNone(); +} From 63711450f48a2582a1784a08198f50cb7bcc3d4b Mon Sep 17 00:00:00 2001 From: DotDebian Date: Mon, 5 Oct 2026 10:25:30 +0200 Subject: [PATCH 101/164] feat(libSceAudioOut): implement sceAudioOutGetLastOutputTime Report the process time at which sceAudioOutOutput or sceAudioOutOutputs accepted the port's last block. A port that never output reports 0, and waiting with a null pointer does not move the time. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAudioOut/CMakeLists.txt | 10 ++ core/libs/prx/libSceAudioOut/src/AudioOut.cpp | 21 ++- .../tests/AudioOutLastOutputTime.cpp | 158 ++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 core/libs/prx/libSceAudioOut/tests/AudioOutLastOutputTime.cpp diff --git a/core/libs/prx/libSceAudioOut/CMakeLists.txt b/core/libs/prx/libSceAudioOut/CMakeLists.txt index e7ae4fef6..024083dad 100644 --- a/core/libs/prx/libSceAudioOut/CMakeLists.txt +++ b/core/libs/prx/libSceAudioOut/CMakeLists.txt @@ -42,4 +42,14 @@ if(BUILD_TESTING) set_tests_properties(audio_out_mix_level_pad_spk PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(audio_out_last_output_time_tests tests/AudioOutLastOutputTime.cpp) + target_include_directories(audio_out_last_output_time_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(audio_out_last_output_time_tests PRIVATE libSceAudioOut libkernel libc) + configure_windows_unwind(audio_out_last_output_time_tests) + add_test(NAME audio_out_last_output_time COMMAND audio_out_last_output_time_tests) + if(WIN32) + set_tests_properties(audio_out_last_output_time PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp index 8f865806a..de2184619 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp @@ -89,6 +89,7 @@ struct Port { int volume[8] = {}; int mixLevel = DEFAULT_VOLUME; std::uint64_t lastOutputTime = 0; + std::uint64_t lastDataOutputTime = 0; SDL_AudioDeviceID device = 0; SDL_AudioSpec spec = {}; }; @@ -328,6 +329,7 @@ int APS5_VABI sceAudioOutOpen(int userId, int type, int index, std::uint32_t len port.format = format; port.channels = channelsForFormat(format); port.lastOutputTime = 0; + port.lastDataOutputTime = 0; port.mixLevel = type == PORT_TYPE_PADSPK ? DEFAULT_PADSPK_MIX_LEVEL : DEFAULT_VOLUME; for (int c = 0; c < port.channels; c++) { port.volume[c] = DEFAULT_VOLUME; @@ -374,6 +376,7 @@ int APS5_VABI sceAudioOutOutput(int handle, const void* ptr) { queueAudio(*port, ptr); port->lastOutputTime = sceKernelGetProcessTime(); + if (ptr != nullptr) port->lastDataOutputTime = port->lastOutputTime; return static_cast(port->samplesNum); } @@ -425,7 +428,10 @@ int APS5_VABI sceAudioOutOutputs(AudioOutOutputParam* param, std::uint32_t num) const std::uint64_t done = sceKernelGetProcessTime(); for (std::uint32_t i = 0; i < num; i++) { - if (auto* port = getPort(param[i].handle)) port->lastOutputTime = done; + if (auto* port = getPort(param[i].handle)) { + port->lastOutputTime = done; + if (param[i].ptr != nullptr) port->lastDataOutputTime = done; + } } return static_cast(first.samplesNum); @@ -457,6 +463,19 @@ int APS5_VABI sceAudioOutSetVolume(int handle, std::uint32_t flag, int* vol) { return 0; } +int APS5_VABI sceAudioOutGetLastOutputTime(int handle, std::uint64_t* outputTime) { + if (outputTime == nullptr) { + return -2144993276; + } + std::lock_guard lock(g_mutex); + Port* port = getPort(handle); + if (port == nullptr) { + return -2144993277; + } + *outputTime = port->lastDataOutputTime; + return 0; +} + int APS5_VABI sceAudioOutGetPortState(int handle, AudioOutPortState* state) { if (state == nullptr) { return -2144993276; diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutLastOutputTime.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutLastOutputTime.cpp new file mode 100644 index 000000000..bc6a54315 --- /dev/null +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutLastOutputTime.cpp @@ -0,0 +1,158 @@ +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceAudioOutOpen(int, int, int, std::uint32_t, std::uint32_t, std::uint32_t); +int APS5_VABI sceAudioOutClose(int); +int APS5_VABI sceAudioOutOutput(int, const void*); +int APS5_VABI sceAudioOutOutputs(AudioOutOutputParam*, std::uint32_t); +int APS5_VABI sceAudioOutGetLastOutputTime(int, std::uint64_t*); +std::uint64_t APS5_VABI sceKernelGetProcessTime(); +} + +static void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +namespace { + +constexpr int user = 0x10000000; +constexpr int portTypeMain = 0; +constexpr int portTypeVibration = 10; +constexpr std::uint32_t formatS16Mono = 0; +constexpr std::uint32_t frames = 256; +constexpr std::uint32_t frequency = 48000; +constexpr std::uint64_t untouched = 0xA5A5A5A5A5A5A5A5ull; +constexpr int invalidPort = static_cast(0x80260003); +constexpr int invalidPointer = static_cast(0x80260004); + +int Open(int type) { + const int handle = sceAudioOutOpen(user, type, 0, frames, frequency, formatS16Mono); + Require(handle > 0, "port must open"); + return handle; +} + +std::uint64_t LastOutputTime(int handle) { + std::uint64_t time = untouched; + Require(sceAudioOutGetLastOutputTime(handle, &time) == 0, "an open port must report its last output time"); + return time; +} + +void Pause() { + std::this_thread::sleep_for(std::chrono::milliseconds(5)); +} + +std::uint64_t OutputAndCheck(int handle, const std::vector& block) { + Pause(); + const std::uint64_t before = sceKernelGetProcessTime(); + Require(sceAudioOutOutput(handle, block.data()) == static_cast(frames), "output must accept the block"); + const std::uint64_t after = sceKernelGetProcessTime(); + const std::uint64_t time = LastOutputTime(handle); + Require(time >= before && time <= after, "last output time must be the process time of the output"); + return time; +} + +void TestOutput(int type) { + const std::vector block(frames, 256); + const int handle = Open(type); + Require(LastOutputTime(handle) == 0, "a port that never output must report 0"); + + const std::uint64_t first = OutputAndCheck(handle, block); + Pause(); + Require(LastOutputTime(handle) == first, "the time must not move without an output"); + + const std::uint64_t second = OutputAndCheck(handle, block); + Require(second > first, "a later output must report a later time"); + + Pause(); + Require(sceAudioOutOutput(handle, nullptr) == static_cast(frames), "waiting for the output must succeed"); + Require(LastOutputTime(handle) == second, "waiting without data must not move the time"); + + Require(sceAudioOutClose(handle) == 0, "port must close"); +} + +void TestPortsAreIndependent() { + const std::vector block(frames, 256); + const int played = Open(portTypeMain); + const int silent = Open(portTypeMain); + Require(played != silent, "two open ports must have different handles"); + OutputAndCheck(played, block); + Require(LastOutputTime(silent) == 0, "an output must not move the time of another port"); + Require(sceAudioOutClose(played) == 0, "port must close"); + Require(sceAudioOutClose(silent) == 0, "port must close"); +} + +void TestOutputs() { + const std::vector block(frames, 256); + const int first = Open(portTypeMain); + const int second = Open(portTypeMain); + const int waiting = Open(portTypeMain); + AudioOutOutputParam params[3] = {{first, block.data()}, {second, block.data()}, {waiting, nullptr}}; + + Pause(); + const std::uint64_t before = sceKernelGetProcessTime(); + Require(sceAudioOutOutputs(params, 3) == static_cast(frames), "outputs must accept the blocks"); + const std::uint64_t after = sceKernelGetProcessTime(); + + for (const int handle : {first, second}) { + const std::uint64_t time = LastOutputTime(handle); + Require(time >= before && time <= after, "outputs must set the time of every port that received data"); + } + Require(LastOutputTime(waiting) == 0, "outputs must not move the time of a port that received no data"); + + for (const int handle : {first, second, waiting}) Require(sceAudioOutClose(handle) == 0, "port must close"); +} + +void TestReopenedPort() { + const std::vector block(frames, 256); + const int handle = Open(portTypeMain); + OutputAndCheck(handle, block); + Require(sceAudioOutClose(handle) == 0, "port must close"); + const int reopened = Open(portTypeMain); + Require(LastOutputTime(reopened) == 0, "a port opened after a close must not keep the time of the closed one"); + Require(sceAudioOutClose(reopened) == 0, "port must close"); +} + +void TestErrors() { + const std::vector block(frames, 256); + const int handle = Open(portTypeMain); + const std::uint64_t time = OutputAndCheck(handle, block); + Require(sceAudioOutGetLastOutputTime(handle, nullptr) == invalidPointer, "a null destination must be rejected"); + Require(LastOutputTime(handle) == time, "a rejected call must not move the time"); + + std::uint64_t destination = untouched; + Require(sceAudioOutGetLastOutputTime(0, &destination) == invalidPort, "handle 0 must be rejected"); + Require(sceAudioOutGetLastOutputTime(-1, &destination) == invalidPort, "a negative handle must be rejected"); + Require(sceAudioOutGetLastOutputTime(handle + 1, &destination) == invalidPort, "a port that is not open must be rejected"); + Require(sceAudioOutGetLastOutputTime(1000, &destination) == invalidPort, "an out of range handle must be rejected"); + Require(sceAudioOutClose(handle) == 0, "port must close"); + Require(sceAudioOutGetLastOutputTime(handle, &destination) == invalidPort, "a closed port must be rejected"); + Require(destination == untouched, "a rejected call must not write the destination"); +} + +} + +int main() { +#ifdef _WIN32 + _putenv_s("SDL_AUDIODRIVER", "dummy"); +#else + setenv("SDL_AUDIODRIVER", "dummy", 1); +#endif + sceKernelGetProcessTime(); + TestOutput(portTypeMain); + TestOutput(portTypeVibration); + TestPortsAreIndependent(); + TestOutputs(); + TestReopenedPort(); + TestErrors(); + return 0; +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index c7520ce93..99137269c 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -49,6 +49,7 @@ Throughout the project, every function at every stage either **does exactly what - [X+4jdIS75P0](../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown name, signature - [sceAudioOutSetMixLevelPadSpk](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - effect of a negative mix level unknown; only 0 to 32768 (0 dB) is accepted - [sceAudioOutSetMixLevelPadSpk](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - no source applies the level (shadPS4 only stores it); it is assumed to be a linear gain on the port's own output, and the -9 dB default (11626) is applied to every pad speaker port, including those of titles that never call the function +- [sceAudioOutGetLastOutputTime](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - signature and behaviour taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [fpPS4](https://github.com/red-prig/fpPS4) reimplementations of the PS4 library, not confirmed on a PS5 title: the time is the process time at which `sceAudioOutOutput` or `sceAudioOutOutputs` accepted the port's last block, not the time the block reached the device - [AOWqIYsgVHs](../../core/libs/prx/libSceContentExport/Export.cpp) (libSceContentExport) - unknown name, signature - [GQTObcITIXI](../../core/libs/prx/libSceShare/Export.cpp) (libSceShare) - unknown name, signature - [BnMAMrsfVWo](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libc) - unknown name, signature From 2b388e0b0e0069b9fd8a4fbfc75137fba111fb00 Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Mon, 5 Oct 2026 21:27:15 +1300 Subject: [PATCH 102/164] perf(agc): resolve the recorder's query and fold entry points once Every occlusion counter dump the recorder handles on the GPU ends the batch's occlusion query, copies the pending results, folds them with a compute dispatch, then resets and begins a new query. Each of those commands was resolved through vkGetDeviceProcAddr, a loader name lookup, on every call: seven lookups per dump on the graphics worker, and a title dumps the counter twice per occlusion query. The mesh argument and timing paths resolved their bind, push, dispatch and reset per call too. They join the entry points the recorder already resolves in its constructor: vkCmdBeginQuery, vkCmdEndQuery, vkCmdResetQueryPool, vkCmdCopyQueryPoolResults, vkCmdBindPipeline, vkCmdPushConstants and vkCmdDispatch. The same functions are called with the same arguments; APS5_NO_PROC_TABLE=1 still resolves every call. --- .../Graphics/include/Recorder.hpp | 7 +++++ .../libSceAgcDriver/Graphics/src/Recorder.cpp | 29 ++++++++++++------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp index 6e3c1ddca..c4cf5e386 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp @@ -641,6 +641,13 @@ private: PFN_vkEndCommandBuffer endCommandBuffer = nullptr; PFN_vkQueueSubmit queueSubmit = nullptr; PFN_vkCmdPipelineBarrier cmdPipelineBarrier = nullptr; + PFN_vkCmdBeginQuery cmdBeginQuery = nullptr; + PFN_vkCmdEndQuery cmdEndQuery = nullptr; + PFN_vkCmdResetQueryPool cmdResetQueryPool = nullptr; + PFN_vkCmdCopyQueryPoolResults cmdCopyQueryPoolResults = nullptr; + PFN_vkCmdBindPipeline cmdBindPipeline = nullptr; + PFN_vkCmdPushConstants cmdPushConstants = nullptr; + PFN_vkCmdDispatch cmdDispatch = nullptr; template TFunction function(TFunction resolved, const char* name) const { return resolved != nullptr ? resolved : context.Function(name); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp index 474ac0976..9d486a317 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp @@ -1074,6 +1074,13 @@ Recorder::Recorder(const Context& context, bool timelineSemaphores) : context(co endCommandBuffer = context.Function("vkEndCommandBuffer"); queueSubmit = context.Function("vkQueueSubmit"); cmdPipelineBarrier = context.Function("vkCmdPipelineBarrier"); + cmdBeginQuery = context.Function("vkCmdBeginQuery"); + cmdEndQuery = context.Function("vkCmdEndQuery"); + cmdResetQueryPool = context.Function("vkCmdResetQueryPool"); + cmdCopyQueryPoolResults = context.Function("vkCmdCopyQueryPoolResults"); + cmdBindPipeline = context.Function("vkCmdBindPipeline"); + cmdPushConstants = context.Function("vkCmdPushConstants"); + cmdDispatch = context.Function("vkCmdDispatch"); } if (!timelineSemaphores) return; // The timeline starts at 0 and every Submit signals its serial (1, 2, ...): a value that only @@ -1837,7 +1844,7 @@ std::uint32_t Recorder::beginTiming(std::uint64_t key) { return NoTiming; } } - context.Function("vkCmdResetQueryPool")(commands, open->queries, 0, queryCount); + function(cmdResetQueryPool, "vkCmdResetQueryPool")(commands, open->queries, 0, queryCount); } if (open->timedKeys.size() >= queryCount / 2) { timingDropped.fetch_add(1, std::memory_order_relaxed); @@ -1926,7 +1933,7 @@ bool Recorder::gpuSampleCounter() { void Recorder::endSamples(Batch& batch) { if (!batch.sampleActive) return; - context.Function("vkCmdEndQuery")(batch.commands, batch.samples, 0); + function(cmdEndQuery, "vkCmdEndQuery")(batch.commands, batch.samples, 0); batch.sampleActive = false; if (sampleCounterState > 0 && batch.samplesDrawn) pendingSamples.push_back({batch.samplePool, batch.samples}); batch.samplesDrawn = false; @@ -1934,10 +1941,10 @@ void Recorder::endSamples(Batch& batch) { void Recorder::foldSamples(Batch& batch, VkDeviceAddress target) { const auto commands = batch.commands; - const auto copy = context.Function("vkCmdCopyQueryPoolResults"); - const auto bind = context.Function("vkCmdBindPipeline"); - const auto push = context.Function("vkCmdPushConstants"); - const auto dispatch = context.Function("vkCmdDispatch"); + const auto copy = function(cmdCopyQueryPoolResults, "vkCmdCopyQueryPoolResults"); + const auto bind = function(cmdBindPipeline, "vkCmdBindPipeline"); + const auto push = function(cmdPushConstants, "vkCmdPushConstants"); + const auto dispatch = function(cmdDispatch, "vkCmdDispatch"); std::size_t done = 0; do { const auto count = static_cast(std::min(SampleFoldCapacity, pendingSamples.size() - done)); @@ -2011,15 +2018,15 @@ bool Recorder::RecordMeshArguments(VkCommandBuffer commands, VkDeviceAddress rec } if (meshArgumentState < 0) return false; recordBarrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); - context.Function("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, meshArgumentPipeline); + function(cmdBindPipeline, "vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, meshArgumentPipeline); struct { VkDeviceAddress record; VkDeviceAddress arguments; std::array rules; } parameters{record, arguments, {rules[0], rules[1], rules[2], rules[3], rules[4], rules[5], rules[6], 0u}}; static_assert(sizeof(parameters) == PushBytes); - context.Function("vkCmdPushConstants")(commands, meshArgumentLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, PushBytes, ¶meters); - context.Function("vkCmdDispatch")(commands, 1, 1, 1); + function(cmdPushConstants, "vkCmdPushConstants")(commands, meshArgumentLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, PushBytes, ¶meters); + function(cmdDispatch, "vkCmdDispatch")(commands, 1, 1, 1); recordBarrier(commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT | VK_PIPELINE_STAGE_MESH_SHADER_BIT_EXT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_SHADER_READ_BIT); CountBarriers(CommandClass::Draw, 2); return true; @@ -2049,8 +2056,8 @@ void Recorder::beginSamples(Batch& batch) { throw; } } - context.Function("vkCmdResetQueryPool")(batch.commands, batch.samples, 0, 1); - context.Function("vkCmdBeginQuery")(batch.commands, batch.samples, 0, context.occlusionQueryPrecise ? VK_QUERY_CONTROL_PRECISE_BIT : 0u); + function(cmdResetQueryPool, "vkCmdResetQueryPool")(batch.commands, batch.samples, 0, 1); + function(cmdBeginQuery, "vkCmdBeginQuery")(batch.commands, batch.samples, 0, context.occlusionQueryPrecise ? VK_QUERY_CONTROL_PRECISE_BIT : 0u); batch.sampleActive = true; batch.samplesDrawn = false; } From 23d67b59a7df3ccc96e388e52bcbd6e7c55ac5cf Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Mon, 5 Oct 2026 15:17:01 +1300 Subject: [PATCH 103/164] perf(agc): keep label stores larger than 64 bytes out of the label table PPSA21564 clears each occlusion query slot with a 256-byte WRITE_DATA before its begin dump, about 8,400 times per frame in one level. The clear is larger than a deferred label, so it is recorded at once, and Recorder::NoteLabel entered all 64 of its dwords in the pending-label table: 64 map inserts, 64 batch dword entries and 64 group entries, then 64 lookups in CloseLabelGroup and 64 erases when the batch is reaped. A replay of that pattern (21 clears per batch, 20 batches in flight) costs 1.6 us for the inserts, 0.7 us for the close and 1.3 us for the erases per clear, about 31 ms of the graphics worker per frame there. The new path costs 0.07 us per clear in the same replay. The table serves WAIT_REG_MEM values and CPU pollers, which read 4 or 8 bytes. A store larger than 64 bytes (the bound the driver's own label history already uses) now enters one range instead of its dwords: - table entries inside the range are removed, as the store replaced them before: a lookup there finds no value and falls back to the pending-write path, where it used to find the store's value; - the range leaves with its batch, like the dword entries; - PendingLabelIn sees the range, and the write-evidence check, which syncs a capture read over any pending label dword, now also syncs over a pending range (Recorder::WideLabelIn). The store is still a pending write of the open batch (the flush hook syncs CPU readers), still starts the label flush deadline and is recorded exactly as before. Stores of 64 bytes or less are unchanged. Test: agc_driver_recorder_tests largeLabelTests (a 256-byte label enters no dwords and removes the older entry under it but not one beside it, its range, pending write and deadline, a later 4-byte label over it, a 64-byte label entered in full, everything gone after the batch). --- .../src/Driver/Memory/WriteEvidence.cpp | 4 +++ .../Graphics/include/Recorder.hpp | 6 ++++ .../libSceAgcDriver/Graphics/src/Recorder.cpp | 30 +++++++++++++++-- .../prx/libSceAgcDriver/tests/Recorder.cpp | 33 +++++++++++++++++++ 4 files changed, 71 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp index 144ce3aaa..bf0f11838 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp @@ -165,6 +165,10 @@ ShaderMemory::PendingWrite Driver::classifyPendingWrite(std::uint64_t address, s } const auto first = address & ~3ull; const auto limit = address + bytes; + if (Graphics::Recorder::WideLabelIn(first, static_cast(limit - first))) { + reason = &ValidateCounters::syncedLabel; + return Policy::Sync; + } for (std::uint64_t dword = first; dword < limit; dword += 4) { if (Graphics::Recorder::LookupLabelValue(dword, 4, 0).has_value()) { reason = &ValidateCounters::syncedLabel; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp index 6e3c1ddca..7ed38f979 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp @@ -235,6 +235,7 @@ public: // Entries leave the table with their batch (a memory bound only). The note also enters the // label's range as a pending write of the open batch (the flush hook syncs CPU reads), marked // as the label's own so it does not count as an overwrite of the entry. + static constexpr std::size_t LabelTableBytes = 64; void NoteLabel(std::uint64_t address, std::span bytes, std::uint64_t stamp, std::uint32_t queue); // Late rule: an entry whose stamp is not newer than the wait's submission is still the value // memory will hold, unless a CPU store touched the dword since it was recorded (the table @@ -285,6 +286,7 @@ public: static std::optional LookupLabel(std::uint64_t address, std::size_t bytes, std::uint64_t afterStamp, LabelRefusal* refusal = nullptr); // The value alone, for callers asking whether any label is pending in a dword (afterStamp 0). static std::optional LookupLabelValue(std::uint64_t address, std::size_t bytes, std::uint64_t afterStamp); + static bool WideLabelIn(std::uint64_t address, std::size_t bytes); // A label a queue worker decoded but has not recorded yet (Driver.cpp DeferredLabels): it // enters the table with no batch, and only a lookup made on the noting thread (a WAIT_REG_MEM // of the same queue) takes its value, since that queue's later work follows the label in queue @@ -520,6 +522,7 @@ private: std::uint64_t readGeneration = 0; // Dword addresses this batch noted in the label table (removed when it finishes). std::vector labelDwords; + std::vector::iterator> wideLabels; // Labels stored by this batch's completion actions (see AfterCompletions); subtracted from // the lock-free pending count when the batch finishes, whether or not its completions ran. std::uint32_t completionLabelCount = 0; @@ -709,6 +712,9 @@ private: // The queued entries (batch always null), by dword; a lookup on the noting queue's thread // prefers them (its newest store in program order), every other lookup sees `labels` alone. std::map queuedLabels; + std::multimap wideLabels; + std::uint64_t wideLabelBytes = 0; + bool wideLabelInLocked(std::uint64_t address, std::size_t bytes) const; // labels.size(), readable without the table mutex: a noted write skips the mutex while the // table is empty (most of the time between label groups). std::atomic recordedLabels{0}; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp index 474ac0976..3ab0636fe 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp @@ -1208,6 +1208,20 @@ std::optional Recorder::LookupLabel(std::uint64_t address, s return labelTableOwner->lookupLabel(address, bytes, afterStamp, refusal); } +bool Recorder::WideLabelIn(std::uint64_t address, std::size_t bytes) { + std::lock_guard tableLock(labelTableMutex); + return labelTableOwner != nullptr && labelTableOwner->wideLabelInLocked(address, bytes); +} + +bool Recorder::wideLabelInLocked(std::uint64_t address, std::size_t bytes) const { + if (wideLabels.empty() || bytes == 0) return false; + const auto end = address + bytes; + for (auto it = wideLabels.lower_bound(address >= wideLabelBytes ? address - wideLabelBytes + 1 : 0); it != wideLabels.end() && it->first < end; ++it) { + if (it->second > address) return true; + } + return false; +} + std::optional Recorder::LookupLabelValue(std::uint64_t address, std::size_t bytes, std::uint64_t afterStamp) { const auto hit = LookupLabel(address, bytes, afterStamp); if (!hit.has_value()) return std::nullopt; @@ -2501,7 +2515,16 @@ void Recorder::NoteLabel(std::uint64_t address, std::span bytes ensureOpen(); // The table entry before the write note: the note bumps the write generation a poller // watches, and a poller that sees the bump then finds the label without the GPU mutex. - noteLabelOn(*open, address, bytes, stamp, queue); + const bool tabled = bytes.size() <= LabelTableBytes; + if (tabled) { + noteLabelOn(*open, address, bytes, stamp, queue); + } else { + std::lock_guard tableLock(labelTableMutex); + labels.erase(labels.lower_bound(address), labels.lower_bound(address + bytes.size())); + recordedLabels.store(labels.size(), std::memory_order_relaxed); + open->wideLabels.push_back(wideLabels.emplace(address, address + bytes.size())); + wideLabelBytes = std::max(wideLabelBytes, bytes.size()); + } if (activeRecorder == this && pendingLabelSince.load(std::memory_order_relaxed) == NoPendingLabel) { pendingLabelSince.store(std::chrono::steady_clock::now().time_since_epoch().count(), std::memory_order_release); } @@ -2524,6 +2547,7 @@ bool Recorder::PendingLabelIn(std::uint64_t address, std::size_t bytes) const { if (bytes == 0) return false; const auto end = address + bytes; if (const auto first = labels.lower_bound(address); first != labels.end() && first->first < end) return true; + if (wideLabelInLocked(address, bytes)) return true; // A queued label of another queue is unordered against the caller on hardware (nothing that // queue recorded could have satisfied a wait yet); it is still reported, since the table mutex // is held anyway and a caller deciding a CPU store wants the conservative answer. @@ -3256,15 +3280,17 @@ void Recorder::finish(std::unique_ptr batch, bool wait, int source) { } // The batch's label entries leave the table (a later label to the same dword already replaced // its entry and belongs to another batch). Correctness never depended on this removal. - if (!batch->labelDwords.empty()) { + if (!batch->labelDwords.empty() || !batch->wideLabels.empty()) { std::lock_guard tableLock(labelTableMutex); for (const auto dword : batch->labelDwords) { const auto found = labels.find(dword); if (found != labels.end() && found->second.batch == batch.get()) labels.erase(found); } + for (const auto entry : batch->wideLabels) wideLabels.erase(entry); recordedLabels.store(labels.size(), std::memory_order_relaxed); } batch->labelDwords.clear(); + batch->wideLabels.clear(); release(*batch); } diff --git a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp index 8e07fcb18..22369a7a8 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp @@ -429,6 +429,38 @@ void lateLabelTests(Recorder& recorder) { Require(!Recorder::LookupLabel(a, 4, 5).has_value() && !Recorder::LookupLabel(b, 8, 5).has_value() && !recorder.PendingLabelIn(0x70000, 0x400), "(7) entries outlived their batch"); } +void largeLabelTests(Recorder& recorder) { + constexpr std::uint64_t slot = 0x71000, small = slot + 0x10, outside = slot - 4, wide = 0x72000; + const std::array one{std::byte{1}, std::byte{0}, std::byte{0}, std::byte{0}}; + std::array clear{}; + std::array limit{}; + limit.fill(std::byte{3}); + recorder.Sync(); + Require(recorder.PendingLabels() == 0 && !Recorder::PendingLabelSince().has_value() && !Recorder::WideLabelIn(0, ~0ull), "the table is not empty before the large label tests"); + recorder.NoteLabel(small, one, 100, 0); + recorder.NoteLabel(outside, one, 100, 0); + Recorder::CloseLabelGroup(AgcDriver::GuestMemory::TrackerGeneration()); + Require(recorder.PendingLabels() == 2 && Recorder::LookupLabel(small, 4, 50).has_value(), "4-byte labels did not enter the table"); + recorder.NoteLabel(slot, clear, 101, 0); + Recorder::CloseLabelGroup(AgcDriver::GuestMemory::TrackerGeneration()); + Require(recorder.PendingLabels() == 1 && Recorder::LookupLabel(outside, 4, 50).has_value(), "a label larger than the table bound entered dwords or removed one outside its range"); + Require(!Recorder::LookupLabel(small, 4, 50).has_value() && !Recorder::LookupLabel(slot, 8, 0).has_value(), "an older label under a larger one still composes the value"); + Require(Recorder::WideLabelIn(slot, 4) && Recorder::WideLabelIn(slot + clear.size() - 4, 4) && Recorder::WideLabelIn(slot - 0x100, 0x104) && !Recorder::WideLabelIn(slot - 0x100, 0x100) && !Recorder::WideLabelIn(slot + clear.size(), 4), "a large label's range is wrong"); + Require(recorder.PendingLabelIn(slot + 0x20, 0x20) && recorder.PendingWriteOverlaps(slot, clear.size()), "a large label is not pending"); + Require(Recorder::PendingLabelSince().has_value(), "a large label did not start the label flush deadline"); + recorder.NoteLabel(small, one, 102, 0); + Recorder::CloseLabelGroup(AgcDriver::GuestMemory::TrackerGeneration()); + const auto later = Recorder::LookupLabel(small, 4, 101); + Require(later.has_value() && later->value == 1 && later->stamp == 102, "a label after a large one is not served"); + recorder.NoteLabel(wide, limit, 103, 0); + Recorder::CloseLabelGroup(AgcDriver::GuestMemory::TrackerGeneration()); + Require(recorder.PendingLabels() == 2 + Recorder::LabelTableBytes / 4 && !Recorder::WideLabelIn(wide, limit.size()), "a label at the table bound did not enter every dword"); + const auto bound = Recorder::LookupLabel(wide + Recorder::LabelTableBytes - 8, 8, 0); + Require(bound.has_value() && bound->value == 0x0303030303030303ull && bound->stamp == 103, "a label at the table bound is not served"); + recorder.Sync(); + Require(recorder.PendingLabels() == 0 && !Recorder::WideLabelIn(0, ~0ull) && !recorder.PendingLabelIn(slot, clear.size()) && !recorder.PendingWriteOverlaps(slot, clear.size()), "large label tests left entries, ranges or writes behind"); +} + // (6) GuestMemory::UnchangedSinceCollected: false outside the watched arena, and around a // MarkWritten or a CPU write of the block inside it. void unchangedSinceTests() { @@ -2581,6 +2613,7 @@ int main() { batchStampTests(recorder); labelTests(recorder); lateLabelTests(recorder); + largeLabelTests(recorder); unchangedSinceTests(); closeRaceTests(device, recorder); keyProofTests(device, recorder); From 84974a9b2a244bc61bdff8ae1e570e0f65e30a02 Mon Sep 17 00:00:00 2001 From: DotDebian Date: Mon, 5 Oct 2026 10:29:48 +0200 Subject: [PATCH 104/164] fix(shader): locate image LOD, mip and sample index by the instruction's dimension The address operands of a MIMG instruction are laid out by its own DIM field: the decoder counts them that way. The SPIR-V emitter located the gradients, the LOD, the LOD clamp, the mip level and the sample index after the coordinate count of the bound T# instead, so an array instruction on a non-array texture took the slice as its LOD. An instruction that addresses fewer coordinates than its image has is refused instead of reading the next address component as a coordinate. Co-Authored-By: Claude Opus 5.5 --- .../tests/execution/ImageAddressDimension.cpp | 188 ++++++++++++++++++ .../SpirvBackend/src/SpirvImageEmitter.cpp | 31 +-- 2 files changed, 206 insertions(+), 13 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/ImageAddressDimension.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAddressDimension.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAddressDimension.cpp new file mode 100644 index 000000000..0910f962a --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAddressDimension.cpp @@ -0,0 +1,188 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#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 Words = 32; +constexpr std::uint32_t Size = 16; +constexpr std::uint32_t Levels = 5; +constexpr std::uint32_t Format8888UNorm = 56; +constexpr std::uint32_t Type2D = 9; +constexpr std::uint32_t ArrayInput = 0; +constexpr std::uint32_t ArrayResult = 8; +constexpr std::uint32_t PlainInput = 16; +constexpr std::uint32_t PlainResult = 24; +alignas(256) std::array Buffer{}; +alignas(256) std::array Texels{}; + +alignas(256) constexpr std::array Code{ + 0x34020087, 0xe0301000, 0x80000201, 0xe0301004, 0x80000301, 0xe0301008, 0x80000401, 0xe030100c, + 0x80000501, 0xe0301010, 0x80000601, 0xe0301014, 0x80000701, 0xe0301018, 0x80000801, 0xe030101c, + 0x80000901, 0xe0301040, 0x80001001, 0xe0301044, 0x80001101, 0xe0301048, 0x80001201, 0xe030104c, + 0x80001301, 0xe0301050, 0x80001401, 0xe0301054, 0x80001501, 0xbf8c3f70, 0xf0900f28, 0x00610c02, + 0xe0701020, 0x80000c01, 0xe0701024, 0x80000d01, 0xe0701028, 0x80000e01, 0xe070102c, 0x80000f01, + 0xf0041f28, 0x00010c06, 0xe0701030, 0x80000c01, 0xe0701034, 0x80000d01, 0xe0701038, 0x80000e01, + 0xe070103c, 0x80000f01, 0xf0900f08, 0x00610c10, 0xe0701060, 0x80000c01, 0xe0701064, 0x80000d01, + 0xe0701068, 0x80000e01, 0xe070106c, 0x80000f01, 0xf0041f08, 0x00010c13, 0xe0701070, 0x80000c01, + 0xe0701074, 0x80000d01, 0xe0701078, 0x80000e01, 0xe070107c, 0x80000f01, 0xbf810000, +}; + +alignas(256) constexpr std::array NarrowCode{ + 0x34020087, 0xe0301040, 0x80001001, 0xe0301044, 0x80001101, 0xbf8c3f70, 0xf0900f00, 0x00610c10, + 0xe0701070, 0x80000c01, 0xbf810000, +}; + +struct Sample { + std::uint32_t level; + std::uint32_t x; + std::uint32_t y; +}; + +Sample SampleOf(std::uint32_t tid) { + return {tid % Levels, (tid * 7u + 3u) % Size, (tid * 5u + 1u) % Size}; +} + +std::uint32_t Bits(float value) { + return std::bit_cast(value); +} + +void FillInput() { + Buffer.fill(0xdeadbeefu); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto sample = SampleOf(tid); + const auto u = Bits((static_cast(sample.x) + 0.5f) / static_cast(Size)); + const auto v = Bits((static_cast(sample.y) + 0.5f) / static_cast(Size)); + const auto lod = Bits(static_cast(sample.level)); + const auto x = sample.x >> sample.level; + const auto y = sample.y >> sample.level; + auto* words = &Buffer[tid * Words]; + const std::array array{u, v, Bits(0.0f), lod, x, y, 0u, sample.level}; + const std::array plain{u, v, lod, x, y, sample.level}; + std::copy(array.begin(), array.end(), words + ArrayInput); + std::copy(plain.begin(), plain.end(), words + PlainInput); + } +} + +void FillTexture() { + const auto mips = AgcDriver::Graphics::ComputeMipLayout(AgcDriver::Graphics::TextureTileMode::kLinear, Format8888UNorm, Size, Size, Levels); + Require(AgcDriver::Graphics::ComputeSurfaceSize(mips, 1) <= Texels.size(), "image address dimension: the mip chain does not fit the texel storage"); + Texels.fill(0xeeu); + for (std::uint32_t level = 0; level < Levels; ++level) { + const auto& mip = mips[level]; + for (std::uint32_t y = 0; y < mip.height; ++y) { + for (std::uint32_t x = 0; x < mip.width; ++x) { + auto* texel = &Texels[mip.tiledOffset + static_cast(y) * mip.pitchBytes + x * 4u]; + texel[0] = static_cast(level); + texel[1] = static_cast(x); + texel[2] = static_cast(y); + texel[3] = 255u; + } + } + } +} + +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::array TextureDescriptor(const void* data) { + const auto address = static_cast(reinterpret_cast(data)); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (((Size - 1u) & 3u) << 30u), + ((Size - 1u) >> 2u) | ((Size - 1u) << 14u), + 0xfacu | ((Levels - 1u) << 16u) | (Type2D << 28u), + 0u, + (Levels - 1u) << 4u, + 0u, + 0u, + }; +} + +std::array SamplerDescriptor() { + return {0u, 0xfffu << 12u, 1u << 26u, 0u}; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code) { + std::vector userData(16, 0u); + const auto buffer = BufferDescriptor(Buffer.data(), static_cast(Buffer.size() * 4u)); + const auto texture = TextureDescriptor(Texels.data()); + const auto sampler = SamplerDescriptor(); + std::copy(buffer.begin(), buffer.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + std::copy(sampler.begin(), sampler.end(), userData.begin() + 12); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + constexpr std::array names{"image_sample_l 2d_array", "image_load_mip 2d_array", "image_sample_l 2d", "image_load_mip 2d"}; + constexpr std::array results{ArrayResult, ArrayResult + 4u, PlainResult, PlainResult + 4u}; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto sample = SampleOf(tid); + const std::array expected{sample.level, sample.x >> sample.level, sample.y >> sample.level, 255u}; + for (std::uint32_t index = 0; index < names.size(); ++index) { + for (std::uint32_t component = 0; component < 4u; ++component) { + const float value = std::bit_cast(Buffer[tid * Words + results[index] + component]) * 255.0f; + Require(std::lround(value) == static_cast(expected[component]), std::string(names[index]) + " on a 2D texture: thread " + std::to_string(tid) + " component " + std::to_string(component) + " is " + std::to_string(value) + ", expected " + std::to_string(expected[component])); + } + } + } +} + +void RequireRefused(AgcDriver::VulkanDevice& device) { + std::string refusal; + try { + Run(device, NarrowCode); + } catch (const std::exception& error) { + refusal = error.what(); + } + Require(refusal.find("too few coordinate components") != std::string::npos, "image_sample_l 1d on a 2D texture was not refused: " + refusal); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + FillInput(); + FillTexture(); + Run(*device, Code); + Check(); + RequireRefused(*device); + std::puts("image address dimension tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp index 5edd79713..9fe529a7c 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp @@ -82,6 +82,10 @@ bool HasFlag(const MemoryInfo& mem, std::uint32_t flag) { return (mem.imageSampleFlags & flag) != 0u; } +const RdnaImageDimensionInfo& AddressDimension(const ImageEmitAccess& access) { + return RdnaImageDimensionInfoFor(access.mem.imageDimension); +} + std::uint32_t ZeroF32(SpirvEmitterState& state) { return ConstantF32(state, 0); } @@ -111,10 +115,10 @@ std::uint32_t AddressF32(SpirvValueEmitContext& ctx, const ImageEmitAccess& acce return GetRdnaImageAddressComponentLayout(access.mem.imageSampleFlags, component).bitWidth == 16u ? EmitF16BitsToF32(ctx.state, value) : Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), value); } -ImageSampleLayout Layout(const MemoryInfo& mem, RdnaImageDimension dimension) { +ImageSampleLayout Layout(const MemoryInfo& mem) { ImageSampleLayout layout; std::uint32_t cursor = 0; - const auto& info = RdnaImageDimensionInfoFor(dimension); + const auto& info = RdnaImageDimensionInfoFor(mem.imageDimension); if (HasFlag(mem, RdnaImageSampleFlagOffset)) { layout.offset = cursor++; } @@ -155,8 +159,8 @@ std::uint32_t CubeLayer(SpirvEmitterState& state, std::uint32_t value) { return result; } -std::uint32_t CoordF32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, std::uint32_t first, std::uint32_t components) { - if (first == NoImageComponent || access.mem.imageAddressComponents < first + components) { +std::uint32_t CoordF32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, std::uint32_t first, std::uint32_t components, std::uint32_t encoded) { + if (first == NoImageComponent || encoded < components || access.mem.imageAddressComponents < first + encoded) { ctx.Fail(access.inst, "has an image address with too few coordinate components"); } const bool cube = access.image.cube; @@ -184,7 +188,7 @@ std::uint32_t CoordF32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access std::uint32_t CoordU32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { const auto components = RdnaImageDimensionInfoFor(access.image.dimension).coordinateComponents; - if (access.mem.imageAddressComponents < components) { + if (AddressDimension(access).coordinateComponents < components || access.mem.imageAddressComponents < components) { ctx.Fail(access.inst, "has an image address with too few coordinate components"); } const auto x = AddressU32(ctx, access, 0); @@ -206,7 +210,7 @@ std::uint32_t LodU32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) if (!access.mem.imageHasMip) { return ConstantU32(ctx.state, 0); } - const auto component = RdnaImageDimensionInfoFor(access.image.dimension).coordinateComponents; + const auto component = AddressDimension(access).coordinateComponents; if (access.mem.imageAddressComponents <= component) { ctx.Fail(access.inst, "has an image address without the mip level component"); } @@ -691,7 +695,7 @@ void EmitQueryLodOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { auto& state = ctx.state; state.module.EmitCapability(spv::CapabilityImageQuery); const auto sampled = MakeSampledImage(state, access.mem.resource, access.mem.sampler, access.slot); - const auto coord = CoordF32(ctx, access, 0, RdnaImageDimensionInfoFor(access.image.dimension).spatialComponents); + const auto coord = CoordF32(ctx, access, 0, RdnaImageDimensionInfoFor(access.image.dimension).spatialComponents, AddressDimension(access).spatialComponents); const auto lod = state.module.AllocateId(); state.module.AddFunction(spv::OpImageQueryLod, TypeF32Vector(state, 2), lod, sampled, coord); std::uint32_t values[4] = {ConstantU32(state, 0), ConstantU32(state, 0), ConstantU32(state, 0), ConstantU32(state, 0)}; @@ -715,10 +719,11 @@ void EmitReadOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { const auto color = state.module.AllocateId(); const auto coord = CoordU32(ctx, access); if (dimensionInfo.multisampled != 0u) { - if (access.mem.imageAddressComponents <= dimensionInfo.coordinateComponents) { + const auto& addressInfo = AddressDimension(access); + if (addressInfo.multisampled == 0u || access.mem.imageAddressComponents <= addressInfo.coordinateComponents) { ctx.Fail(access.inst, "has no sample index in the image address"); } - state.module.AddFunction(spv::OpImageFetch, ImageVectorType(state, numericClass, 4), color, descriptor, coord, spv::ImageOperandsSampleMask, AddressU32(ctx, access, dimensionInfo.coordinateComponents)); + state.module.AddFunction(spv::OpImageFetch, ImageVectorType(state, numericClass, 4), color, descriptor, coord, spv::ImageOperandsSampleMask, AddressU32(ctx, access, addressInfo.coordinateComponents)); } else { state.module.AddFunction(spv::OpImageFetch, ImageVectorType(state, numericClass, 4), color, descriptor, coord, spv::ImageOperandsLodMask, LodU32(ctx, access)); } @@ -783,12 +788,12 @@ void EmitAtomicOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { SampleSetup MakeSampleSetup(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { const auto& dimensionInfo = RdnaImageDimensionInfoFor(access.image.dimension); - const auto layout = Layout(access.mem, access.image.dimension); + const auto layout = Layout(access.mem); const bool dref = HasFlag(access.mem, RdnaImageSampleFlagCompare); if (dref && access.image.conversionFormat != IrBufferFormat::Invalid) { ctx.Fail(access.inst, "uses depth comparison with a packed integer image"); } - const auto coord = CoordF32(ctx, access, layout.coord, dimensionInfo.coordinateComponents); + const auto coord = CoordF32(ctx, access, layout.coord, dimensionInfo.coordinateComponents, AddressDimension(access).coordinateComponents); return {dimensionInfo, layout, access.image.numericClass, dref, coord}; } @@ -908,8 +913,8 @@ void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, con std::vector operands; if (HasFlag(mem, RdnaImageSampleFlagDerivative)) { operandMask |= spv::ImageOperandsGradMask; - operands.push_back(CoordF32(ctx, access, setup.layout.gradX, setup.dimensionInfo.spatialComponents)); - operands.push_back(CoordF32(ctx, access, setup.layout.gradY, setup.dimensionInfo.spatialComponents)); + operands.push_back(CoordF32(ctx, access, setup.layout.gradX, setup.dimensionInfo.spatialComponents, AddressDimension(access).spatialComponents)); + operands.push_back(CoordF32(ctx, access, setup.layout.gradY, setup.dimensionInfo.spatialComponents, AddressDimension(access).spatialComponents)); } else if (explicitLod) { operandMask |= spv::ImageOperandsLodMask; operands.push_back(HasFlag(mem, RdnaImageSampleFlagLod) ? AddressF32(ctx, access, setup.layout.lod) : ZeroF32(state)); From 16f19c51ad0e0f90925550285e25f70019e544e0 Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Sun, 4 Oct 2026 18:20:15 +1300 Subject: [PATCH 105/164] feat(agc): thick mip tails of mipmapped 3D textures 3D textures in SW_4KB_S or SW_64KB_S whose smaller levels reach the mip tail were refused ("3D texture mip tails are not implemented"). The tail now follows addrlib's GFX10 thick layout (Gfx10Lib::ComputeSurfaceInfoMacroTiled): - a level enters the tail once its size, rounded up, fits the thick tail extent (the block with its height halved for 4 KiB blocks and its width halved for 64 KiB blocks) and at most GetMaxNumMipsInTail levels remain (5 for 4 KiB, 10 for 64 KiB); - each slab starts with one tail block, then the other levels from the smallest to level 0; - tail level i sits in slot maxMipsInTail - 1 - i, at the element offset addrlib derives from that slot's byte offset and the 256-byte thick micro block, at every depth slice. The tests check element addresses of five volumes (4 KiB and 64 KiB blocks, 1 to 16 bytes per element, odd sizes, a chain that starts in the tail) against Mesa addrlib's Addr2ComputeSurfaceAddrFromCoord for a Navi10 configuration. --- .../Graphics/include/TextureTiling.hpp | 6 +- .../Graphics/src/TextureTiling.cpp | 39 +++++++++--- .../libSceAgcDriver/tests/TextureTiling.cpp | 60 +++++++++++++++++-- 3 files changed, 89 insertions(+), 16 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp index b4a5c6427..b83d4f4f8 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp @@ -41,9 +41,9 @@ std::array ThinBlockLayout(TextureTileMode tileMode, std::uint // levels and their padding). A retile of the mip writes every byte of these and no other. std::vector> CoveredMipBytes(TextureTileMode tileMode, std::uint32_t bytesPerElement, const TileMipLayout& mip); -// 3D surface. Each slab holds the whole mip chain, smallest level first. Depth slice z of a level is detiled -// from slab z / blockDepth (slabBytes apart) with the swizzle's slice input set to z, into linear -// slices sliceLinearBytes apart. +// 3D surface. Each slab holds the whole mip chain: the mip tail block, if any, then the other levels from the +// smallest to level 0. Depth slice z of a level is detiled from slab z / blockDepth (slabBytes apart) with the +// swizzle's slice input set to z, into linear slices sliceLinearBytes apart. struct ThickLayout { std::vector mips; std::uint32_t depth; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp index cc3b08146..a25632749 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp @@ -289,6 +289,8 @@ ThickLayout ComputeThickLayout(TextureTileMode tileMode, std::uint32_t format, s result.mips.resize(mipCount); std::array block{CalcLinearBlockWidth(bytesPerElement), 1u, 1u}; std::uint32_t blockBytes = 0; + std::uint32_t maxTailLevels = 0; + auto firstTailLevel = mipCount; if (tileMode != TextureTileMode::kLinear) { block = ThickBlockExtent(tileMode, bytesPerElement); blockBytes = tileMode == TextureTileMode::kStandard4KB ? 4096u : 65536u; @@ -296,18 +298,22 @@ ThickLayout ComputeThickLayout(TextureTileMode tileMode, std::uint32_t format, s const auto tailWidth = log2BlockBytes % 3u == 1u ? block[0] >> 1u : block[0]; const auto tailHeight = log2BlockBytes % 3u == 0u ? block[1] >> 1u : block[1]; const auto effectiveLog2 = log2BlockBytes - (log2BlockBytes - 8u) / 3u; - const auto maxTailLevels = effectiveLog2 <= 11u ? 1u + (1u << (effectiveLog2 - 9u)) : effectiveLog2 - 4u; - for (std::uint32_t level = 0; mipCount > 1 && level < mipCount; ++level) - Require(std::max(width >> level, 1u) > tailWidth || std::max(height >> level, 1u) > tailHeight || mipCount - level > maxTailLevels, "3D texture mip tails are not implemented"); + maxTailLevels = effectiveLog2 <= 11u ? 1u + (1u << (effectiveLog2 - 9u)) : effectiveLog2 - 4u; + for (std::uint32_t level = 0; mipCount > 1 && level < mipCount; ++level) { + if (ShiftCeil(width, level) <= tailWidth && ShiftCeil(height, level) <= tailHeight && mipCount - level <= maxTailLevels) { + firstTailLevel = level; + break; + } + } } result.blockDepth = block[2]; - std::uint64_t tiledOffset = 0; + std::uint64_t tiledOffset = firstTailLevel < mipCount ? blockBytes : 0u; std::uint64_t linearOffset = 0; for (auto level = mipCount; level-- > 0;) { auto& mip = result.mips[level]; mip.width = std::max(width >> level, 1u); mip.height = std::max(height >> level, 1u); - mip.tail = false; + mip.tail = level >= firstTailLevel; mip.tailX = 0; mip.tailY = 0; const bool tiled = tileMode != TextureTileMode::kLinear; @@ -315,17 +321,34 @@ ThickLayout ComputeThickLayout(TextureTileMode tileMode, std::uint32_t format, s const auto levelHeight = tiled ? std::max((height + (1u << level) - 1u) >> level, 1u) : mip.height; const auto paddedWidth = AlignUp(levelWidth, block[0]); mip.pitchBytes = paddedWidth * bytesPerElement; - if (!tiled) { + if (mip.tail) { + constexpr std::uint32_t microBlock[5][2] = {{8, 4}, {4, 4}, {4, 4}, {4, 2}, {2, 2}}; + const auto element = static_cast(std::countr_zero(bytesPerElement)); + const auto slot = maxTailLevels - 1u - (level - firstTailLevel); + const auto slotOffset = slot > 6u ? 16u << slot : slot << 8u; + for (std::uint32_t bit = 0; bit < 6u; ++bit) { + mip.tailX |= ((slotOffset >> (9u + 2u * bit)) & 1u) << bit; + mip.tailY |= ((slotOffset >> (8u + 2u * bit)) & 1u) << bit; + } + mip.tailX *= microBlock[element][0]; + mip.tailY *= microBlock[element][1]; + mip.blocksPerRow = 1u; + mip.pitchBytes = block[0] * bytesPerElement; + mip.tiledOffset = 0; + mip.tiledSize = blockBytes; + } else if (!tiled) { mip.blocksPerRow = paddedWidth; mip.tiledSize = static_cast(mip.pitchBytes) * mip.height; } else { mip.blocksPerRow = paddedWidth / block[0]; mip.tiledSize = static_cast(mip.blocksPerRow) * (AlignUp(levelHeight, block[1]) / block[1]) * blockBytes; } + if (!mip.tail) { + mip.tiledOffset = tiledOffset; + tiledOffset += mip.tiledSize; + } mip.linearSize = static_cast(mip.pitchBytes) * mip.height; - mip.tiledOffset = tiledOffset; mip.linearOffset = linearOffset; - tiledOffset += mip.tiledSize; linearOffset += mip.linearSize; } result.slabBytes = tiledOffset; diff --git a/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp b/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp index 38324bf69..3058cc098 100644 --- a/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,14 @@ void reject(TAction action, std::string_view reason) { throw std::runtime_error(std::string("expected texture tiling rejection: ") + std::string(reason)); } +struct ElementAddress { + std::uint32_t mip; + std::uint32_t x; + std::uint32_t y; + std::uint32_t z; + std::uint64_t address; +}; + std::uint64_t equationOffset(const TextureSwizzleEquation& equation, std::uint32_t x, std::uint32_t y, std::uint32_t z) { std::uint64_t offset = 0; for (std::uint32_t bit = 0; bit < 16; ++bit) { @@ -34,6 +43,39 @@ std::uint64_t equationOffset(const TextureSwizzleEquation& equation, std::uint32 return offset; } +GuestTextureResource thickVolume(TextureTileMode tileMode, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t depth, std::uint32_t mipCount) { + GuestTextureResource resource{}; + resource.width = width; + resource.height = height; + resource.depthOrLastArray = depth - 1u; + resource.mipCount = mipCount; + resource.tileMode = tileMode; + resource.dimension = TextureDimension::k3D; + resource.format = format; + return resource; +} + +void requireThickAddresses(const GuestTextureResource& resource, std::uint32_t bytesPerElement, std::uint32_t firstTailLevel, std::uint64_t guestBytes, std::span expected, const std::string& what) { + const auto geometry = DescribeSurface(resource); + Require(geometry.thick && geometry.mips.size() == resource.mipCount && geometry.guestBytes == guestBytes, what + ": guest size differs from addrlib"); + for (std::uint32_t level = 0; level < resource.mipCount; ++level) Require(geometry.mips[level].tail == (level >= firstTailLevel), what + ": mip " + std::to_string(level) + " is on the wrong side of the mip tail"); + const auto blockBytes = resource.tileMode == TextureTileMode::kStandard4KB ? 4096u : 65536u; + const auto* equation = FindTextureSwizzleEquation(resource.tileMode == TextureTileMode::kStandard4KB ? 0x105u : 0x109u, bytesPerElement); + Require(equation != nullptr, what + ": missing thick swizzle equation"); + const auto block = ThickBlockExtent(resource.tileMode, bytesPerElement); + for (const auto& element : expected) { + Require(geometry.HasLayer(element.mip, element.z), what + ": sample slice lies outside its level"); + const auto& mip = geometry.mips.at(element.mip); + auto address = geometry.GuestLayerOffset(element.z) + mip.tiledOffset; + if (mip.tail) { + address += equationOffset(*equation, element.x + mip.tailX, element.y + mip.tailY, element.z); + } else { + address += (static_cast(element.y / block[1]) * mip.blocksPerRow + element.x / block[0]) * blockBytes + equationOffset(*equation, element.x, element.y, element.z); + } + Require(address == element.address, what + ": mip " + std::to_string(element.mip) + " element (" + std::to_string(element.x) + ", " + std::to_string(element.y) + ", " + std::to_string(element.z) + ") detiles from " + std::to_string(address) + " instead of addrlib's " + std::to_string(element.address)); + } +} + } void RunTextureTilingTests() { @@ -154,11 +196,19 @@ void RunTextureTilingTests() { const auto odd = DescribeSurface(volume); Require(odd.mips[1].width == 16 && odd.mips[1].blocksPerRow == 3 && odd.mips[1].tiledSize == 12288, "a 3D level must be padded from its size rounded up, as addrlib does"); Require(odd.mips[0].blocksPerRow == 5 && odd.mips[0].tiledOffset == 12288 && odd.mips[0].tiledSize == 40960, "3D mip 0 of a non-power-of-two volume changed"); - volume.width = 8; - volume.height = 8; - volume.depthOrLastArray = 7; - volume.mipCount = 2; - reject([&] { DescribeSurface(volume); }, "3D texture mip tails are not implemented"); + } + + { + constexpr ElementAddress chainInTail[] = {{0, 0, 0, 0, 0x800}, {0, 7, 7, 7, 0xffc}, {0, 4, 2, 4, 0xe20}, {0, 2, 7, 0, 0x968}, {0, 7, 0, 7, 0xed4}, {1, 0, 0, 0, 0x300}, {1, 3, 3, 3, 0x3fc}, {1, 2, 1, 2, 0x3c8}, {1, 1, 3, 0, 0x32c}, {1, 3, 0, 3, 0x3d4}}; + requireThickAddresses(thickVolume(TextureTileMode::kStandard4KB, 56, 8, 8, 8, 2), 4, 0, 4096, chainInTail, "SW_4KB_S 32 bpp 8x8x8, 2 levels"); + constexpr ElementAddress unevenTail[] = {{0, 0, 0, 0, 0x6000}, {0, 32, 19, 11, 0x1f0b8}, {0, 16, 6, 6, 0x85a0}, {0, 11, 19, 0, 0xc06c}, {0, 32, 0, 11, 0x1a090}, {1, 0, 0, 0, 0x3000}, {1, 15, 9, 5, 0x4e5c}, {1, 8, 3, 3, 0x40b8}, {1, 5, 9, 0, 0x3a0c}, {1, 15, 0, 5, 0x4654}, {2, 0, 0, 0, 0x1000}, {2, 7, 4, 2, 0x13c4}, {2, 4, 1, 1, 0x1218}, {2, 2, 4, 0, 0x1140}, {2, 7, 0, 2, 0x12c4}, {3, 0, 0, 0, 0x800}, {3, 3, 1, 0, 0x84c}, {3, 2, 0, 0, 0x840}, {3, 1, 1, 0, 0x80c}, {3, 3, 0, 0, 0x844}, {4, 0, 0, 0, 0x300}, {4, 1, 0, 0, 0x304}, {4, 0, 0, 0, 0x300}, {5, 0, 0, 0, 0x200}}; + requireThickAddresses(thickVolume(TextureTileMode::kStandard4KB, 56, 33, 20, 12, 6), 4, 3, 131072, unevenTail, "SW_4KB_S 32 bpp 33x20x12, 6 levels"); + constexpr ElementAddress wideTail[] = {{0, 0, 0, 0, 0x20000}, {0, 39, 23, 19, 0xb0bf8}, {0, 20, 8, 10, 0x2e280}, {0, 13, 23, 0, 0x41b28}, {0, 39, 0, 19, 0x902d8}, {1, 0, 0, 0, 0x10000}, {1, 19, 11, 9, 0x1e178}, {1, 10, 4, 5, 0x11c50}, {1, 6, 11, 0, 0x14360}, {1, 19, 0, 9, 0x1a058}, {2, 0, 0, 0, 0x8000}, {2, 9, 5, 4, 0x9c28}, {2, 5, 2, 2, 0x8388}, {2, 3, 5, 0, 0x8868}, {2, 9, 0, 4, 0x9408}, {3, 0, 0, 0, 0x4000}, {3, 4, 2, 1, 0x4310}, {3, 2, 1, 1, 0x4070}, {3, 1, 2, 0, 0x4108}, {3, 4, 0, 1, 0x4210}, {4, 0, 0, 0, 0x1000}, {4, 1, 0, 0, 0x1008}, {5, 0, 0, 0, 0xa00}}; + requireThickAddresses(thickVolume(TextureTileMode::kStandard64KB, 71, 40, 24, 20, 6), 8, 2, 786432, wideTail, "SW_64KB_S 64 bpp 40x24x20, 6 levels"); + constexpr ElementAddress byteTail[] = {{0, 0, 0, 0, 0x20000}, {0, 99, 59, 69, 0x11c8af}, {0, 50, 20, 35, 0x8d116}, {0, 33, 59, 0, 0x4c829}, {0, 99, 0, 69, 0xf8087}, {1, 0, 0, 0, 0x10000}, {1, 49, 29, 34, 0x7d919}, {1, 25, 10, 17, 0x13a25}, {1, 16, 29, 0, 0x15908}, {1, 49, 0, 34, 0x79011}, {2, 0, 0, 0, 0x8000}, {2, 24, 14, 16, 0xbb20}, {2, 12, 5, 8, 0x8748}, {2, 8, 14, 0, 0x8b20}, {2, 24, 0, 16, 0xb200}, {3, 0, 0, 0, 0x4000}, {3, 11, 6, 7, 0x43b7}, {3, 6, 2, 4, 0x40e2}, {3, 4, 6, 0, 0x4160}, {3, 11, 0, 7, 0x4297}, {4, 0, 0, 0, 0x1000}, {4, 5, 2, 3, 0x1075}, {4, 3, 1, 2, 0x101b}, {4, 2, 2, 0, 0x1022}, {4, 5, 0, 3, 0x1055}, {5, 0, 0, 0, 0xa00}, {5, 2, 0, 1, 0xa06}, {5, 1, 0, 0, 0xa01}, {6, 0, 0, 0, 0x900}}; + requireThickAddresses(thickVolume(TextureTileMode::kStandard64KB, 1, 100, 60, 70, 7), 1, 2, 1179648, byteTail, "SW_64KB_S 8 bpp 100x60x70, 7 levels"); + constexpr ElementAddress wideElementTail[] = {{0, 0, 0, 0, 0x3000}, {0, 8, 4, 2, 0x5880}, {0, 4, 1, 1, 0x4030}, {0, 3, 4, 0, 0x3a40}, {0, 8, 0, 2, 0x5080}, {1, 0, 0, 0, 0x1000}, {1, 3, 1, 0, 0x1260}, {1, 2, 0, 0, 0x1200}, {1, 1, 1, 0, 0x1060}, {1, 3, 0, 0, 0x1240}, {2, 0, 0, 0, 0x800}, {2, 1, 0, 0, 0x840}, {3, 0, 0, 0, 0x300}}; + requireThickAddresses(thickVolume(TextureTileMode::kStandard4KB, 77, 9, 5, 3, 4), 16, 2, 24576, wideElementTail, "SW_4KB_S 128 bpp 9x5x3, 4 levels"); } { From 7bb6c59e8353bad0c6dca0a0faccb193a3c3e3c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 12:26:25 +0200 Subject: [PATCH 106/164] fix(shader): v_mullit_f32 legacy rules, v_cvt_pk_u8_f32 rounding and VOP3 neg/abs Co-Authored-By: Claude --- .../execution/Vop3LegacySourceModifiers.cpp | 186 ++++++++++++++++++ .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 4 +- .../Translation/TranslationContext.hpp | 1 + .../Translation/src/ConvertInstructions.cpp | 9 +- .../Translation/src/FloatInstructions.cpp | 19 ++ .../Translation/src/VectorInstructions.cpp | 3 +- 6 files changed, 219 insertions(+), 3 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Vop3LegacySourceModifiers.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3LegacySourceModifiers.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3LegacySourceModifiers.cpp new file mode 100644 index 000000000..5562b5295 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3LegacySourceModifiers.cpp @@ -0,0 +1,186 @@ +#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 +#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, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeadbeef, 0x7e1602ff, + 0xdeadbeef, 0x7e1802ff, 0xdeadbeef, 0x7e1a02ff, 0xdeadbeef, 0x7e1c02ff, 0xdeadbeef, 0x7e1e02ff, + 0xdeadbeef, 0x7e2002ff, 0xdeadbeef, 0x7e2202ff, 0xdeadbeef, 0x7e2402ff, 0xdeadbeef, 0x7e2602ff, + 0xdeadbeef, 0x7e2802ff, 0xdeadbeef, 0x7e2a02ff, 0xdeadbeef, 0x7e2c02ff, 0xdeadbeef, 0x7e2e02ff, + 0xdeadbeef, 0x7e3002ff, 0xdeadbeef, 0x7e3202ff, 0xdeadbeef, 0xd550000a, 0x041a0b04, 0xd550000b, + 0x241a0b04, 0xd550000c, 0x441a0b04, 0xd550020d, 0x841a0b04, 0xd550070e, 0x641a0b04, 0xd550050f, + 0x841a0b04, 0xd55e0010, 0x041a0f04, 0xd55e0011, 0x241a0f04, 0xd55e0112, 0x041a0f04, 0xd55e0113, + 0x241a0f04, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, + 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x3fc00000u, 0x00000000u, 0xbf800000u, 0x7e318ad6u}, + {0xc0000000u, 0x3fc00000u, 0x3fc00000u, 0x1b3a953cu}, + {0xff7ffffeu, 0x437e8000u, 0x7f800000u, 0x8511fd5bu}, + {0x3f19999au, 0x00000000u, 0x7f800000u, 0x4607d625u}, + {0x7fc00001u, 0x3fc00000u, 0x3f000000u, 0xe4fead80u}, + {0x40200000u, 0x7f800000u, 0x3f000002u, 0xb68c914bu}, + {0x437f8000u, 0x437e8000u, 0x7f7fffffu, 0x400224c0u}, + {0xffc00000u, 0xc0000000u, 0x7f7fffffu, 0xd9c578ddu}, + {0x7f7fffffu, 0xbf800000u, 0x7f61b1e6u, 0xc7c63fe1u}, + {0x7f800000u, 0x3fc00000u, 0xbf400000u, 0x00b09f63u}, + {0x7f800000u, 0x3f19999au, 0x7f800000u, 0x054367bau}, + {0xff800000u, 0x7f800000u, 0xff7fffffu, 0xdee7b644u}, + {0x7fc00001u, 0x7f800000u, 0xc9a27dd4u, 0xeac29dbfu}, + {0xffc00000u, 0x7f7fffffu, 0xb587728cu, 0x929cedc6u}, + {0x7f7fffffu, 0x3f800000u, 0x7f7fffffu, 0x034bd1bau}, + {0x7f7fffffu, 0xbf800000u, 0xff800000u, 0x88c035d3u}, + {0x0da24260u, 0x7f7fffffu, 0xd4ab4b77u, 0xc51bdf32u}, + {0x00000000u, 0x00000000u, 0xf149f2cau, 0x9531985du}, + {0x00000000u, 0x00000000u, 0x7f800000u, 0x36f675ccu}, + {0x00000000u, 0x3fc00000u, 0xbf000002u, 0xdbc496cbu}, + {0x00000000u, 0x7f800000u, 0x95e761d1u, 0x2e05319au}, + {0x7f800000u, 0x00000000u, 0x7f61b1e6u, 0x3173b8d9u}, + {0x7f7fffffu, 0x00000000u, 0xbf000000u, 0x933de2fcu}, + {0xbf400000u, 0xff7fffffu, 0xff800000u, 0x79f35099u}, + {0xbf800000u, 0x00000000u, 0xff7fffffu, 0x03213676u}, + {0x7f61b1e6u, 0x7fc00001u, 0xff7ffffeu, 0x7ace7351u}, + {0x3f800001u, 0x7fc00001u, 0xff800000u, 0xe3153518u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x110e2cb6u}, + {0x80000000u, 0x80000000u, 0x7f800001u, 0xf81e54ddu}, + {0x80000000u, 0xc0000000u, 0x80000000u, 0x8d118e37u}, + {0x80000000u, 0x7fc00001u, 0x437e8000u, 0x28541424u}, + {0x7f800000u, 0x00000000u, 0x7f800001u, 0xa4672c0cu} +}; +constexpr std::uint32_t Expected[32][10] = { + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0xbf020000u, 0xbf000000u, 0xbf020000u, 0xbf000000u}, + {0xc0400000u, 0x40400000u, 0x40400000u, 0xff7fffffu, 0x40400000u, 0xff7fffffu, 0x3fc00000u, 0x3fc00002u, 0x3fc00002u, 0x3fc00000u}, + {0xff800000u, 0x7f800000u, 0x7f800000u, 0xff7fffffu, 0x7f800000u, 0xff7fffffu, 0x00800000u, 0xff800000u, 0xff800000u, 0x00800000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xff7fffffu, 0x00000000u, 0xff7fffffu, 0x7f800100u, 0x7f800000u, 0x7f800100u, 0x7f800000u}, + {0x7fc00001u, 0xffc00001u, 0x7fc00001u, 0xff7fffffu, 0xffc00001u, 0xff7fffffu, 0x3f000000u, 0x3f000000u, 0x3f000000u, 0x3f000000u}, + {0x7f800000u, 0xff800000u, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x02000002u, 0x00000002u, 0x02000002u, 0x00000002u}, + {0x477e00c0u, 0xc77e00c0u, 0xc77e00c0u, 0xff7fffffu, 0x477e00c0u, 0xff7fffffu, 0x7f7fffffu, 0x7f7fff00u, 0x7f7fffffu, 0x7f7fff00u}, + {0xffc00000u, 0x7fc00000u, 0xffc00000u, 0xff7fffffu, 0xffc00000u, 0xff7fffffu, 0x7f7f00ffu, 0x7f7f00ffu, 0x7f7f00ffu, 0x7f7f00ffu}, + {0xff7fffffu, 0x7f7fffffu, 0x7f7fffffu, 0xff7fffffu, 0x7f7fffffu, 0xff7fffffu, 0x7f61ffe6u, 0x7f6100e6u, 0x7f61ffe6u, 0x7f6100e6u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x7f800000u, 0x7f800000u, 0xff7fffffu, 0xff400000u, 0x00400000u, 0xff400000u, 0x00400000u}, + {0x7f800000u, 0xff800000u, 0xff800000u, 0xff7fffffu, 0x7f800000u, 0xff7fffffu, 0x7fff0000u, 0x7f000000u, 0x7fff0000u, 0x7f000000u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff800000u, 0xff7fffffu, 0xff7fffffu, 0xff7fff00u, 0xff7fffffu, 0xff7fffffu, 0xff7fff00u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x7fc00001u, 0xff7fffffu, 0xff7fffffu, 0x00a27dd4u, 0x00a27dd4u, 0x00a27dd4u, 0x00a27dd4u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xffc00000u, 0xff7fffffu, 0xff7fffffu, 0xb500728cu, 0xb500728cu, 0xb500728cu, 0xb500728cu}, + {0x7f7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x7f7fffffu, 0xff7fffffu, 0x7fffffffu, 0x7f00ffffu, 0x7fffffffu, 0x7f00ffffu}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x7f7fffffu, 0x7f7fffffu, 0xff7fffffu, 0xff800000u, 0x00800000u, 0xff800000u, 0x00800000u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x4da2425fu, 0xff7fffffu, 0xff7fffffu, 0xd4004b77u, 0xd4004b77u, 0xd4004b77u, 0xd4004b77u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0xf14900cau, 0xf14900cau, 0xf14900cau, 0xf14900cau}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xff7fffffu, 0x00000000u, 0xff7fffffu, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x00000000u, 0xff7fffffu, 0xff7fffffu, 0x950061d1u, 0x950061d1u, 0x950061d1u, 0x950061d1u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xff7fffffu, 0x00000000u, 0xff7fffffu, 0x7f61ffe6u, 0x7f6100e6u, 0x7f61ffe6u, 0x7f6100e6u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0xbf0000ffu, 0xbf000000u, 0xbf0000ffu, 0xbf000000u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff3fffffu, 0xff7fffffu, 0xff7fffffu, 0xff800000u, 0xff800100u, 0xff800100u, 0xff800000u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0xff00ffffu, 0xff01ffffu, 0xff01ffffu, 0xff00ffffu}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7ffffeu, 0xff7f00feu, 0xff7ffffeu, 0xff7f00feu}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff800001u, 0xff800000u, 0xff800001u, 0xff800000u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x437e8000u, 0x437e8000u, 0x437e8000u, 0x437e8000u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x7f8000ffu, 0x7f800000u, 0x7f8000ffu, 0x7f800000u} +}; +constexpr std::uint32_t Checked[32] = {0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu}; +constexpr const char* Names[10] = { + "v_mullit_f32 v10, v4, v5, v6", + "v_mullit_f32 v11, -v4, v5, v6", + "v_mullit_f32 v12, v4, -v5, v6", + "v_mullit_f32 v13, v4, |v5|, -v6", + "v_mullit_f32 v14, -|v4|, -|v5|, |v6|", + "v_mullit_f32 v15, |v4|, v5, -|v6|", + "v_cvt_pk_u8_f32 v16, v4, v7, v6", + "v_cvt_pk_u8_f32 v17, -v4, v7, v6", + "v_cvt_pk_u8_f32 v18, |v4|, v7, v6", + "v_cvt_pk_u8_f32 v19, -|v4|, v7, v6", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop3 legacy source modifiers: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 10; ++i) { + if ((Checked[tid] >> i) & 1u) Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop3 legacy source modifiers tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index f91cfbcd5..fc47cec1c 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -1847,6 +1847,8 @@ bool supportsNativeVop3SourceModifiers(RdnaOpcode opcode) { switch (opcode) { case RdnaOpcode::VMulLegacyF32: case RdnaOpcode::VMacLegacyF32: + case RdnaOpcode::VMullitF32: + case RdnaOpcode::VCvtPkU8F32: case RdnaOpcode::VCndmaskB32: case RdnaOpcode::VAddF32: case RdnaOpcode::VSubF32: @@ -1985,7 +1987,7 @@ void checkNativeVop3Modifiers(RdnaOpcode opcode, bool permlane, bool carryInOut, } return; } - if (opcode == RdnaOpcode::VLdexpF16) { + if (opcode == RdnaOpcode::VLdexpF16 || opcode == RdnaOpcode::VCvtPkU8F32) { if ((abs & ~1u) != 0u || opSel != 0u || clamp != 0u || omod != 0u || (neg & ~1u) != 0u) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); } diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index e3be70909..b2e93e342 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -176,6 +176,7 @@ private: bool vCvtPknormF16(const RdnaInstruction& inst, bool signedValue); bool vSatPkU8I16(const RdnaInstruction& inst); bool vMulLegacyF32(const RdnaInstruction& inst, bool accumulate); + bool vMullitF32(const RdnaInstruction& inst); void emitFloat16ClassCompare(const RdnaInstruction& inst, bool cmpx); bool float64Operation(const RdnaInstruction& inst, IrOpcode opcode); void writeF64Result(const RdnaOperand& operand, IrValue& value); diff --git a/core/shader/recompiler/Translation/src/ConvertInstructions.cpp b/core/shader/recompiler/Translation/src/ConvertInstructions.cpp index 8339838d7..91978c218 100644 --- a/core/shader/recompiler/Translation/src/ConvertInstructions.cpp +++ b/core/shader/recompiler/Translation/src/ConvertInstructions.cpp @@ -171,7 +171,14 @@ void TranslationContext::vCvtPknormF32(const RdnaInstruction& inst, bool signedV void TranslationContext::vCvtPkU8F32(const RdnaInstruction& inst) { const IrF32 source(*readOperand(sourceAt(inst, 0u), IrType::F32)); - const IrU32 byteValue = convertF32ToU32Saturated(source, 255.0f, 255.0f, 255u); + const IrU32 truncated = convertF32ToU32Saturated(source, 255.0f, 255.0f, 255u); + IrValue& whole = ir.Emit(IrOpcode::ConvertF32U32, IrType::F32, {&truncated.Value()}); + IrValue& fraction = ir.Emit(IrOpcode::FPSub32, IrType::F32, {&source.Value(), &whole}); + const IrU1 odd(ir.INotEqual(ir.BitwiseAnd(truncated.Value(), ir.Constant(1u)), ir.Constant(0u))); + const IrU1 aboveHalf(ir.Emit(IrOpcode::FPOrdGreaterThan32, IrType::U1, {&fraction, &ir.ConstantF32(0.5f)})); + const IrU1 tie(ir.LogicalAnd(ir.Emit(IrOpcode::FPOrdEqual32, IrType::U1, {&fraction, &ir.ConstantF32(0.5f)}), odd.Value())); + const IrU1 roundUp(ir.LogicalAnd(ir.LogicalOr(aboveHalf.Value(), tie.Value()), ir.ULessThan(truncated.Value(), ir.Constant(255u)))); + const IrU32 byteValue(ir.Select(roundUp.Value(), ir.IAdd(truncated.Value(), ir.Constant(1u)), truncated.Value())); const IrU32 index(ir.BitwiseAnd(readU32(sourceAt(inst, 1u)).Value(), ir.Constant(3u))); const IrU32 shift(ir.ShiftLeftLogical(index.Value(), ir.Constant(3u))); const IrU32 mask(ir.ShiftLeftLogical(ir.Constant(0xffu), shift.Value())); diff --git a/core/shader/recompiler/Translation/src/FloatInstructions.cpp b/core/shader/recompiler/Translation/src/FloatInstructions.cpp index e0c73c88d..9a1f16828 100644 --- a/core/shader/recompiler/Translation/src/FloatInstructions.cpp +++ b/core/shader/recompiler/Translation/src/FloatInstructions.cpp @@ -312,6 +312,25 @@ bool TranslationContext::vMulLegacyF32(const RdnaInstruction& inst, bool accumul return true; } +bool TranslationContext::vMullitF32(const RdnaInstruction& inst) { + const IrU32 lhs = readU32(sourceAt(inst, 0u)); + const IrU32 rhs = readU32(sourceAt(inst, 1u)); + const IrU32 limit = readU32(sourceAt(inst, 2u)); + const auto magnitude = [&](const IrU32& bits) { return IrU32(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffffffu))); }; + const auto isNan = [&](const IrU32& bits) { return IrU1(ir.UGreaterThan(magnitude(bits).Value(), ir.Constant(0x7f800000u))); }; + const auto isZero = [&](const IrU32& bits) { return IrU1(ir.IEqual(magnitude(bits).Value(), ir.Constant(0u))); }; + const IrU1 limitNotPositive(ir.LogicalOr(isZero(limit).Value(), ir.INotEqual(ir.BitwiseAnd(limit.Value(), ir.Constant(0x80000000u)), ir.Constant(0u)))); + const IrU1 rhsNegativeMax(ir.UGreaterThan(rhs.Value(), ir.Constant(0xff7ffffeu))); + IrU1 lowest(ir.LogicalOr(rhsNegativeMax.Value(), ir.LogicalOr(isNan(limit).Value(), limitNotPositive.Value()))); + lowest = IrU1(ir.LogicalOr(lowest.Value(), isNan(rhs).Value())); + const IrU32 product(ir.BitCastU32(ir.Emit(IrOpcode::FPMul32, IrType::F32, {&ir.BitCastF32(lhs.Value()), &ir.BitCastF32(rhs.Value())}))); + IrU32 result(ir.Select(isNan(lhs).Value(), ir.BitwiseOr(lhs.Value(), ir.Constant(0x00400000u)), product.Value())); + result = IrU32(ir.Select(ir.LogicalOr(isZero(lhs).Value(), isZero(rhs).Value()), ir.Constant(0u), result.Value())); + result = IrU32(ir.Select(lowest.Value(), ir.Constant(0xff7fffffu), result.Value())); + writeOperand(inst.destination, &result.Value()); + return true; +} + void TranslationContext::emitFloat16ClassCompare(const RdnaInstruction& inst, bool cmpx) { const IrU32 bits = readF16Bits(sourceAt(inst, 0u)); const IrU32 mask = readU32(sourceAt(inst, 1u)); diff --git a/core/shader/recompiler/Translation/src/VectorInstructions.cpp b/core/shader/recompiler/Translation/src/VectorInstructions.cpp index 0b93cffa2..cf769044f 100644 --- a/core/shader/recompiler/Translation/src/VectorInstructions.cpp +++ b/core/shader/recompiler/Translation/src/VectorInstructions.cpp @@ -542,6 +542,8 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { return vMulLegacyF32(inst, false); case RdnaOpcode::VMacLegacyF32: return vMulLegacyF32(inst, true); + case RdnaOpcode::VMullitF32: + return vMullitF32(inst); case RdnaOpcode::VCmpClassF32: emitFloatClassCompare(inst, false); return true; @@ -928,7 +930,6 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { case RdnaOpcode::VSubrevF32: return floatBinary(inst, IrOpcode::FPSub32, true); case RdnaOpcode::VMulF32: - case RdnaOpcode::VMullitF32: return floatBinary(inst, IrOpcode::FPMul32, false); case RdnaOpcode::VMinF32: return floatBinary(inst, IrOpcode::FPMin32, false); From 9e6362dc7136b89d73c196f63eec48dabb247326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 23:09:21 +0200 Subject: [PATCH 107/164] docs: record the MODE assumption of v_mullit_f32 in TechnicalDebt Co-Authored-By: Claude --- docs/dev/TechnicalDebt.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index c7520ce93..850207226 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -80,6 +80,7 @@ Throughout the project, every function at every stage either **does exactly what - The shader recompiler [ignores `s_setreg_b32`](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp): writes to MODE (float rounding and denormal controls) and the other hardware registers have no effect, and `s_getreg_b32` reads of the MODE round fields report round to nearest even even after `s_setreg_b32` wrote another mode. The initial round mode from the shader's program registers is not read either. - [Shader recompilation](../../core/shader/recompiler/Recompiler.cpp) currently occurs right before it was transferred to Vulkan with caching, but should be moved to the [relinker](../../core/relinker/main.cpp) stage. For this purpose, [shader/recompiler](../../core/shader/recompiler) was written completely independently from [libs/prx](../../core/libs/prx). - [v_fma_f64](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp) is fused exactly only for normal inputs: with a subnormal input it is a separate multiply and add, which rounds twice. +- [v_mullit_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follows the hardware measured with the MODE register at IEEE=1 and denormals kept (a signaling NaN src0 is quieted, a positive denormal src2 counts as positive): the recompiler does not read MODE, so other IEEE/denormal settings are not modelled. - The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability. - [libSceJpegEnc](../../core/libs/prx/libSceJpegEnc/Export.cpp) encodes 4:2:2 sampling requests with 4:2:0 chroma subsampling, and grayscale requests as a 3-component JPEG with neutral chroma instead of a single-component one: the [JPEG encoder](../../core/Decoder/Jpeg/src/Jpeg.cpp) (stb) only produces 3-component 4:2:0 and 4:4:4 images. - [v_rcp, v_rsq, v_sqrt, v_log, v_exp, v_sin and v_cos](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (f32 and f16) use the driver's approximation for finite results, not the console's: the low bits can differ. Only NaN, invalid, zero and infinite results and the exact sin/cos zeros are chosen on the bits. v_sin_f32/v_cos_f32 with inputs beyond ±256 cycles were not compared with the console. From f457d87bbd2a472c2c786fe0d13f06257bc743ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 13:22:32 +0200 Subject: [PATCH 108/164] feat(shader): accept clamp on v_cube*_f32 and match their zero and subnormal results Co-Authored-By: Claude --- .../tests/execution/Vop3CubeClamp.cpp | 207 ++++++++++++++++++ .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 7 +- .../Translation/src/FloatInstructions.cpp | 45 ++-- 3 files changed, 241 insertions(+), 18 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Vop3CubeClamp.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3CubeClamp.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3CubeClamp.cpp new file mode 100644 index 000000000..07e25ea2b --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3CubeClamp.cpp @@ -0,0 +1,207 @@ +#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 +#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, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd544000a, 0x041a0b04, 0xd545000b, + 0x041a0b04, 0xd546000c, 0x041a0b04, 0xd547000d, 0x041a0b04, 0xd544800e, 0x041a0b04, 0xd545800f, + 0x041a0b04, 0xd5468010, 0x041a0b04, 0xd5478011, 0x041a0b04, 0xd5440212, 0x241a0b04, 0xd5450113, + 0xc41a0b04, 0xd5460414, 0x641a0b04, 0xd5470115, 0x841a0b04, 0xd5448016, 0xe41a0b04, 0xd5458417, + 0x441a0b04, 0xd5468118, 0x841a0b04, 0xd5478219, 0x241a0b04, 0xe0781000, 0x80010a03, 0xe0781010, + 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x3f800000u, 0xbf800000u, 0x3f800000u, 0x00000000u}, + {0x7f800001u, 0x3f000000u, 0x3f000000u, 0x00000000u}, + {0x40000000u, 0x3f000000u, 0xff812345u, 0x00000000u}, + {0x80000007u, 0x7fc00000u, 0x7fc00000u, 0x00000000u}, + {0x00000007u, 0xffc00000u, 0x7f800001u, 0x00000000u}, + {0xff800000u, 0x80000007u, 0x00000007u, 0x00000000u}, + {0x7fc00000u, 0x00000007u, 0xff800000u, 0x00000000u}, + {0xbf000000u, 0x00000000u, 0x80000007u, 0x00000000u}, + {0x00000007u, 0x00000007u, 0x7f800001u, 0x00000000u}, + {0x00000007u, 0x7f800000u, 0x80000007u, 0x00000000u}, + {0x00000007u, 0x00000007u, 0x80000007u, 0x00000000u}, + {0xff800000u, 0xff800000u, 0x40000000u, 0x00000000u}, + {0x40000000u, 0xc0200000u, 0xc0200000u, 0x00000000u}, + {0x7fc00000u, 0xff800000u, 0x80000000u, 0x00000000u}, + {0xc0200000u, 0x7f800000u, 0x7f800000u, 0x00000000u}, + {0xbf000000u, 0xbf000000u, 0x3f000000u, 0x00000000u}, + {0x7fc00000u, 0xff800000u, 0xc0200000u, 0x00000000u}, + {0x00000000u, 0xff800000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xff800000u, 0xbf000000u, 0x00000000u}, + {0xff800000u, 0x00000000u, 0xff800000u, 0x00000000u}, + {0xff800000u, 0x7f800000u, 0x00000000u, 0x00000000u}, + {0xff800000u, 0x7f800000u, 0xbf000000u, 0x00000000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0x00000000u}, + {0xff800000u, 0xff800000u, 0xbf000000u, 0x00000000u}, + {0xff800000u, 0xbf000000u, 0xbf000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7fc00000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xffc00000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7f800001u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xff812345u, 0x00000000u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x40800000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40800000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x40800000u, 0x3f800000u, 0x3f800000u, 0x40000000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x40800000u, 0xbf800000u, 0xbf800000u, 0xc0000000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u}, + {0x00000000u, 0xbf000000u, 0xbf000000u, 0x7f800001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f000000u, 0x3f000000u, 0x7f800001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x7f812345u, 0xbf000000u, 0x40800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0xff812345u, 0x3f000000u, 0x40800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xffc00000u, 0xffc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc00000u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xff800001u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7f800001u, 0xffc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x00000007u, 0x00000007u, 0xff800000u, 0x3f800000u, 0x00000007u, 0x00000007u, 0x00000000u, 0x00000000u, 0x00000007u, 0x80000007u, 0x7f800000u, 0x00000000u, 0x00000007u, 0x00000007u, 0x3f800000u}, + {0x00000000u, 0x7f800000u, 0x80000007u, 0x7fc00000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff800000u, 0x00000007u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x80000007u, 0x80000000u, 0xbf800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000007u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000007u, 0x00000000u, 0x3f800000u}, + {0x40000000u, 0x00000007u, 0x7f800001u, 0x00000000u, 0x3f800000u, 0x00000007u, 0x00000000u, 0x00000000u, 0x40000000u, 0x00000007u, 0x7f800001u, 0x00000000u, 0x3f800000u, 0x00000007u, 0x00000000u, 0x00000000u}, + {0x40000000u, 0x00000007u, 0x80000007u, 0x7f800000u, 0x3f800000u, 0x00000007u, 0x00000000u, 0x3f800000u, 0x40000000u, 0x00000007u, 0x80000007u, 0x7f800000u, 0x3f800000u, 0x00000007u, 0x00000007u, 0x3f800000u}, + {0x40800000u, 0x00000007u, 0x80000007u, 0x00000000u, 0x3f800000u, 0x00000007u, 0x00000000u, 0x00000000u, 0x40800000u, 0x00000007u, 0x00000007u, 0x00000000u, 0x3f800000u, 0x00000007u, 0x00000000u, 0x00000000u}, + {0x40400000u, 0xff800000u, 0xc0000000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0x7f800000u, 0x40000000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u}, + {0x40a00000u, 0xc0000000u, 0x40200000u, 0xc0a00000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x40a00000u, 0x40000000u, 0xc0200000u, 0x40a00000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7f800000u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x80000000u, 0xff800000u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0x40800000u, 0xc0200000u, 0xff800000u, 0x7f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x40800000u, 0xc0200000u, 0x7f800000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x40800000u, 0xbf000000u, 0x3f000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f000000u, 0x3f800000u, 0x40800000u, 0xbf000000u, 0xbf000000u, 0xbf800000u, 0x3f800000u, 0x00000000u, 0x3f000000u, 0x3f800000u}, + {0x00000000u, 0x40200000u, 0x7f800000u, 0x7fc00000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0xc0200000u, 0xff800000u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0x40400000u, 0x00000000u, 0x80000000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x00000000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x40400000u, 0x00000000u, 0x3f000000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x3f000000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x40a00000u, 0x7f800000u, 0x80000000u, 0xff800000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x40a00000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x40000000u, 0xff800000u, 0x00000000u, 0x7f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x40000000u, 0x7f800000u, 0x80000000u, 0x7f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x40000000u, 0xff800000u, 0xbf000000u, 0x7f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x40000000u, 0x7f800000u, 0xbf000000u, 0x7f800000u, 0x3f800000u, 0x00000000u, 0x3f000000u, 0x3f800000u}, + {0x40a00000u, 0x7f800000u, 0x7f800000u, 0xff800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x40a00000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0x40400000u, 0xff800000u, 0x3f000000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x40000000u, 0x7f800000u, 0x3f000000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x3f800000u, 0xbf000000u, 0x3f000000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x00000000u, 0xbf000000u, 0xbf000000u, 0x7f800000u, 0x00000000u, 0x3f000000u, 0x3f000000u, 0x3f800000u}, + {0x40800000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x40800000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x40000000u, 0x00000000u, 0x7fc00000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x7fc00000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x40000000u, 0x00000000u, 0xffc00000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x7fc00000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x40000000u, 0x00000000u, 0x7f800001u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x7f800001u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x40000000u, 0x00000000u, 0xff812345u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x7f812345u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u} +}; +constexpr const char* Names[16] = { + "v_cubeid_f32 v10, v4, v5, v6", + "v_cubesc_f32 v11, v4, v5, v6", + "v_cubetc_f32 v12, v4, v5, v6", + "v_cubema_f32 v13, v4, v5, v6", + "v_cubeid_f32 v14, v4, v5, v6 clamp", + "v_cubesc_f32 v15, v4, v5, v6 clamp", + "v_cubetc_f32 v16, v4, v5, v6 clamp", + "v_cubema_f32 v17, v4, v5, v6 clamp", + "v_cubeid_f32 v18, -v4, |v5|, v6", + "v_cubesc_f32 v19, |v4|, -v5, -v6", + "v_cubetc_f32 v20, -v4, -v5, |v6|", + "v_cubema_f32 v21, |v4|, v5, -v6", + "v_cubeid_f32 v22, -v4, -v5, -v6 clamp", + "v_cubesc_f32 v23, v4, -v5, |v6| clamp", + "v_cubetc_f32 v24, |v4|, v5, -v6 clamp", + "v_cubema_f32 v25, -v4, |v5|, v6 clamp", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop3 cube clamp: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +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}, 0u, {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 std::span code(Code); + const auto result = Compile(device, code); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void CheckOmodRefused(AgcDriver::VulkanDevice& device) { + for (const std::uint32_t opcode : {0x144u, 0x145u, 0x146u, 0x147u}) { + alignas(256) const std::array code{0xd400000au | (opcode << 16u), 0x0c1a0b04u, 0xbf810000u}; + std::string refusal; + try { + static_cast(Compile(device, code)); + } catch (const std::exception& error) { + refusal = error.what(); + } + Require(refusal.find("VOP3 source modifiers are not implemented") != std::string::npos, "VOP3 opcode " + Hex(opcode) + " with omod was not refused"); + } +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + CheckOmodRefused(*device); + std::puts("vop3 cube clamp tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index f91cfbcd5..9d4658bee 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -757,6 +757,11 @@ bool usesSignedSaturateClamp(RdnaOpcode opcode) { return opcode == RdnaOpcode::VAddNcI32 || opcode == RdnaOpcode::VSubNcI32; } +bool isCubeOpcode(RdnaOpcode opcode) { + return opcode == RdnaOpcode::VCubeidF32 || opcode == RdnaOpcode::VCubescF32 || opcode == RdnaOpcode::VCubetcF32 || + opcode == RdnaOpcode::VCubemaF32; +} + bool isNativeVop3B16BinaryOpcode(RdnaOpcode opcode) { switch (opcode) { case RdnaOpcode::VAddNcU16: @@ -1932,7 +1937,7 @@ bool isF32DivisionStepOpcode(RdnaOpcode opcode) { bool supportsNativeVop3Clamp(RdnaOpcode opcode) { return supportsNativeVop3ResultModifiers(opcode) || usesInexactClampControl(opcode) || usesSignedSaturateClamp(opcode) || - isF32DivisionStepOpcode(opcode); + isF32DivisionStepOpcode(opcode) || isCubeOpcode(opcode); } void checkNativeVop3Modifiers(RdnaOpcode opcode, bool permlane, bool carryInOut, bool scalarDst, std::uint32_t abs, std::uint32_t opSel, std::uint32_t clamp, std::uint32_t omod, std::uint32_t neg) { diff --git a/core/shader/recompiler/Translation/src/FloatInstructions.cpp b/core/shader/recompiler/Translation/src/FloatInstructions.cpp index e0c73c88d..32b749646 100644 --- a/core/shader/recompiler/Translation/src/FloatInstructions.cpp +++ b/core/shader/recompiler/Translation/src/FloatInstructions.cpp @@ -568,22 +568,34 @@ bool TranslationContext::vCubemaF32(const RdnaInstruction& inst) { } bool TranslationContext::floatCube(const RdnaInstruction& inst, std::uint32_t resultKind) { - const IrF32 x = readMixF32(sourceAt(inst, 0u)); - const IrF32 y = readMixF32(sourceAt(inst, 1u)); - const IrF32 z = readMixF32(sourceAt(inst, 2u)); - const IrF32 nx(ir.Emit(IrOpcode::FPNeg32, IrType::F32, {&x.Value()})); - const IrF32 ny(ir.Emit(IrOpcode::FPNeg32, IrType::F32, {&y.Value()})); - const IrF32 nz(ir.Emit(IrOpcode::FPNeg32, IrType::F32, {&z.Value()})); - const IrF32 ax(ir.Emit(IrOpcode::FPAbs32, IrType::F32, {&x.Value()})); - const IrF32 ay(ir.Emit(IrOpcode::FPAbs32, IrType::F32, {&y.Value()})); - const IrF32 az(ir.Emit(IrOpcode::FPAbs32, IrType::F32, {&z.Value()})); + const IrF32 x(ir.BitCastF32(readU32(sourceAt(inst, 0u)).Value())); + const IrF32 y(ir.BitCastF32(readU32(sourceAt(inst, 1u)).Value())); + const IrF32 z(ir.BitCastF32(readU32(sourceAt(inst, 2u)).Value())); + const IrF32 nx(ir.BitCastF32(ir.BitwiseXor(ir.BitCastU32(x.Value()), ir.Constant(0x80000000u)))); + const IrF32 ny(ir.BitCastF32(ir.BitwiseXor(ir.BitCastU32(y.Value()), ir.Constant(0x80000000u)))); + const IrF32 nz(ir.BitCastF32(ir.BitwiseXor(ir.BitCastU32(z.Value()), ir.Constant(0x80000000u)))); + const auto exponentZero = [&](IrF32 value) { + return IrU1(ir.IEqual(ir.BitwiseAnd(ir.BitCastU32(value.Value()), ir.Constant(0x7f800000u)), ir.Constant(0u))); + }; + const auto flushed = [&](IrF32 value) { + return IrF32(ir.BitCastF32(ir.Select(exponentZero(value).Value(), ir.Constant(0u), ir.BitCastU32(value.Value())))); + }; + const auto magnitude = [&](IrF32 value) { + return IrF32(ir.BitCastF32(ir.BitwiseAnd(ir.BitCastU32(value.Value()), ir.Constant(0x7fffffffu)))); + }; + const IrF32 fx = flushed(x); + const IrF32 fy = flushed(y); + const IrF32 fz = flushed(z); + const IrF32 ax = magnitude(fx); + const IrF32 ay = magnitude(fy); + const IrF32 az = magnitude(fz); const IrU1 zDominatesX(ir.Emit(IrOpcode::FPOrdGreaterThanEqual32, IrType::U1, {&az.Value(), &ax.Value()})); const IrU1 zDominatesY(ir.Emit(IrOpcode::FPOrdGreaterThanEqual32, IrType::U1, {&az.Value(), &ay.Value()})); const IrU1 zFace(ir.LogicalAnd(zDominatesX.Value(), zDominatesY.Value())); const IrU1 yFace(ir.Emit(IrOpcode::FPOrdGreaterThanEqual32, IrType::U1, {&ay.Value(), &ax.Value()})); - const IrU1 xNegative(ir.Emit(IrOpcode::FPOrdLessThan32, IrType::U1, {&x.Value(), &ir.ConstantF32(0.0f)})); - const IrU1 yNegative(ir.Emit(IrOpcode::FPOrdLessThan32, IrType::U1, {&y.Value(), &ir.ConstantF32(0.0f)})); - const IrU1 zNegative(ir.Emit(IrOpcode::FPOrdLessThan32, IrType::U1, {&z.Value(), &ir.ConstantF32(0.0f)})); + const IrU1 xNegative(ir.Emit(IrOpcode::FPOrdLessThan32, IrType::U1, {&fx.Value(), &ir.ConstantF32(0.0f)})); + const IrU1 yNegative(ir.Emit(IrOpcode::FPOrdLessThan32, IrType::U1, {&fy.Value(), &ir.ConstantF32(0.0f)})); + const IrU1 zNegative(ir.Emit(IrOpcode::FPOrdLessThan32, IrType::U1, {&fz.Value(), &ir.ConstantF32(0.0f)})); const auto selectFace = [&](IrF32 xValue, IrF32 yValue, IrF32 zValue) { return selectF32(zFace, zValue, selectF32(yFace, yValue, xValue)); }; @@ -606,11 +618,10 @@ bool TranslationContext::floatCube(const RdnaInstruction& inst, std::uint32_t re result = selectFace(ny, selectF32(yNegative, nz, z), ny); break; case 3u: { - const IrF32 two(ir.ConstantF32(2.0f)); - const IrF32 xTwo(ir.Emit(IrOpcode::FPMul32, IrType::F32, {&x.Value(), &two.Value()})); - const IrF32 yTwo(ir.Emit(IrOpcode::FPMul32, IrType::F32, {&y.Value(), &two.Value()})); - const IrF32 zTwo(ir.Emit(IrOpcode::FPMul32, IrType::F32, {&z.Value(), &two.Value()})); - result = selectFace(xTwo, yTwo, zTwo); + const IrF32 major = selectFace(x, y, z); + const IrF32 twice(ir.Emit(IrOpcode::FPMul32, IrType::F32, {&major.Value(), &ir.ConstantF32(2.0f)})); + const IrU1 nan(ir.UGreaterThan(ir.BitCastU32(magnitude(major).Value()), ir.Constant(0x7f800000u))); + result = selectF32(nan, major, selectF32(exponentZero(major), IrF32(ir.ConstantF32(0.0f)), twice)); break; } default: From 4d05623a425d92f9a4539fb9a780b10f6072e84b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 11:11:04 +0200 Subject: [PATCH 109/164] fix(tools): count s_endpgm_saved and s_endpgm_ordered_ps_done as decoded shader instructions Co-Authored-By: Claude --- tools/progress.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/progress.py b/tools/progress.py index 9bcad2fe7..b78a73e89 100644 --- a/tools/progress.py +++ b/tools/progress.py @@ -26,6 +26,7 @@ OPCODE_ALIASES = { "VMadMixhiF16": "V_FMA_MIXHI_F16", } OPCODE_VARIANTS = { + "SEndpgm": ("S_ENDPGM_SAVED", "S_ENDPGM_ORDERED_PS_DONE"), "VFmaF32": ("V_FMA_MIX_F32",), "SAddI32": ("S_ADDK_I32",), "SCmpEqI32": ("S_CMPK_EQ_I32",), From 6a20036154e6f99538e63f60db1173b2d19608c9 Mon Sep 17 00:00:00 2001 From: DotDebian Date: Mon, 5 Oct 2026 11:12:56 +0200 Subject: [PATCH 110/164] feat(libs): implement NpCppWebApi Common::String constructor, destructor and c_str The constructor was a silent stub that left the guest object uninitialised and c_str threw. The constructor now writes the 0x20-byte object, c_str returns the buffer or an empty string, and the destructor throws for a non-null buffer, whose release needs the LibContext allocator. Co-Authored-By: Claude Opus 5.5 --- .../libs/prx/libSceNpCppWebApi/CMakeLists.txt | 21 ++++ core/libs/prx/libSceNpCppWebApi/Export.cpp | 27 +++-- core/libs/tests/GuestNpCppWebApiString.cpp | 112 ++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 152 insertions(+), 9 deletions(-) create mode 100644 core/libs/prx/libSceNpCppWebApi/CMakeLists.txt create mode 100644 core/libs/tests/GuestNpCppWebApiString.cpp diff --git a/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt b/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt new file mode 100644 index 000000000..2519e8cc8 --- /dev/null +++ b/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt @@ -0,0 +1,21 @@ +add_library(libSceNpCppWebApi SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceNpCppWebApi PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceNpCppWebApi) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_np_cpp_web_api_string_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpCppWebApiString.cpp) + target_include_directories(guest_np_cpp_web_api_string_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_cpp_web_api_string_tests PRIVATE libSceNpCppWebApi libc) + configure_windows_unwind(guest_np_cpp_web_api_string_tests) + add_test(NAME guest_np_cpp_web_api_string COMMAND guest_np_cpp_web_api_string_tests) + if(WIN32) + set_tests_properties(guest_np_cpp_web_api_string PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpCppWebApi/Export.cpp b/core/libs/prx/libSceNpCppWebApi/Export.cpp index 50c5a6ad9..ac292124c 100644 --- a/core/libs/prx/libSceNpCppWebApi/Export.cpp +++ b/core/libs/prx/libSceNpCppWebApi/Export.cpp @@ -447,14 +447,24 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12Intrusive return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(void* self) { - (void)self; - return 0; +struct CommonString { + std::int32_t referenceCount; + char* buffer; + std::uint64_t bufferSize; + void* libContext; +}; +static_assert(sizeof(CommonString) == 0x20 && offsetof(CommonString, buffer) == 0x08); +static_assert(offsetof(CommonString, bufferSize) == 0x10 && offsetof(CommonString, libContext) == 0x18); + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(CommonString* self, void* libContext) { + self->referenceCount = 0; + self->buffer = nullptr; + self->bufferSize = 0; + self->libContext = libContext; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringD1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringD1Ev(CommonString* self) { + if (self->buffer != nullptr) NotImplemented_nid_no_patch("sce::Np::CppWebApi::Common::String buffer release"); } int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_12Leaderboards2V14UserEEEE8pushBackERKS8_() { @@ -727,9 +737,8 @@ int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv() { return 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6String5c_strEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +const char* APS5_VABI _ZNK3sce2Np9CppWebApi6Common6String5c_strEv(const CommonString* self) { + return self->buffer != nullptr ? self->buffer : ""; } int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEEdeEv() { diff --git a/core/libs/tests/GuestNpCppWebApiString.cpp b/core/libs/tests/GuestNpCppWebApiString.cpp new file mode 100644 index 000000000..c54ee17c3 --- /dev/null +++ b/core/libs/tests/GuestNpCppWebApiString.cpp @@ -0,0 +1,112 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +extern "C" { +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(void* self, void* libContext); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringD1Ev(void* self); +const char* APS5_VABI _ZNK3sce2Np9CppWebApi6Common6String5c_strEv(const void* self); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr std::size_t kStringSize = 0x20; +constexpr std::size_t kGuardSize = 0x10; +constexpr std::size_t kReferenceCountOffset = 0x00; +constexpr std::size_t kBufferOffset = 0x08; +constexpr std::size_t kBufferSizeOffset = 0x10; +constexpr std::size_t kLibContextOffset = 0x18; +constexpr unsigned char kFill = 0xA5; + +struct Storage { + alignas(8) unsigned char bytes[kStringSize + kGuardSize]; +}; + +template +TValue Load(const Storage& storage, std::size_t offset) { + TValue value; + std::memcpy(&value, storage.bytes + offset, sizeof(value)); + return value; +} + +template +void Store(Storage& storage, std::size_t offset, TValue value) { + std::memcpy(storage.bytes + offset, &value, sizeof(value)); +} + +bool GuardIntact(const Storage& storage) { + for (std::size_t i = kStringSize; i < sizeof(storage.bytes); i++) { + if (storage.bytes[i] != kFill) return false; + } + return true; +} + +bool DestructorThrows(Storage& storage) { + try { + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(storage.bytes); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +void TestConstructedStringIsEmpty() { + std::uint64_t libContext = 1; + Storage storage; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(storage.bytes, &libContext); + + Require(Load(storage, kReferenceCountOffset) == 0); + Require(Load(storage, kBufferOffset) == nullptr); + Require(Load(storage, kBufferSizeOffset) == 0); + Require(Load(storage, kLibContextOffset) == &libContext); + Require(GuardIntact(storage)); + + const char* text = _ZNK3sce2Np9CppWebApi6Common6String5c_strEv(storage.bytes); + Require(text != nullptr && text[0] == '\0'); + + Storage before = storage; + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(storage.bytes); + Require(std::memcmp(before.bytes, storage.bytes, sizeof(storage.bytes)) == 0); +} + +void TestNullLibContextIsStored() { + Storage storage; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(storage.bytes, nullptr); + + Require(Load(storage, kLibContextOffset) == nullptr); + Require(Load(storage, kBufferOffset) == nullptr); + Require(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(storage.bytes)[0] == '\0'); + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(storage.bytes); +} + +void TestBufferIsReturnedAndNotDroppedSilently() { + std::uint64_t libContext = 1; + char text[] = "ranking"; + Storage storage; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(storage.bytes, &libContext); + Store(storage, kBufferOffset, text); + Store(storage, kBufferSizeOffset, sizeof(text)); + + Require(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(storage.bytes) == text); + Require(DestructorThrows(storage)); + Require(std::strcmp(text, "ranking") == 0); +} + +} + +int main() { + TestConstructedStringIsEmpty(); + TestNullLibContextIsStored(); + TestBufferIsReturnedAndNotDroppedSilently(); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index c7520ce93..159ccb144 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -66,6 +66,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceNetGetMemoryPoolStats](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature - [sceNpSessionSignalingGetMemoryInfo](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature - [sceNpSessionSignalingGetConnectionStatistics](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature +- [sce::Np::CppWebApi::Common::String](../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x20-byte layout (32-bit reference count, buffer at 0x08, buffer size at 0x10, `LibContext*` at 0x18) comes from the module bundled with one title (PPSA21564); other SDK versions are not checked. The destructor throws for a non-null buffer: releasing it needs the `LibContext` allocator, which is not implemented - [sceNpTrophy2UnregisterUnlockCallback](../../core/libs/prx/libSceNpTrophy2/src/Context.cpp) (libSceNpTrophy2) - unknown signature - [sceSystemServiceDisableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature - [sceSystemServiceReenableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature From f2fb7852bbdb4069d577c0ec5f7d9847bc0d29e5 Mon Sep 17 00:00:00 2001 From: DotDebian Date: Mon, 5 Oct 2026 11:17:33 +0200 Subject: [PATCH 111/164] feat(libs): implement NpCppWebApi IntrusivePtr construction, copy, release and access IntrusivePtr is three pointers: object, deleter, LibContext. The default constructors zero them, the copy constructors copy them and take a reference on the object, operator-> and operator* return the object, and the destructors drop a reference and hand the object to the deleter when it was the last one. The last-reference release of a pointer without a deleter needs the object's destructor and the LibContext memory pool and still throws. Co-Authored-By: Claude Opus 5.5 --- core/libs/CMakeLists.txt | 10 + core/libs/prx/libSceNpCppWebApi/Export.cpp | 164 +++++------ .../tests/GuestNpCppWebApiIntrusivePtr.cpp | 256 ++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 350 insertions(+), 81 deletions(-) create mode 100644 core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 53ec753d7..510110335 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -170,6 +170,16 @@ if(BUILD_TESTING) add_test(NAME host_thread_local COMMAND host_thread_local_tests) set_tests_properties(host_thread_local PROPERTIES TIMEOUT 15) + add_test_executable(guest_np_cpp_web_api_intrusive_ptr_tests tests/GuestNpCppWebApiIntrusivePtr.cpp) + target_include_directories(guest_np_cpp_web_api_intrusive_ptr_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_np_cpp_web_api_intrusive_ptr_tests PRIVATE libSceNpCppWebApi libc) + configure_windows_unwind(guest_np_cpp_web_api_intrusive_ptr_tests) + add_test(NAME guest_np_cpp_web_api_intrusive_ptr COMMAND guest_np_cpp_web_api_intrusive_ptr_tests) + if(WIN32) + set_tests_properties(guest_np_cpp_web_api_intrusive_ptr PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(savedata_core_write_replacement_tests tests/SaveDataWriteReplacement.cpp) target_include_directories(savedata_core_write_replacement_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(savedata_core_write_replacement_tests PRIVATE libSceSaveData) diff --git a/core/libs/prx/libSceNpCppWebApi/Export.cpp b/core/libs/prx/libSceNpCppWebApi/Export.cpp index 50c5a6ad9..b34d33167 100644 --- a/core/libs/prx/libSceNpCppWebApi/Export.cpp +++ b/core/libs/prx/libSceNpCppWebApi/Export.cpp @@ -1,3 +1,4 @@ +#include #include #include #include "SceTypes.hpp" @@ -267,89 +268,100 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12 return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1ERS7_(void* self) { - (void)self; - return 0; +struct IntrusivePtr { + void* object; + void (APS5_VABI* deleter)(void*); + void* libContext; +}; +static_assert(sizeof(IntrusivePtr) == 0x18); + +static constexpr std::size_t ReferenceCountOffset = 0; +static constexpr std::size_t VectorReferenceCountOffset = 0x30; + +static std::atomic_ref ReferenceCount(void* object, const std::size_t offset) { + return std::atomic_ref(*reinterpret_cast(static_cast(object) + offset)); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1Ev(void* self) { - (void)self; - return 0; +static void CopyIntrusivePtr(IntrusivePtr* self, const IntrusivePtr* source, const std::size_t referenceCountOffset) { + *self = *source; + if (self->object) ReferenceCount(self->object, referenceCountOffset).fetch_add(1); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEED1Ev(void* self) { - (void)self; - return 0; +static void ReleaseIntrusivePtr(IntrusivePtr* self, const std::size_t referenceCountOffset, const char* function) { + if (!self->object) return; + if (ReferenceCount(self->object, referenceCountOffset).fetch_sub(1) != 1) return; + if (!self->deleter) NotImplemented_nid_no_patch(function); + self->deleter(self->object); + self->object = nullptr; + self->deleter = nullptr; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEC1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, ReferenceCountOffset); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEED1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEC1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, ReferenceCountOffset); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEED1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEC1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEED1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEC1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEED1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEC1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEED1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEC1Ev(IntrusivePtr* self) { + *self = {}; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEED1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, VectorReferenceCountOffset, __func__); } int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEED2Ev(void* self) { @@ -663,54 +675,44 @@ int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_1 return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEptEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEptEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEptEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEptEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEptEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V15EntryEEptEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V15EntryEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEptEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6BinaryEEptEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6BinaryEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEEdeEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEEdeEv(const IntrusivePtr* self) { + return self->object; } int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEE18getResponseHeadersERSB_() { diff --git a/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp b/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp new file mode 100644 index 000000000..8b5c7b585 --- /dev/null +++ b/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp @@ -0,0 +1,256 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +using Deleter = void (APS5_VABI*)(void*); + +struct IntrusivePtr { + void* object; + Deleter deleter; + void* libContext; +}; + +using Construct = void (APS5_VABI*)(IntrusivePtr*); +using Copy = void (APS5_VABI*)(IntrusivePtr*, const IntrusivePtr*); +using Destroy = void (APS5_VABI*)(IntrusivePtr*); +using Access = void* (APS5_VABI*)(const IntrusivePtr*); + +extern "C" { +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEC1Ev(IntrusivePtr*); + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1ERS7_(IntrusivePtr*, const IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_(IntrusivePtr*, const IntrusivePtr*); + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEED1Ev(IntrusivePtr*); + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V15EntryEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6BinaryEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEEdeEv(const IntrusivePtr*); +} + +static void Require(bool value) { if (!value) std::abort(); } + +static constexpr std::size_t ObjectSize = 0x40; +static constexpr std::size_t VectorReferenceCountOffset = 0x30; + +struct DestroyCase { + Destroy destroy; + std::size_t referenceCountOffset; +}; + +static void* deletedObject = nullptr; +static int deleterCalls = 0; + +static void APS5_VABI RecordDelete(void* object) { + deletedObject = object; + ++deleterCalls; +} + +static std::int32_t ReadCount(const unsigned char* object, const std::size_t offset) { + std::int32_t value; + std::memcpy(&value, object + offset, sizeof(value)); + return value; +} + +static void WriteCount(unsigned char* object, const std::size_t offset, const std::int32_t value) { + std::memcpy(object + offset, &value, sizeof(value)); +} + +static bool OnlyCountChanged(const unsigned char* object, const std::size_t offset) { + for (std::size_t index = 0; index < ObjectSize; ++index) { + if (index >= offset && index < offset + sizeof(std::int32_t)) continue; + if (object[index] != 0x5a) return false; + } + return true; +} + +static void TestConstruct(const Construct construct) { + IntrusivePtr pointer; + std::memset(&pointer, 0xa5, sizeof(pointer)); + construct(&pointer); + Require(pointer.object == nullptr); + Require(pointer.deleter == nullptr); + Require(pointer.libContext == nullptr); +} + +static void TestAccess(const Access access) { + int object = 0; + int context = 0; + const IntrusivePtr empty{nullptr, RecordDelete, &context}; + Require(access(&empty) == nullptr); + const IntrusivePtr filled{&object, nullptr, nullptr}; + Require(access(&filled) == &object); +} + +static void TestCopy(const Copy copy) { + alignas(8) unsigned char object[ObjectSize]; + std::memset(object, 0x5a, sizeof(object)); + WriteCount(object, 0, 1); + int context = 0; + + const IntrusivePtr source{object, RecordDelete, &context}; + IntrusivePtr target; + std::memset(&target, 0xa5, sizeof(target)); + copy(&target, &source); + Require(target.object == object); + Require(target.deleter == RecordDelete); + Require(target.libContext == &context); + Require(source.object == object); + Require(ReadCount(object, 0) == 2); + Require(OnlyCountChanged(object, 0)); + + const IntrusivePtr empty{nullptr, RecordDelete, &context}; + std::memset(&target, 0xa5, sizeof(target)); + copy(&target, &empty); + Require(target.object == nullptr); + Require(target.deleter == RecordDelete); + Require(target.libContext == &context); +} + +static void TestDestroy(const DestroyCase& testCase) { + alignas(8) unsigned char object[ObjectSize]; + std::memset(object, 0x5a, sizeof(object)); + int context = 0; + const std::size_t offset = testCase.referenceCountOffset; + + deleterCalls = 0; + deletedObject = nullptr; + + IntrusivePtr empty{nullptr, RecordDelete, &context}; + testCase.destroy(&empty); + Require(empty.object == nullptr); + Require(empty.deleter == RecordDelete); + Require(empty.libContext == &context); + Require(deleterCalls == 0); + + WriteCount(object, offset, 2); + IntrusivePtr shared{object, RecordDelete, &context}; + testCase.destroy(&shared); + Require(ReadCount(object, offset) == 1); + Require(shared.object == object); + Require(shared.deleter == RecordDelete); + Require(shared.libContext == &context); + Require(deleterCalls == 0); + + IntrusivePtr last{object, RecordDelete, &context}; + testCase.destroy(&last); + Require(ReadCount(object, offset) == 0); + Require(OnlyCountChanged(object, offset)); + Require(deleterCalls == 1); + Require(deletedObject == object); + Require(last.object == nullptr); + Require(last.deleter == nullptr); + Require(last.libContext == &context); + + WriteCount(object, offset, 1); + IntrusivePtr pooled{object, nullptr, &context}; + bool thrown = false; + try { + testCase.destroy(&pooled); + } catch (const std::runtime_error&) { + thrown = true; + } + Require(thrown); + Require(deleterCalls == 1); +} + +static void TestSharedOwnership() { + alignas(8) unsigned char object[ObjectSize]; + std::memset(object, 0x5a, sizeof(object)); + WriteCount(object, 0, 1); + deleterCalls = 0; + deletedObject = nullptr; + + IntrusivePtr owner; + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1Ev(&owner); + owner.object = object; + owner.deleter = RecordDelete; + + IntrusivePtr argument; + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_(&argument, &owner); + Require(_ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv(&argument) == object); + Require(ReadCount(object, 0) == 2); + + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev(&argument); + Require(ReadCount(object, 0) == 1); + Require(deleterCalls == 0); + + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev(&owner); + Require(ReadCount(object, 0) == 0); + Require(deleterCalls == 1); + Require(deletedObject == object); + Require(_ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv(&owner) == nullptr); +} + +int main() { + Require(sizeof(IntrusivePtr) == 0x18); + + const Construct constructors[] = { + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEC1Ev, + }; + for (const auto construct : constructors) TestConstruct(construct); + + const Access accessors[] = { + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V15EntryEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6BinaryEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEEdeEv, + }; + for (const auto access : accessors) TestAccess(access); + + const Copy copies[] = { + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1ERS7_, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_, + }; + for (const auto copy : copies) TestCopy(copy); + + const DestroyCase destructors[] = { + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEED1Ev, VectorReferenceCountOffset}, + }; + for (const auto& testCase : destructors) TestDestroy(testCase); + + TestSharedOwnership(); + return 0; +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index c7520ce93..3721706f4 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -64,6 +64,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceHttpParseResponseHeader](../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature - [sceHttp2GetMemoryPoolStats](../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - unknown signature - [sceNetGetMemoryPoolStats](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sce::Np::CppWebApi::Common::IntrusivePtr](../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x18-byte layout (object, deleter, `LibContext*`) and the offset of the 32-bit reference count in the pointee (0x00, 0x30 for `Vector`) come from the module bundled with one title (PPSA21564); other SDK versions are not checked. The destructor throws on the last reference of a pointer without a deleter: destroying the object needs its destructor and the `LibContext` allocator, which are not implemented - [sceNpSessionSignalingGetMemoryInfo](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature - [sceNpSessionSignalingGetConnectionStatistics](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature - [sceNpTrophy2UnregisterUnlockCallback](../../core/libs/prx/libSceNpTrophy2/src/Context.cpp) (libSceNpTrophy2) - unknown signature From af40e9ca6939d8086338f7d979837fe5435012aa Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 11:20:30 +0200 Subject: [PATCH 112/164] feat(usbd): add the libSceUsbd module Titles that list libSceUsbd.prx as a needed module stop in the startup stub with "Failed to load module: libs\libSceUsbd.prx" (GetLastError 126), even when they import no function from it. Add libSceUsbd with an Export.cpp that contains only APS5_DUMMY_FUN, like libkernel_sys, libkernel_web, libSceAgcVsh, libSceAjm and libSceNgs2. The module now loads, exports no sceUsbd function and guesses no signature, so a title that does import one still stops with "FAIL: unresolved ELF import" for that NID. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceUsbd/Export.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 core/libs/prx/libSceUsbd/Export.cpp diff --git a/core/libs/prx/libSceUsbd/Export.cpp b/core/libs/prx/libSceUsbd/Export.cpp new file mode 100644 index 000000000..fb34206c2 --- /dev/null +++ b/core/libs/prx/libSceUsbd/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} From 3a30b5bae0502bc86a10d9add22e605cbd018ce7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Ortellado?= Date: Mon, 5 Oct 2026 08:37:55 -0300 Subject: [PATCH 113/164] fix(rtc): reject invalid RFC 3339 formatting offsets --- core/libs/prx/libSceRtc/Export.cpp | 1 + core/libs/tests/GuestRtc.cpp | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/core/libs/prx/libSceRtc/Export.cpp b/core/libs/prx/libSceRtc/Export.cpp index 3a0895ae1..08bf4bb06 100644 --- a/core/libs/prx/libSceRtc/Export.cpp +++ b/core/libs/prx/libSceRtc/Export.cpp @@ -272,6 +272,7 @@ int APS5_VABI sceRtcSetWin32FileTime(RtcDateTime* time, uint64_t win32_time) { int APS5_VABI sceRtcFormatRFC3339(char* date_time, const RtcTick* utc, int time_zone_minutes) { if (!date_time || !utc) return SCE_RTC_ERROR_INVALID_POINTER; + if (time_zone_minutes < -1439 || time_zone_minutes > 1439) return SCE_RTC_ERROR_INVALID_VALUE; RtcTick local{}; if (const int result = addTicks(&local, utc, time_zone_minutes, TICKS_PER_MINUTE); result != 0) return result; const RtcDateTime time = fromTick(local.tick); diff --git a/core/libs/tests/GuestRtc.cpp b/core/libs/tests/GuestRtc.cpp index fca1f2688..2d843ebfe 100644 --- a/core/libs/tests/GuestRtc.cpp +++ b/core/libs/tests/GuestRtc.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include extern "C" { @@ -97,6 +98,22 @@ int main() { Require(sceRtcFormatRFC3339(text, &tick, 0) == 0 && std::strcmp(text, "2024-02-29T12:34:56.78Z") == 0); Require(sceRtcFormatRFC3339(text, &tick, 90) == 0 && std::strcmp(text, "2024-02-29T14:04:56.78+01:30") == 0); Require(sceRtcFormatRFC3339(text, &tick, -300) == 0 && std::strcmp(text, "2024-02-29T07:34:56.78-05:00") == 0); + Require(sceRtcFormatRFC3339(text, &tick, 1439) == 0 && std::strcmp(text, "2024-03-01T12:33:56.78+23:59") == 0); + Require(sceRtcFormatRFC3339(text, &tick, -1439) == 0 && std::strcmp(text, "2024-02-28T12:35:56.78-23:59") == 0); + for (int offset : {0, 1, -1, 59, -59, 60, -60, 90, -300, 1439, -1439}) { + RtcTick parsed{}; + Require(sceRtcFormatRFC3339(text, &tick, offset) == 0); + Require(sceRtcParseRFC3339(&parsed, text) == 0 && parsed.tick == leapDayTick - 9000); + } + for (int offset : {1440, -1440, 6000, -6000, std::numeric_limits::min(), std::numeric_limits::max()}) { + std::memset(text, 'x', sizeof(text)); + char original[sizeof(text)]; + std::memcpy(original, text, sizeof(text)); + Require(sceRtcFormatRFC3339(text, &tick, offset) == invalidValue); + Require(std::memcmp(text, original, sizeof(text)) == 0); + } + Require(sceRtcFormatRFC3339(nullptr, &tick, 1440) == invalidPointer); + Require(sceRtcFormatRFC3339(text, nullptr, 1440) == invalidPointer); Require(sceRtcParseRFC3339(&tick, "2024-02-29T14:04:56.789+01:30") == 0 && tick.tick == leapDayTick); Require(sceRtcParseRFC3339(&tick, "2024-02-29t12:34:56.789z") == 0 && tick.tick == leapDayTick); Require(sceRtcParseRFC3339(&tick, "1970-01-01T00:00:00Z") == 0 && tick.tick == unixEpochTick); From 9f9e4e28b851e7e440ac2835727d1c4a2d326c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Ortellado?= Date: Mon, 5 Oct 2026 08:44:55 -0300 Subject: [PATCH 114/164] fix(relinker): reject invalid and overflowing alignment --- core/relinker/io/include/io/BufferUtils.hpp | 11 +++++- core/relinker/io/src/BufferUtils.cpp | 6 +-- core/relinker/io/tests/BufferBoundsTests.cpp | 41 ++++++++++++++++++++ 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/core/relinker/io/include/io/BufferUtils.hpp b/core/relinker/io/include/io/BufferUtils.hpp index 365a50847..82cc8d0ec 100644 --- a/core/relinker/io/include/io/BufferUtils.hpp +++ b/core/relinker/io/include/io/BufferUtils.hpp @@ -2,6 +2,8 @@ #define IO_BUFFERUTILS_HPP #include +#include +#include #include #include @@ -27,7 +29,14 @@ std::uint64_t ReadU64(const std::vector& buf, std::size_t offset); template constexpr T AlignUp(T value, T alignment) { - return (value + alignment - 1) / alignment * alignment; + if (value < 0 || alignment <= 0) + throw std::invalid_argument("AlignUp requires a nonnegative value and a positive alignment"); + const T remainder = value % alignment; + if (remainder == 0) return value; + const T padding = alignment - remainder; + if (value > std::numeric_limits::max() - padding) + throw std::overflow_error("Aligned value exceeds integer range"); + return value + padding; } std::uint64_t AlignUp64(std::uint64_t value, std::uint64_t alignment); diff --git a/core/relinker/io/src/BufferUtils.cpp b/core/relinker/io/src/BufferUtils.cpp index c0d389588..d4ce96336 100644 --- a/core/relinker/io/src/BufferUtils.cpp +++ b/core/relinker/io/src/BufferUtils.cpp @@ -60,11 +60,7 @@ void AppendString(std::vector& buf, const std::string& str) { } void AlignBuffer(std::vector& buf, std::size_t alignment) { - const std::size_t remainder = buf.size() % alignment; - if (remainder != 0) { - const std::size_t padding = alignment - remainder; - buf.resize(buf.size() + padding, 0); - } + buf.resize(AlignUp(buf.size(), alignment), 0); } std::uint64_t AlignUp64(std::uint64_t value, std::uint64_t alignment) { diff --git a/core/relinker/io/tests/BufferBoundsTests.cpp b/core/relinker/io/tests/BufferBoundsTests.cpp index 2de9beb8b..6767291ff 100644 --- a/core/relinker/io/tests/BufferBoundsTests.cpp +++ b/core/relinker/io/tests/BufferBoundsTests.cpp @@ -11,6 +11,8 @@ namespace { using Bytes = std::vector; +static_assert(Io::AlignUp(std::uint64_t{17}, std::uint64_t{16}) == 32); + void require(bool condition, const std::string& message) { if (!condition) throw std::runtime_error(message); } @@ -71,12 +73,51 @@ void writeBounds(const TWrite& write, const std::string& name) { invalidRanges(bytes, [&](Bytes& buffer, std::size_t offset) { write(buffer, offset, value); }, name); } +template +void requireThrows(const TOperation& operation, const std::string& message) { + try { + operation(); + } catch (const TError&) { + return; + } + throw std::runtime_error(message); +} + +template +void alignmentBounds() { + constexpr auto maximum = std::numeric_limits::max(); + require(Io::AlignUp(TValue{0}, TValue{16}) == 0, "AlignUp changed zero"); + require(Io::AlignUp(TValue{17}, TValue{16}) == 32, "AlignUp failed to round up"); + require(Io::AlignUp(TValue{7}, TValue{3}) == 9, "AlignUp requires a power-of-two alignment"); + require(Io::AlignUp(maximum, TValue{1}) == maximum, "AlignUp changed an aligned maximum"); + require(Io::AlignUp(static_cast(maximum - 15), TValue{16}) == maximum - 15, "AlignUp changed an aligned value near the maximum"); + require(Io::AlignUp(static_cast(maximum - 1), maximum) == maximum, "AlignUp overflowed for a representable result"); + requireThrows([&] { Io::AlignUp(maximum, TValue{16}); }, "AlignUp accepted an overflowing result"); + requireThrows([] { Io::AlignUp(TValue{1}, TValue{0}); }, "AlignUp accepted zero alignment"); +} + } int main() { try { const Io::ByteReader reader; const Io::ByteWriter writer; + alignmentBounds(); + alignmentBounds(); + alignmentBounds(); + alignmentBounds(); + requireThrows([] { Io::AlignUp(-1, 16); }, "AlignUp accepted a negative value"); + requireThrows([] { Io::AlignUp(1, -1); }, "AlignUp accepted negative alignment"); + require(Io::AlignUp64(std::numeric_limits::max() - 1, std::numeric_limits::max()) == std::numeric_limits::max(), "AlignUp64 overflowed for a representable result"); + requireThrows([] { Io::AlignUp64(std::numeric_limits::max(), 16); }, "AlignUp64 accepted an overflowing result"); + Bytes aligned{1, 2, 3}; + const auto original = aligned; + requireThrows([&] { Io::AlignBuffer(aligned, 0); }, "AlignBuffer accepted zero alignment"); + require(aligned == original, "AlignBuffer modified the buffer on failure"); + Io::AlignBuffer(aligned, 4); + require(aligned == Bytes({1, 2, 3, 0}), "AlignBuffer failed to preserve bytes and zero-fill padding"); + Io::AlignBuffer(aligned, 4); + require(aligned == Bytes({1, 2, 3, 0}), "AlignBuffer changed an aligned buffer"); readBounds([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU16(bytes, offset); }, "ByteReader::ReadU16"); readBounds([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU32(bytes, offset); }, "ByteReader::ReadU32"); readBounds([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU64(bytes, offset); }, "ByteReader::ReadU64"); From e1bfd03f2360e493529658ed0f948db8ea269e4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Ortellado?= Date: Mon, 5 Oct 2026 08:57:56 -0300 Subject: [PATCH 115/164] fix(libc): return null when strdup allocation fails --- core/libs/prx/libc/src/AllocatingStrings.cpp | 4 ++++ core/libs/prx/libc/src/Strings.cpp | 8 ------- core/libs/tests/ApplicationHeap.cpp | 22 ++++++++++++++++++++ 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/core/libs/prx/libc/src/AllocatingStrings.cpp b/core/libs/prx/libc/src/AllocatingStrings.cpp index d3539587b..74779c475 100644 --- a/core/libs/prx/libc/src/AllocatingStrings.cpp +++ b/core/libs/prx/libc/src/AllocatingStrings.cpp @@ -30,6 +30,10 @@ char* APS5_VABI strndup_nid_postfix(const char* source, std::size_t limit) { } } +char* APS5_VABI strdup_nid_postfix(const char* source) { + return strndup_nid_postfix(source, static_cast(-1)); +} + int APS5_VABI asprintf_nid_postfix(char** destination, const char* format, ...) { if (!destination) { errno = 22; return -1; } *destination = nullptr; diff --git a/core/libs/prx/libc/src/Strings.cpp b/core/libs/prx/libc/src/Strings.cpp index 5bcad4d2a..971b12cfe 100644 --- a/core/libs/prx/libc/src/Strings.cpp +++ b/core/libs/prx/libc/src/Strings.cpp @@ -1,4 +1,3 @@ -#include "prx/libc/include/ApplicationHeap.hpp" #include #include #include @@ -156,13 +155,6 @@ int APS5_VABI strncasecmp_nid_postfix(const char* s1, const char* s2, size_t n) return static_cast(*s1) - static_cast(*s2); } -char* APS5_VABI strdup_nid_postfix(const char* s) { - std::size_t len = std::strlen(s) + 1; - char* copy = static_cast(ApplicationHeapAllocate_nid_no_patch(len)); - std::memcpy(copy, s, len); - return copy; -} - int APS5_VABI bcmp_nid_postfix(const void* s1, const void* s2, size_t n) { return std::memcmp(s1, s2, n); } diff --git a/core/libs/tests/ApplicationHeap.cpp b/core/libs/tests/ApplicationHeap.cpp index 3fb557ed1..0f08924bb 100644 --- a/core/libs/tests/ApplicationHeap.cpp +++ b/core/libs/tests/ApplicationHeap.cpp @@ -14,6 +14,9 @@ void APS5_VABI _ZdlPv_nid_postfix(void*); void APS5_VABI _ZdaPv_nid_postfix(void*); void APS5_VABI _ZdlPvSt11align_val_t_nid_postfix(void*, std::size_t); void* ApplicationHeapRealign_nid_no_patch(void*, std::size_t, std::size_t); +char* APS5_VABI strdup_nid_postfix(const char*); +char* APS5_VABI strndup_nid_postfix(const char*, std::size_t); +int* APS5_VABI __error_nid_postfix(); } namespace { @@ -192,4 +195,23 @@ int main(int argc, char** argv) { reject([] { ApplicationHeapAllocate_nid_no_patch(64); }); recurse = false; require(ApplicationHeapAllocate_nid_no_patch(64) == storage.data()); + const char text[] = "guest string"; + char* copy = strdup_nid_postfix(text); + require(copy == reinterpret_cast(storage.data()) && lastSize == sizeof(text)); + require(std::strcmp(copy, text) == 0 && copy != text); + ApplicationHeapFree_nid_no_patch(copy); + copy = strdup_nid_postfix(""); + require(lastSize == 1 && copy[0] == '\0'); + ApplicationHeapFree_nid_no_patch(copy); + copy = strndup_nid_postfix(text, 5); + require(lastSize == 6 && std::strcmp(copy, "guest") == 0); + ApplicationHeapFree_nid_no_patch(copy); + fail = true; + *__error_nid_postfix() = 0; + require(strdup_nid_postfix(text) == nullptr && *__error_nid_postfix() == 12); + *__error_nid_postfix() = 0; + require(strdup_nid_postfix("") == nullptr && *__error_nid_postfix() == 12); + *__error_nid_postfix() = 0; + require(strndup_nid_postfix(text, 5) == nullptr && *__error_nid_postfix() == 12); + fail = false; } From 23008762cc685b5e4d05d1685018b3e1f5064f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Ortellado?= Date: Mon, 5 Oct 2026 09:13:05 -0300 Subject: [PATCH 116/164] fix(libkernel): preserve 64-bit metadata download sizes --- .../AppMetadata/src/ParamJsonParser.cpp | 14 ++++- core/libs/prx/libkernel/CMakeLists.txt | 9 +++ core/libs/tests/ParamJsonParser.cpp | 56 +++++++++++++++++++ 3 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 core/libs/tests/ParamJsonParser.cpp diff --git a/core/libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp b/core/libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp index 89b53c3f2..aa36abe72 100644 --- a/core/libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp +++ b/core/libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp @@ -1,6 +1,7 @@ #include "prx/libkernel/AppMetadata/include/ParamJsonParser.hpp" #include +#include #include #include #include @@ -21,6 +22,7 @@ struct JsonValue { JsonType type = JsonType::Null; bool boolValue = false; double numberValue = 0.0; + std::string numberText; std::string stringValue; JsonArray arrayValue; JsonObject objectValue; @@ -193,7 +195,8 @@ JsonValue parseJsonNumber(const std::string& text, std::size_t& position) { } JsonValue value; value.type = JsonType::Number; - value.numberValue = std::stod(text.substr(start, position - start)); + value.numberText = text.substr(start, position - start); + value.numberValue = std::stod(value.numberText); return value; } @@ -261,7 +264,14 @@ ParsedParamJson parseParamJson(const std::filesystem::path& paramJsonPath) { result.titleId = titleId; if (const JsonValue* downloadData = findObjectMember(root, "downloadDataSize")) { if (downloadData->type != JsonType::Number || downloadData->numberValue < 0) throw std::runtime_error("param.json downloadDataSize is not a size"); - result.downloadDataSizeMiB = static_cast(downloadData->numberValue); + if (downloadData->numberText.find_first_of("-.eE") == std::string::npos) { + const auto& number = downloadData->numberText; + const auto parsed = std::from_chars(number.data(), number.data() + number.size(), result.downloadDataSizeMiB); + if (parsed.ec != std::errc{} || parsed.ptr != number.data() + number.size()) throw std::runtime_error("param.json downloadDataSize exceeds uint64 range"); + } else { + if (downloadData->numberValue >= 0x1p64) throw std::runtime_error("param.json downloadDataSize exceeds uint64 range"); + result.downloadDataSizeMiB = static_cast(downloadData->numberValue); + } } return result; } diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 2e5364b1b..43949deb4 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -70,6 +70,15 @@ if(MINGW) endif() if(BUILD_TESTING) + add_test_executable(param_json_parser_tests ${LIBS_INCLUDE_DIR}/tests/ParamJsonParser.cpp) + target_include_directories(param_json_parser_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(param_json_parser_tests PRIVATE libkernel libc) + configure_windows_unwind(param_json_parser_tests) + add_test(NAME param_json_parser COMMAND param_json_parser_tests) + if(WIN32) + set_tests_properties(param_json_parser PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_posix_sem_tests ${LIBS_INCLUDE_DIR}/tests/GuestPosixSem.cpp) target_include_directories(guest_posix_sem_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_posix_sem_tests PRIVATE libkernel libc) diff --git a/core/libs/tests/ParamJsonParser.cpp b/core/libs/tests/ParamJsonParser.cpp new file mode 100644 index 000000000..fc3c06cb0 --- /dev/null +++ b/core/libs/tests/ParamJsonParser.cpp @@ -0,0 +1,56 @@ +#include "prx/libkernel/AppMetadata/include/ParamJsonParser.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +static ParsedParamJson ParseSize(const std::filesystem::path& path, const std::string& size) { + { + std::ofstream file(path); + file << R"({"titleId":"PPSA00000","localizedParameters":{"en-US":{"titleName":"Example"}})"; + if (!size.empty()) file << ",\"downloadDataSize\":" << size; + file << '}'; + Require(static_cast(file), "Cannot write param.json fixture"); + } + return parseParamJson(path); +} + +int main() { + const auto path = std::filesystem::temp_directory_path() / ("anyps5-param-json-" + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".json"); + int result = 0; + try { + const std::pair sizes[] = { + {"", 0}, {"0", 0}, {"-0", 0}, {"4096", 4096}, + {"9007199254740993", 9007199254740993ull}, + {"18446744073709551614", std::numeric_limits::max() - 1}, + {"18446744073709551615", std::numeric_limits::max()}, + {"1e3", 1000}, {"1.25", 1} + }; + for (const auto& [text, expected] : sizes) { + const auto parsed = ParseSize(path, text); + Require(parsed.downloadDataSizeMiB == expected, std::string("Incorrect download size: ") + text); + Require(parsed.titleId == "PPSA00000" && parsed.title == "Example", "Title metadata changed"); + } + for (const auto* text : {"18446744073709551616", "18446744073709551617", "1.8446744073709552e19", + "1e40", "-1", "-0.5", "true", "null", "\"4096\""}) { + bool rejected = false; + try { ParseSize(path, text); } catch (const std::exception&) { rejected = true; } + Require(rejected, std::string("Accepted invalid download size: ") + text); + } + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + result = 1; + } + std::filesystem::remove(path); + return result; +} From 1fa44c7b5c8815514a2a4d3b04459cf9d5df40de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Ortellado?= Date: Mon, 5 Oct 2026 09:23:47 -0300 Subject: [PATCH 117/164] fix(relinker): decode autorun exit status for the host --- core/relinker/CMakeLists.txt | 6 +++ core/relinker/cli/src/Autorun.cpp | 13 +++--- core/relinker/cli/tests/AutorunExitTests.cpp | 46 ++++++++++++++++++++ 3 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 core/relinker/cli/tests/AutorunExitTests.cpp diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index a388ec7a3..fb705627a 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -125,6 +125,12 @@ endif() if(BUILD_TESTING) + add_test_executable(autorun_exit_tests + ${CMAKE_CURRENT_SOURCE_DIR}/cli/tests/AutorunExitTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cli/src/Autorun.cpp) + target_include_directories(autorun_exit_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cli/include) + add_test(NAME autorun_exit_status COMMAND autorun_exit_tests) + set_tests_properties(autorun_exit_status PROPERTIES TIMEOUT 20) add_test_executable(buffer_bounds_tests ${CMAKE_CURRENT_SOURCE_DIR}/io/tests/BufferBoundsTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io/src/ByteReader.cpp diff --git a/core/relinker/cli/src/Autorun.cpp b/core/relinker/cli/src/Autorun.cpp index 30979a985..3bb7016be 100644 --- a/core/relinker/cli/src/Autorun.cpp +++ b/core/relinker/cli/src/Autorun.cpp @@ -20,12 +20,15 @@ int Autorun(const std::string& absPath, bool toWindows) { const int rawCode = std::system(cmd.c_str()); int exitCode = rawCode; - if (toWindows) { - std::cout << "\nExit code: " << rawCode << '\n'; - } else { - if (rawCode != -1) exitCode = (rawCode >> 8) & 0xFF; - std::cout << "\nRaw exit code: " << rawCode << "; Unpacked: " << exitCode << '\n'; +#ifdef _WIN32 + std::cout << "\nExit code: " << rawCode << '\n'; +#else + if (rawCode != -1) { + const int signal = rawCode & 0x7F; + exitCode = signal ? 128 + signal : (rawCode >> 8) & 0xFF; } + std::cout << "\nRaw exit code: " << rawCode << "; Unpacked: " << exitCode << '\n'; +#endif std::cout << "\nPress Enter to exit...\n"; std::cin.ignore(std::numeric_limits::max(), '\n'); diff --git a/core/relinker/cli/tests/AutorunExitTests.cpp b/core/relinker/cli/tests/AutorunExitTests.cpp new file mode 100644 index 000000000..f6fc70829 --- /dev/null +++ b/core/relinker/cli/tests/AutorunExitTests.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int, char* argv[]) { + const auto executable = std::filesystem::absolute(argv[0]); + const auto name = executable.stem().string(); + if (name.starts_with("anyps5-autorun-child-")) { + if (name.find("signal") != std::string::npos) std::raise(SIGILL); + return 42; + } + const auto child = std::filesystem::temp_directory_path() / ("anyps5-autorun-child-" + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + " with spaces" + executable.extension().string()); +#ifndef _WIN32 + const auto signaled = child.parent_path() / ("anyps5-autorun-child-signal-" + child.filename().string()); +#endif + std::istringstream input("\n\n\n"); + auto* savedInput = std::cin.rdbuf(input.rdbuf()); + int result = 0; + try { + std::filesystem::copy_file(executable, child); + for (const bool windows : {false, true}) { + const int code = Cli::Autorun(child.string(), windows); + if (code != 42) throw std::runtime_error("Autorun changed exit code 42 to " + std::to_string(code)); + } +#ifndef _WIN32 + std::filesystem::rename(child, signaled); + const int code = Cli::Autorun(signaled.string(), false); + if (code != 128 + SIGILL) throw std::runtime_error("Autorun lost the child signal"); +#endif + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + result = 1; + } + std::cin.rdbuf(savedInput); + std::filesystem::remove(child); +#ifndef _WIN32 + std::filesystem::remove(signaled); +#endif + return result; +} From 7e90590146dcc836575c19c17fe1f09d2de9de69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 13:50:46 +0200 Subject: [PATCH 118/164] feat(shader): accept every SDWA selector on VOP2 float ops Co-Authored-By: Claude --- .../tests/execution/SdwaFloatSelectors.cpp | 218 ++++++++++++++++++ .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 29 +-- 2 files changed, 234 insertions(+), 13 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/SdwaFloatSelectors.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SdwaFloatSelectors.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaFloatSelectors.cpp new file mode 100644 index 000000000..10863bc09 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaFloatSelectors.cpp @@ -0,0 +1,218 @@ +#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 +#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, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x06140af9, 0x06000604, 0x7e160307, + 0x08160af9, 0x05061504, 0x0a180af9, 0x06142a04, 0x101a0af9, 0x03260404, 0x0e1c0af9, 0x06010604, + 0x1e1e0af9, 0x160a0604, 0x7e200307, 0x20200af9, 0x08061104, 0x20220af9, 0x060d2604, 0x64240af9, + 0x05010004, 0x66260af9, 0x02150d04, 0x7e280307, 0x68280af9, 0x24031304, 0x6a2a0af9, 0x00040604, + 0x7e2c0307, 0x742c0af9, 0x05081404, 0x722e0af9, 0x19050204, 0x06300af9, 0x06052b04, 0x6a320af9, + 0x06020904, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, + 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000000u, 0x40404040u, 0x8b2af49fu}, + {0x00000000u, 0x10663f9du, 0x8b35e743u, 0x7d7d9db4u}, + {0x0008dbf2u, 0x47004700u, 0x3c000000u, 0x80000000u}, + {0x05c21fabu, 0x00470047u, 0x80000000u, 0x47004700u}, + {0x1a0e7cdeu, 0x00000000u, 0xefc2742bu, 0xcd8d3db8u}, + {0xc4de998au, 0xaf1fba34u, 0x0000bc00u, 0xbc00bc00u}, + {0x00000000u, 0x00470047u, 0xc2c2c2c2u, 0xfda0095du}, + {0x3c000000u, 0x80808080u, 0x5960b098u, 0xadd5eff7u}, + {0xbc00bc00u, 0x00000000u, 0x42a502c0u, 0x3c000000u}, + {0x80808080u, 0x1a64a58eu, 0xfe242714u, 0x3c003c00u}, + {0x00000000u, 0x7c007c00u, 0x7f7f7f7fu, 0x3c003c00u}, + {0x00470047u, 0x00000000u, 0x3468787du, 0x7e7103e1u}, + {0x00470047u, 0x80000000u, 0x702bd672u, 0x47004700u}, + {0x00000000u, 0x00800080u, 0x4659ff82u, 0xc2c2c2c2u}, + {0x7f800000u, 0x00000000u, 0x01000100u, 0x1b1c3f27u}, + {0x80000000u, 0x00800080u, 0xbf800000u, 0x829ea49cu}, + {0x00000000u, 0x0000bc00u, 0x3343e632u, 0x13e60ef5u}, + {0x00000000u, 0x00470047u, 0xb5a54b0bu, 0x6769ec45u}, + {0x00000000u, 0x00800080u, 0xa2e7068bu, 0x871c3c9cu}, + {0x00000000u, 0x00800080u, 0xafe5c4b5u, 0x3c003c00u}, + {0x00000000u, 0x00ff00ffu, 0x0000bc00u, 0x40404040u}, + {0x00000000u, 0x00ff00ffu, 0x7f800000u, 0x00ff00ffu}, + {0x00000000u, 0x00ff00ffu, 0xb4ad5866u, 0xe4ea7accu}, + {0x00000000u, 0x00ff00ffu, 0xd03c7dbeu, 0x5de25876u}, + {0x00000000u, 0x01000100u, 0x52eff50fu, 0x00000000u}, + {0x00000000u, 0x182f8e4au, 0xe117e1fau, 0x0000bc00u}, + {0x00000000u, 0x20d8e2bcu, 0xe73074b1u, 0xbf800000u}, + {0x00000000u, 0x2333ad9cu, 0x00800080u, 0x3f800000u}, + {0x00000000u, 0x24f70eabu, 0xfdb88e95u, 0x17e6487au}, + {0x00000000u, 0x2a778095u, 0x00470047u, 0x7c007c00u}, + {0x00000000u, 0x2b119b05u, 0x0000bc00u, 0x4ce5144fu}, + {0x00000000u, 0x2b5d55fdu, 0x03f55439u, 0x7f7f7f7fu} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000000u, 0x0000f49fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x8b2a009fu, 0x00000000u, 0x00000000u, 0x80000000u, 0x002af49fu, 0x00000000u, 0x8b2a0000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x10663f9du, 0x10669db4u, 0xff9d0000u, 0x00000000u, 0x00000000u, 0x90663f9du, 0x7d7d00b4u, 0x10663f9du, 0x00000066u, 0x80660000u, 0x9d7d9db4u, 0x00000000u, 0x7d7d0000u, 0x00000000u, 0x9d000000u, 0x00000000u}, + {0x47004700u, 0x94f20000u, 0x00000000u, 0x00000000u, 0x006dbcbdu, 0xc7004700u, 0x8000f200u, 0x3f800000u, 0x00000000u, 0x80080000u, 0x00000000u, 0x00008000u, 0x80004700u, 0x00080000u, 0x00000000u, 0x00003800u}, + {0x004700f2u, 0x1fab4700u, 0xfff20000u, 0x00000000u, 0x00000000u, 0x80470047u, 0x4700ab00u, 0x00470047u, 0x00000066u, 0x86090000u, 0x42004700u, 0x00000001u, 0x47000047u, 0x00c20000u, 0x09000000u, 0x00000000u}, + {0x000000deu, 0x7cde3db8u, 0xffde0000u, 0x00000000u, 0x00000000u, 0x80000000u, 0xcd8ddeb8u, 0x00001a0eu, 0x0000007cu, 0x9a0e0000u, 0x1a8d3db8u, 0x00007edeu, 0xcd8d0000u, 0x000e0000u, 0x0e000000u, 0x00000000u}, + {0xaf1fba34u, 0x998abc00u, 0x00000000u, 0x0000c158u, 0x80000000u, 0x2f1fba34u, 0xbc003400u, 0x00000000u, 0x0000001fu, 0x44de0000u, 0x3400bc00u, 0x00008000u, 0xbc00af1fu, 0x00de0000u, 0x00000000u, 0xffffac00u}, + {0x00470047u, 0x0047095du, 0x00470000u, 0x00000000u, 0x00000000u, 0x80470047u, 0xfda0475du, 0x00470047u, 0x00000047u, 0x80470000u, 0x47a0095du, 0x00000000u, 0xfda00000u, 0x00000000u, 0x47000000u, 0x00000000u}, + {0x80808080u, 0x0000eff7u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0xadd500f7u, 0x00003c00u, 0x00000080u, 0xbc000000u, 0x44d5eff7u, 0x00000000u, 0xadd58080u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xbc000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x3c000000u, 0x00000000u, 0x000000bcu, 0x3c000000u, 0xbc000000u, 0x00008000u, 0x3c000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x1a64a58eu, 0x9ae43c00u, 0xff8e0000u, 0x00000000u, 0x00000000u, 0x9a64a58eu, 0x3c008000u, 0x1a64a58eu, 0x00000068u, 0x001c0000u, 0x8e003c00u, 0x00008000u, 0x3c001a64u, 0x00800000u, 0x8e000000u, 0x00000300u}, + {0x7c007c00u, 0x7c003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfc007c00u, 0x3c000000u, 0x3f800000u, 0x00000000u, 0x80000000u, 0x00003c00u, 0x00000000u, 0x3c000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000047u, 0x004703e1u, 0x00470000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x7e7147e1u, 0x00000047u, 0x00000000u, 0x80470000u, 0x007103e1u, 0x00000000u, 0x7e710000u, 0x00470000u, 0x47000000u, 0x00000000u}, + {0x00000047u, 0x80474700u, 0x00470000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x47004700u, 0x00000047u, 0x00000000u, 0x80470000u, 0x00004700u, 0x00000000u, 0x47008000u, 0x00470000u, 0x47000000u, 0x00000000u}, + {0x00800080u, 0x0080c2c2u, 0xff800000u, 0x00000000u, 0x00000000u, 0x80800080u, 0xc2c200c2u, 0x00800080u, 0x00000080u, 0x80800000u, 0x80c2c2c2u, 0x00000000u, 0xc2c20000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x00003f27u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x1b1c0027u, 0x00007f80u, 0x00000000u, 0xff800000u, 0x7f1c3f27u, 0x00000000u, 0x1b1c0000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00800080u, 0x0080a49cu, 0xff800000u, 0x00000000u, 0x00000000u, 0x80800080u, 0x829e009cu, 0x00800080u, 0x00000080u, 0x80800000u, 0x009ea49cu, 0x00000000u, 0x829e0000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x0000bc00u, 0x00000ef5u, 0x00000000u, 0x00000000u, 0x00000000u, 0x8000bc00u, 0x13e600f5u, 0x0000bc00u, 0x00000000u, 0x80000000u, 0x00e60ef5u, 0x00000000u, 0x13e60000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00470047u, 0x0047ec45u, 0x00470000u, 0x00000000u, 0x00000000u, 0x80470047u, 0x67694745u, 0x00470047u, 0x00000047u, 0x80470000u, 0x4769ec45u, 0x00000000u, 0x67690000u, 0x00000000u, 0x47000000u, 0x00000000u}, + {0x00800080u, 0x00803c9cu, 0xff800000u, 0x00000000u, 0x00000000u, 0x80800080u, 0x871c009cu, 0x00800080u, 0x00000080u, 0x80800000u, 0x801c3c9cu, 0x00000000u, 0x871c0000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00800080u, 0x00803c00u, 0xff800000u, 0x00000000u, 0x00000000u, 0x80800080u, 0x3c000000u, 0x00800080u, 0x00000080u, 0x80800000u, 0x80003c00u, 0x00000000u, 0x3c000000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00ff00ffu, 0x00ff4040u, 0xffff0000u, 0x00000000u, 0x00000000u, 0x80ff00ffu, 0x40400040u, 0x00ff00ffu, 0x000000ffu, 0x80ff0000u, 0xff404040u, 0x00000000u, 0x40400000u, 0x00000000u, 0xff000000u, 0x00000000u}, + {0x00ff00ffu, 0x00ff00ffu, 0xffff0000u, 0x00000000u, 0x00000000u, 0x80ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x000000ffu, 0x80ff0000u, 0xffff00ffu, 0x00000000u, 0x00ff0000u, 0x00000000u, 0xff000000u, 0x00000000u}, + {0x00ff00ffu, 0x00ff7accu, 0xffff0000u, 0x00000000u, 0x00000000u, 0x80ff00ffu, 0xe4ea00ccu, 0x00ff00ffu, 0x000000ffu, 0x80ff0000u, 0xffea7accu, 0x00000000u, 0xe4ea0000u, 0x00000000u, 0xff000000u, 0x00000000u}, + {0x00ff00ffu, 0x00ff5876u, 0xffff0000u, 0x00000000u, 0x00000000u, 0x80ff00ffu, 0x5de20076u, 0x00ff00ffu, 0x000000ffu, 0x80ff0000u, 0xffe25876u, 0x00000000u, 0x5de20000u, 0x00000000u, 0xff000000u, 0x00000000u}, + {0x01000100u, 0x01000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x81000100u, 0x00000000u, 0x01000100u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x182f8e4au, 0x182fbc00u, 0x004a0000u, 0x00000000u, 0x00000000u, 0x982f8e4au, 0x00004a00u, 0x182f8e4au, 0x0000002fu, 0x802f0000u, 0x4a00bc00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x4a000000u, 0x00000000u}, + {0x20d8e2bcu, 0x20d80000u, 0xffbc0000u, 0x00000000u, 0x00000000u, 0xa0d8e2bcu, 0xbf800000u, 0x20d8e2bcu, 0x000000d8u, 0x80d80000u, 0xbc800000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0xbc000000u, 0x00000000u}, + {0x2333ad9cu, 0x23330000u, 0xff9c0000u, 0x00000000u, 0x00000000u, 0xa333ad9cu, 0x3f800000u, 0x2333ad9cu, 0x00000033u, 0x80330000u, 0x9c800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x9c000000u, 0x00000000u}, + {0x24f70eabu, 0x24f7487au, 0xffab0000u, 0x00000000u, 0x00000000u, 0xa4f70eabu, 0x17e6007au, 0x24f70eabu, 0x000000f7u, 0x80f70000u, 0xabe6487au, 0x00000000u, 0x17e60000u, 0x00000000u, 0xab000000u, 0x00000000u}, + {0x2a778095u, 0x2a777c00u, 0xff950000u, 0x00000000u, 0x00000000u, 0xaa778095u, 0x7c000000u, 0x2a778095u, 0x00000077u, 0x80770000u, 0x95007c00u, 0x00000000u, 0x7c000000u, 0x00000000u, 0x95000000u, 0x00000000u}, + {0x2b119b05u, 0x2b11144fu, 0x00050000u, 0x00000000u, 0x00000000u, 0xab119b05u, 0x4ce5054fu, 0x2b119b05u, 0x00000011u, 0x80110000u, 0x05e5144fu, 0x00000000u, 0x4ce50000u, 0x00000000u, 0x05000000u, 0x00000000u}, + {0x2b5d55fdu, 0x2b5d7f7fu, 0xfffd0000u, 0x00000000u, 0x00000000u, 0xab5d55fdu, 0x7f7f007fu, 0x2b5d55fdu, 0x0000005du, 0x805d0000u, 0xfd7f7f7fu, 0x00000000u, 0x7f7f0000u, 0x00000000u, 0xfd000000u, 0x00000000u} +}; +constexpr std::uint32_t Checked[32] = {0xffffu, 0xfdfdu, 0x5dadu, 0xa05au, 0xb27au, 0x7fa7u, 0xb818u, 0xea77u, 0xfaffu, 0x5cfdu, 0x7ffdu, 0x9d38u, 0x9d38u, 0xf8fdu, 0xb977u, 0xfcfdu, 0xff5eu, 0xb818u, 0xf8fdu, 0xf8fdu, 0xf8fdu, 0xf8fdu, 0xf8fdu, 0xf8fdu, 0xfffdu, 0xfdbdu, 0xfdfdu, 0xfdfdu, 0xfdfdu, 0xf9fdu, 0xfdbdu, 0xfdfdu}; +constexpr const char* Names[16] = { + "add_f32 BYTE_0/DWORD -> DWORD", + "sub_f32 DWORD/WORD_1 -> WORD_1", + "subrev_f32 WORD_0/DWORD -> BYTE_2", + "mul_f32 DWORD/BYTE_3 -> WORD_0", + "mul_legacy_f32 BYTE_1/DWORD -> DWORD", + "min_f32 BYTE_2/DWORD -> DWORD", + "max_f32 DWORD/BYTE_0 -> BYTE_1", + "max_f32 WORD_1/DWORD -> DWORD", + "add_f16 BYTE_1/WORD_1 -> BYTE_0", + "sub_f16 WORD_1/BYTE_2 -> WORD_1", + "subrev_f16 BYTE_3/WORD_0 -> BYTE_3", + "mul_f16 WORD_0/BYTE_0 -> DWORD", + "min_f16 BYTE_0/WORD_1 -> WORD_0", + "max_f16 WORD_1/BYTE_1 -> BYTE_2", + "add_f32 WORD_1/DWORD -> BYTE_3", + "mul_f16 BYTE_2/DWORD -> BYTE_1", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("sdwa float selectors: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +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}, 0u, {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 std::span code(Code); + const auto result = Compile(device, code); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void CheckByteSelectorClampRefused(AgcDriver::VulkanDevice& device) { + constexpr std::array, 6> forms{{ + {0x640a0cf9u, 0x04052004u}, + {0x660a0cf9u, 0x04022504u}, + {0x680a0cf9u, 0x03042604u}, + {0x6a0a0cf9u, 0x05043304u}, + {0x740a0cf9u, 0x06012404u}, + {0x720a0cf9u, 0x00062604u}, + }}; + for (const auto& form : forms) { + alignas(256) const std::array code{form[0], form[1], 0xbf810000u}; + std::string refusal; + try { + static_cast(Compile(device, code)); + } catch (const std::exception& error) { + refusal = error.what(); + } + Require(refusal.find("VOP2 SDWA clamp with byte selectors is not implemented") != std::string::npos, "f16 SDWA form " + Hex(form[0]) + " " + Hex(form[1]) + " with clamp was not refused"); + } +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + if ((Checked[tid] >> i) & 1u) { + Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + CheckByteSelectorClampRefused(*device); + std::puts("sdwa float selectors tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index f91cfbcd5..4a94c8482 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -23,10 +23,9 @@ struct VectorOpcodeInfo { enum class Vop2SdwaProfile { None, - Float32, - Float16, - PackedFloat16, + Float, Integer, + Float16, }; struct Vop2OpcodeInfo { @@ -44,17 +43,17 @@ struct VopcOpcodeInfo { constexpr Vop2OpcodeInfo vop2Opcodes[] = { {0x01u, RdnaOpcode::VCndmaskB32, Vop2SdwaProfile::Integer}, {0x02u, RdnaOpcode::VDot2cF32F16}, - {0x03u, RdnaOpcode::VAddF32, Vop2SdwaProfile::Float32}, - {0x04u, RdnaOpcode::VSubF32, Vop2SdwaProfile::Float32}, - {0x05u, RdnaOpcode::VSubrevF32}, - {0x08u, RdnaOpcode::VMulF32, Vop2SdwaProfile::Float32}, + {0x03u, RdnaOpcode::VAddF32, Vop2SdwaProfile::Float}, + {0x04u, RdnaOpcode::VSubF32, Vop2SdwaProfile::Float}, + {0x05u, RdnaOpcode::VSubrevF32, Vop2SdwaProfile::Float}, + {0x08u, RdnaOpcode::VMulF32, Vop2SdwaProfile::Float}, {0x09u, RdnaOpcode::VMulI32I24, Vop2SdwaProfile::Integer}, {0x0au, RdnaOpcode::VMulHiI32I24, Vop2SdwaProfile::Integer}, {0x0bu, RdnaOpcode::VMulU32U24, Vop2SdwaProfile::Integer}, {0x0cu, RdnaOpcode::VMulHiU32U24, Vop2SdwaProfile::Integer}, {0x0du, RdnaOpcode::VDot4cI32I8}, - {0x0fu, RdnaOpcode::VMinF32}, - {0x10u, RdnaOpcode::VMaxF32}, + {0x0fu, RdnaOpcode::VMinF32, Vop2SdwaProfile::Float}, + {0x10u, RdnaOpcode::VMaxF32, Vop2SdwaProfile::Float}, {0x11u, RdnaOpcode::VMinI32, Vop2SdwaProfile::Integer}, {0x12u, RdnaOpcode::VMaxI32, Vop2SdwaProfile::Integer}, {0x13u, RdnaOpcode::VMinU32, Vop2SdwaProfile::Integer}, @@ -84,7 +83,7 @@ constexpr Vop2OpcodeInfo vop2Opcodes[] = { {0x2bu, RdnaOpcode::VMacF32}, {0x2cu, RdnaOpcode::VMadmkF32}, {0x2du, RdnaOpcode::VMadakF32}, - {0x2fu, RdnaOpcode::VCvtPkrtzF16F32, Vop2SdwaProfile::PackedFloat16}, + {0x2fu, RdnaOpcode::VCvtPkrtzF16F32, Vop2SdwaProfile::Float}, {0x32u, RdnaOpcode::VAddF16, Vop2SdwaProfile::Float16}, {0x33u, RdnaOpcode::VSubF16, Vop2SdwaProfile::Float16}, {0x34u, RdnaOpcode::VSubrevF16, Vop2SdwaProfile::Float16}, @@ -96,7 +95,7 @@ constexpr Vop2OpcodeInfo vop2Opcodes[] = { {0x3au, RdnaOpcode::VMinF16, Vop2SdwaProfile::Float16}, {0x3cu, RdnaOpcode::VPkFmacF16}, {0x06u, RdnaOpcode::VMacLegacyF32}, - {0x07u, RdnaOpcode::VMulLegacyF32}, + {0x07u, RdnaOpcode::VMulLegacyF32, Vop2SdwaProfile::Float}, {0x3bu, RdnaOpcode::VLdexpF16}, }; @@ -1298,14 +1297,14 @@ struct Vop2SdwaRule { std::uint32_t src1Selectors = sdwaSelFull(); bool partialDst = false; bool sourceModifiers = false; + bool byteSelectorClamp = true; }; constexpr Vop2SdwaRule vop2SdwaRules[] = { {}, - {sdwaSelFull(), sdwaSelFull(), sdwaSelFull(), false, true}, - {sdwaSelWords() | sdwaSelFull(), sdwaSelWords() | sdwaSelFull(), sdwaSelWords() | sdwaSelFull(), true, true}, {sdwaSelAll(), sdwaSelAll(), sdwaSelAll(), true, true}, {sdwaSelAll(), sdwaSelAll(), sdwaSelAll(), true, false}, + {sdwaSelAll(), sdwaSelAll(), sdwaSelAll(), true, true, false}, }; const Vop2SdwaRule* findVop2SdwaRule(std::uint32_t encoding) { @@ -1365,6 +1364,10 @@ void validateVop2Sdwa(const RdnaInstruction& instruction, std::uint32_t opcode, if (!rule->sourceModifiers && hasSourceModifiers && !cndmaskFloatModifiers) { throw std::invalid_argument("VOP2 SDWA source modifiers are not supported"); } + const bool byteSelector = fields.src0Sel < 4u || fields.src1Sel < 4u || fields.dstSel < 4u; + if (!rule->byteSelectorClamp && fields.clamp != 0u && byteSelector) { + throw std::invalid_argument("VOP2 SDWA clamp with byte selectors is not implemented"); + } } void finalizeVop2Instruction(std::span code, std::uint32_t wordIndex, RdnaInstruction& instruction) { From 62ad3a68026eeb772642791baa358a999fdcaf2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 15:05:14 +0200 Subject: [PATCH 119/164] test(shader): cover the bitwise and unsigned image atomics Co-Authored-By: Claude --- .../tests/execution/ImageAtomicsBitwise.cpp | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsBitwise.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsBitwise.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsBitwise.cpp new file mode 100644 index 000000000..59fbc3c05 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsBitwise.cpp @@ -0,0 +1,142 @@ +#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 + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Operations = 5; +constexpr std::uint32_t TextureHeight = 8; +constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t Type2D = 9; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; +alignas(4096) std::array Texels{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e100300, 0x7e120280, 0x7e140304, + 0xf03c0108, 0x00020a08, 0x7e160305, 0xf0602108, 0x00020b08, 0x7e180280, 0xf0442108, 0x00020c08, + 0xbf8c3f70, 0x7e28030b, 0x7e2a030c, 0x7e120281, 0x7e140304, 0xf03c0108, 0x00020a08, 0x7e160305, + 0xf0642108, 0x00020b08, 0x7e180280, 0xf0442108, 0x00020c08, 0xbf8c3f70, 0x7e2c030b, 0x7e2e030c, + 0x7e120282, 0x7e140304, 0xf03c0108, 0x00020a08, 0x7e160305, 0xf0682108, 0x00020b08, 0x7e180280, + 0xf0442108, 0x00020c08, 0xbf8c3f70, 0x7e30030b, 0x7e32030c, 0x7e120283, 0x7e140304, 0xf03c0108, + 0x00020a08, 0x7e160305, 0xf0542108, 0x00020b08, 0x7e180280, 0xf0442108, 0x00020c08, 0xbf8c3f70, + 0x7e34030b, 0x7e36030c, 0x7e120284, 0x7e140304, 0xf03c0108, 0x00020a08, 0x7e160305, 0xf05c2108, + 0x00020b08, 0x7e180280, 0xf0442108, 0x00020c08, 0xbf8c3f70, 0x7e38030b, 0x7e3a030c, 0xe0701000, + 0x80011403, 0xe0701004, 0x80011503, 0xe0701008, 0x80011603, 0xe070100c, 0x80011703, 0xe0701010, + 0x80011803, 0xe0701014, 0x80011903, 0xe0701018, 0x80011a03, 0xe070101c, 0x80011b03, 0xe0701020, + 0x80011c03, 0xe0701024, 0x80011d03, 0xbf810000, +}; + +constexpr std::array, 14> Edges{{ + {0x0u, 0x0u}, {0x0u, 0xffffffffu}, {0xffffffffu, 0x0u}, {0xffffffffu, 0xffffffffu}, {0x7fffffffu, 0x80000000u}, {0x80000000u, 0x7fffffffu}, + {0xffffffffu, 0x1u}, {0x1u, 0xffffffffu}, {0xaaaaaaaau, 0x55555555u}, {0xf0f0f0f0u, 0xff00ff00u}, {0x5u, 0x5u}, {0x4u, 0x5u}, + {0x6u, 0x5u}, {0x80000000u, 0x80000000u}, +}}; + +void FillInput() { + std::uint64_t state = 0x9e3779b97f4a7c15ull; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + auto* words = &Input[tid * Inputs]; + for (std::uint32_t j = 0; j < 2u; ++j) { + state = state * 6364136223846793005ull + 1442695040888963407ull; + words[j] = static_cast(state >> 32u); + } + if (tid < Edges.size()) { + words[0] = Edges[tid][0]; + words[1] = Edges[tid][1]; + } + } +} + +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::array TextureDescriptor(const void* data, std::uint32_t width, std::uint32_t height) { + const auto address = static_cast(reinterpret_cast(data)); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((width - 1u) & 3u) << 30u), + ((width - 1u) >> 2u) | ((height - 1u) << 14u), + 0xfacu | (Type2D << 28u), + 0u, 0u, 0u, 0u, + }; +} + +void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + Output.fill(0xdeadbeefu); + Texels.fill(0xdeadbeefu); + std::vector userData(16, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + const auto texture = TextureDescriptor(Texels.data(), Threads, TextureHeight); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + std::copy(texture.begin(), texture.end(), userData.begin() + 8); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + 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(); +} + +void Check(const char* run) { + constexpr std::array names{"image_atomic_and", "image_atomic_or", "image_atomic_xor", "image_atomic_umin", "image_atomic_umax"}; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto* in = &Input[tid * Inputs]; + const std::uint32_t a = in[0]; + const std::uint32_t b = in[1]; + const std::array expected{a & b, a | b, a ^ b, std::min(a, b), std::max(a, b)}; + for (std::uint32_t j = 0; j < Operations; ++j) { + const std::uint32_t returned = Output[tid * Results + 2u * j]; + const std::uint32_t stored = Output[tid * Results + 2u * j + 1u]; + const auto where = std::string(names[j]) + " " + run + ": thread " + std::to_string(tid); + Require(returned == a, where + " returned " + std::to_string(returned) + ", expected " + std::to_string(a)); + Require(stored == expected[j], where + " stored " + std::to_string(stored) + ", expected " + std::to_string(expected[j])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + FillInput(); + Run(*device, 32, device->Target()); + Check("wave32"); + Run(*device, 64, device->Target()); + Check("wave64"); + Run(*device, 64, device->ComputeTarget(32)); + Check("wave64 split"); + std::puts("image atomics bitwise tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} From a93faff19f21ead39f5a16ba47c07a42cdaacddf Mon Sep 17 00:00:00 2001 From: Nahuel Genchi <65604505+NahuelGenchi@users.noreply.github.com> Date: Mon, 5 Oct 2026 10:32:39 -0300 Subject: [PATCH 120/164] feat(shader): implement global_atomic_csub GLOBAL opcode 0x34 subtracts the data register from a global dword and clamps the result to zero, returning the previous value. It only exists in the global segment, so the flat form throws in the decoder, and the form without glc throws like buffer_atomic_csub does. The clamped subtract is now a shared helper used by both opcodes. --- .../tests/execution/GlobalAtomicCsub.cpp | 249 ++++++++++++++++++ .../IntermediateRepresentation/IrOpcode.hpp | 1 + .../src/IrOpcode.cpp | 2 + .../include/RdnaDecoder/RdnaOpcode.hpp | 1 + .../RdnaDecoder/src/RdnaMemoryOpDecoder.cpp | 5 + .../SpirvBackend/SpirvEmitterHelpers.hpp | 1 + .../SpirvBackend/src/SpirvFlowEmitter.cpp | 1 + .../SpirvMemory/SpirvMemoryInstructions.cpp | 15 +- .../MemoryInstructionDispatch.cpp | 5 + 9 files changed, 275 insertions(+), 5 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicCsub.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicCsub.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicCsub.cpp new file mode 100644 index 000000000..7c84e3e8c --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicCsub.cpp @@ -0,0 +1,249 @@ +#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 +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 4; +constexpr std::uint32_t Written = 3; +constexpr std::uint32_t SharedOffset = Threads * 4u; +constexpr std::uint32_t Untouched = 0xdeadbeefu; +constexpr std::uint32_t SharedPartial = 0x800186a0u; +constexpr std::uint32_t SharedExhausted = 1000u; +constexpr std::size_t BlockBytes = 4096; +constexpr std::uint8_t Fill = 0xcd; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060082, 0xe0301000, 0x80000a01, 0xe0301004, 0x80000b01, 0xe0301008, 0x80000c01, + 0x7e0e02ff, 0x00000100, 0xbf8c3f70, 0xdcd18000, 0x14080a03, 0xdcd18000, 0x15080b07, 0xdcd18004, + 0x16080c07, 0xbf8c3f70, 0xe0701000, 0x80011401, 0xe0701004, 0x80011501, 0xe0701008, 0x80011601, + 0xbf810000, +}; + +alignas(256) constexpr std::array NoReturnCode{ + 0xdcd08000, 0x00080a03, 0xbf810000, +}; + +alignas(256) constexpr std::array FlatSegmentCode{ + 0xdcd10000, 0x147f0a03, 0xbf810000, +}; + +struct Row { + std::uint32_t memory; + std::uint32_t subtrahend; +}; + +constexpr std::array Edges{{ + {0u, 0u}, {0u, 1u}, {1u, 0u}, {1u, 1u}, + {5u, 7u}, {7u, 5u}, {0xffffffffu, 1u}, {1u, 0xffffffffu}, + {0x80000000u, 0x7fffffffu}, {0x7fffffffu, 0x80000000u}, {0xffffffffu, 0xffffffffu}, {0x80000000u, 0x80000000u}, + {0x80000001u, 0x80000000u}, {0u, 0xffffffffu}, {0xffffffffu, 0u}, {0x12345678u, 0x12345679u}, +}}; + +Row RowOf(std::uint32_t tid) { + if (tid < Edges.size()) return Edges[tid]; + const std::uint32_t memory = (tid + 1u) * 0x9e3779b9u; + const std::uint32_t subtrahend = (tid + 1u) * 0x85ebca6bu; + return {memory, (tid & 3u) == 0u ? subtrahend >> 8u : subtrahend}; +} + +std::uint32_t SharedPartialStep(std::uint32_t tid) { return tid + 1u; } +std::uint32_t SharedExhaustedStep(std::uint32_t tid) { return tid + 100u; } + +std::uint32_t SubtractClamped(std::uint32_t memory, std::uint32_t subtrahend) { + return memory >= subtrahend ? memory - subtrahend : 0u; +} + +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 atomic csub: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, true); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +void Put(std::vector& image, std::uint32_t offset, std::uint32_t value) { + for (std::uint32_t byte = 0; byte < 4u; ++byte) image.at(offset + byte) = static_cast(value >> (byte * 8u)); +} + +std::vector Initial() { + std::vector image(BlockBytes, Fill); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, tid * 4u, RowOf(tid).memory); + } + Put(image, SharedOffset, SharedPartial); + Put(image, SharedOffset + 4u, SharedExhausted); + return image; +} + +std::vector Expected() { + auto image = Initial(); + std::uint32_t partial = SharedPartial; + std::uint32_t exhausted = SharedExhausted; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const Row row = RowOf(tid); + Put(image, tid * 4u, SubtractClamped(row.memory, row.subtrahend)); + partial = SubtractClamped(partial, SharedPartialStep(tid)); + exhausted = SubtractClamped(exhausted, SharedExhaustedStep(tid)); + } + Require(partial != 0u && exhausted == 0u, "global atomic csub: the shared dwords do not cover both outcomes"); + Put(image, SharedOffset, partial); + Put(image, SharedOffset + 4u, exhausted); + return image; +} + +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; +} + +std::vector UserData(const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 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); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + return userData; +} + +ShaderRecompiler::RecompileResult Recompile(const AgcDriver::VulkanDevice& device, std::span code, std::uint32_t waveSize, std::span userData) { + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 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, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + return ShaderRecompiler::Recompile(request); +} + +void CheckChain(const std::string& where, std::uint32_t column, std::uint32_t initial, std::uint32_t (*step)(std::uint32_t)) { + std::vector> order; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + order.emplace_back(Output[tid * Results + column], tid); + } + std::sort(order.begin(), order.end(), [](const auto& a, const auto& b) { return a.first > b.first; }); + std::uint32_t running = initial; + for (const auto& [returned, tid] : order) { + Require(returned == running, where + " returned " + Hex(returned) + " to thread " + std::to_string(tid) + ", expected " + Hex(running)); + running = SubtractClamped(running, step(tid)); + } +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + Input.fill(0u); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Input[tid * Inputs] = RowOf(tid).subtrahend; + Input[tid * Inputs + 1u] = SharedPartialStep(tid); + Input[tid * Inputs + 2u] = SharedExhaustedStep(tid); + } + Output.fill(Untouched); + const auto userData = UserData(guest.Data()); + const std::span code(Code); + const auto result = Recompile(device, code, waveSize, userData); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + + const auto wave = "global atomic csub: wave" + std::to_string(waveSize) + " "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const Row row = RowOf(tid); + const auto returned = Output[tid * Results]; + Require(returned == row.memory, wave + "thread " + std::to_string(tid) + " returned " + Hex(returned) + ", expected " + Hex(row.memory)); + for (std::uint32_t j = Written; j < Results; ++j) { + Require(Output[tid * Results + j] == Untouched, wave + "thread " + std::to_string(tid) + " result " + std::to_string(j) + " was written"); + } + } + CheckChain(wave + "dword shared by all lanes", 1u, SharedPartial, SharedPartialStep); + CheckChain(wave + "dword exhausted by all lanes", 2u, SharedExhausted, SharedExhaustedStep); + const auto expected = Expected(); + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + const auto actual = guest.Data()[offset]; + Require(actual == expected[offset], wave + "byte " + std::to_string(offset) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[offset])); + } +} + +void CheckRejected(const AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code, const std::string& reason) { + const auto userData = UserData(guest.Data()); + std::string failure; + try { + static_cast(Recompile(device, code, 32, userData)); + } catch (const std::exception& error) { + failure = error.what(); + } + Require(failure.find(reason) != std::string::npos, "global atomic csub: expected '" + reason + "', got '" + failure + "'"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + Run(*device, guest, 32); + Run(*device, guest, 64); + CheckRejected(*device, guest, NoReturnCode, "global_atomic_csub without glc is not supported"); + CheckRejected(*device, guest, FlatSegmentCode, "global_atomic_csub is available only in the global segment"); + std::puts("global atomic csub tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp index b1fac1488..805a366f9 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp @@ -303,6 +303,7 @@ enum class IrOpcode : std::uint16_t { AddressAtomicXor32, AddressAtomicInc32, AddressAtomicDec32, + AddressAtomicUSubSat32, AddressAtomicSwap64, AddressAtomicCmpSwap64, AddressAtomicIAdd64, diff --git a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp index eb7d22e56..7057ea930 100644 --- a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp +++ b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp @@ -344,6 +344,7 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("AddressAtomicXor32", U32, AddressResource, U32, U32, U32, U1), makeMeta("AddressAtomicInc32", U32, AddressResource, U32, U32, U32, U1), makeMeta("AddressAtomicDec32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicUSubSat32", U32, AddressResource, U32, U32, U32, U1), makeMeta("AddressAtomicSwap64", U64, AddressResource, U32, U32, U64, U1), makeMeta("AddressAtomicCmpSwap64", U64, AddressResource, U32, U32, U64, U64, U1), makeMeta("AddressAtomicIAdd64", U64, AddressResource, U32, U32, U64, U1), @@ -744,6 +745,7 @@ AddressOpcodeInfo AddressOpcodeInfoOf(IrOpcode opcode) { case IrOpcode::AddressAtomicXor32: case IrOpcode::AddressAtomicInc32: case IrOpcode::AddressAtomicDec32: + case IrOpcode::AddressAtomicUSubSat32: case IrOpcode::AddressAtomicFCmpSwap32: case IrOpcode::AddressAtomicFMin32: case IrOpcode::AddressAtomicFMax32: diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp index 14cc7e3a1..23d5277fa 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, + GlobalAtomicCsub, GlobalStoreDwordAddtid, DsAddU32, DsAddRtnU32, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp index 53e6a4605..2544e10eb 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp @@ -190,6 +190,7 @@ constexpr MemoryOpcodeInfo flatOpcodes[] = { {0x31u, RdnaOpcode::FlatAtomicCmpswap, 1, 32, false, false, false}, {0x32u, RdnaOpcode::FlatAtomicAdd, 1, 32, false, false, false}, {0x33u, RdnaOpcode::FlatAtomicSub, 1, 32, false, false, false}, + {0x34u, RdnaOpcode::GlobalAtomicCsub, 1, 32, false, false, false}, {0x35u, RdnaOpcode::FlatAtomicSmin, 1, 32, false, false, false}, {0x36u, RdnaOpcode::FlatAtomicUmin, 1, 32, false, false, false}, {0x37u, RdnaOpcode::FlatAtomicSmax, 1, 32, false, false, false}, @@ -602,6 +603,7 @@ bool isFlatAtomicOpcode(RdnaOpcode opcode) { case RdnaOpcode::FlatAtomicFmaxX2: case RdnaOpcode::FlatAtomicIncX2: case RdnaOpcode::FlatAtomicDecX2: + case RdnaOpcode::GlobalAtomicCsub: return true; default: return false; } @@ -820,6 +822,9 @@ RdnaInstruction DecodeRdnaFlat(std::uint32_t programCounter, std::span Date: Mon, 5 Oct 2026 10:34:45 -0300 Subject: [PATCH 121/164] docs: record the fused assumption of v_fma_legacy_f32 in TechnicalDebt --- docs/dev/TechnicalDebt.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 7468ef292..ff50930ec 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -84,6 +84,7 @@ Throughout the project, every function at every stage either **does exactly what - [Shader recompilation](../../core/shader/recompiler/Recompiler.cpp) currently occurs right before it was transferred to Vulkan with caching, but should be moved to the [relinker](../../core/relinker/main.cpp) stage. For this purpose, [shader/recompiler](../../core/shader/recompiler) was written completely independently from [libs/prx](../../core/libs/prx). - [v_fma_f64](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp) is fused exactly only for normal inputs: with a subnormal input it is a separate multiply and add, which rounds twice. - [v_mullit_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follows the hardware measured with the MODE register at IEEE=1 and denormals kept (a signaling NaN src0 is quieted, a positive denormal src2 counts as positive): the recompiler does not read MODE, so other IEEE/denormal settings are not modelled. +- [v_fma_legacy_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (VOP3 0x140) is assumed to be fused, which has not been measured on hardware. LLVM decodes this encoding as `v_mad_legacy_f32` for gfx1013, and `v_mac_legacy_f32` (VOP2 0x06) was measured to round its product before the add, so 0x140 may do the same. - The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability. - [libSceJpegEnc](../../core/libs/prx/libSceJpegEnc/Export.cpp) encodes 4:2:2 sampling requests with 4:2:0 chroma subsampling, and grayscale requests as a 3-component JPEG with neutral chroma instead of a single-component one: the [JPEG encoder](../../core/Decoder/Jpeg/src/Jpeg.cpp) (stb) only produces 3-component 4:2:0 and 4:4:4 images. - [v_rcp, v_rsq, v_sqrt, v_log, v_exp, v_sin and v_cos](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (f32 and f16) use the driver's approximation for finite results, not the console's: the low bits can differ. Only NaN, invalid, zero and infinite results and the exact sin/cos zeros are chosen on the bits. v_sin_f32/v_cos_f32 with inputs beyond ±256 cycles were not compared with the console. From 4066e6dd29df6d9acb959a81d115cd1c43ec10c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 16:54:53 +0200 Subject: [PATCH 122/164] test(shader): keep the flat cache bits test within CI's time limit Co-Authored-By: Claude --- .../tests/execution/FlatCacheBits.cpp | 91 ++++++++----------- 1 file changed, 40 insertions(+), 51 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatCacheBits.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatCacheBits.cpp index 9b1650f3f..717f9f028 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/FlatCacheBits.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatCacheBits.cpp @@ -23,62 +23,58 @@ namespace { using AgcDriver::Graphics::Require; using ShaderRecompiler::ShaderStage; -constexpr std::uint32_t Threads = 64; -constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Results = 4; constexpr std::uint32_t Sentinel = 0x05e471e1; constexpr std::size_t BlockBytes = 65536; constexpr std::uint8_t Fill = 0xcd; alignas(256) std::array Output{}; -alignas(256) constexpr std::array FlatCacheBitsCode{ - 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0xd70f6a1e, 0x02020208, 0x7e3e0209, - 0x503e3e80, 0xdc318000, 0x0a080001, 0xdc328004, 0x0b080001, 0xdc309008, 0x0c080001, 0xdc33900c, - 0x0d080001, 0xdc359004, 0x0e080001, 0xdc228001, 0x10080001, 0xdc259007, 0x11080001, 0xbf8c3f70, - 0xdc739000, 0x00080501, 0xdc628006, 0x00080601, 0xbf8c3f70, 0xdc359000, 0x12080001, 0xdccb9008, - 0x14080701, 0xdcce900c, 0x00080401, 0xbf8c3f70, 0xdc358008, 0x15080001, 0xdc311000, 0x177d001e, - 0xdc730000, 0x007d071e, 0xbf8c0070, 0xdcef1000, 0x187d061e, 0xbf8c0070, 0xdc320000, 0x197d001e, - 0xbf8c0070, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, 0x80011202, 0xe0781030, - 0x80011602, 0xbf810000 +alignas(256) constexpr std::array FlatCacheBitsCode{ + 0x34020084, 0x34040084, 0xd70f6a1e, 0x02020208, 0x7e3e0209, 0x503e3e80, 0xd70f6a1e, 0x02023c8c, + 0x503e3e80, 0xdc379000, 0x0a080001, 0xdc331000, 0x0d7d001e, 0xbf8c0070, 0xdc739004, 0x00080d01, + 0xdccb9008, 0x0c080a01, 0xdc731000, 0x007d0b1e, 0xbf8c0070, 0xe0781000, 0x80010a02, 0xbf810000 }; struct Row { std::array memory; std::array results; + std::array final; }; constexpr std::array Rows{{ - {{0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x0000007f, 0x00000080, 0x7fffffff, 0x0000007f, 0x00000080, 0x00000000, 0x00000000, 0x0000007f, 0x0080007f, 0x00000080, 0x8000007f, 0x7ffffffe, 0x0000007f, 0x7fffffff, 0x7fffff7f}}, - {{0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01, 0x000000ff, 0x80000000, 0x00000000, 0x00000000, 0x000000ff, 0x000000ff, 0x80000000, 0x00ff7f01, 0x80ff7e82, 0x000000ff, 0x80ff7f01, 0x00ff7f01}}, - {{0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff, 0x80000000, 0xdeadbeef, 0x00000000, 0xffffff80, 0x80000000, 0x80ef0000, 0xdeadbeef, 0xdeadbfee, 0x0000007f, 0x80000000, 0x000000ff, 0xdeadbe10}}, - {{0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef, 0x12345678, 0x00000001, 0x00000000, 0x00000012, 0x12345678, 0x12015678, 0x00000001, 0xdeadbef0, 0xdeadbdf0, 0x12345678, 0xdeadbeef, 0xdeadbeee}}, - {{0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080, 0x80ff7f01, 0x000000ff, 0x000000ff, 0xffffff80, 0x80ff7f01, 0x80ff7f01, 0x000000ff, 0x0000017f, 0x80000081, 0x80ff7f01, 0x00000080, 0x0000007f}}, - {{0x80000000, 0x00000001, 0xffffffff, 0x12345678}, {0x80000000, 0x00000001, 0xffffffff, 0x12345678, 0x00000001, 0xffffffff, 0x00000000, 0x00000000, 0x00000001, 0x00ff0001, 0xffffffff, 0x12345677, 0x92345678, 0x00000001, 0x12345678, 0xedcba987}}, - {{0xffffffff, 0x00000080, 0x80ff7f01, 0x0000007f}, {0xffffffff, 0x00000080, 0x80ff7f01, 0x0000007f, 0x00000080, 0x80ff7f01, 0x000000ff, 0x00000000, 0x00000080, 0x00010080, 0x80ff7f01, 0x80ff7f80, 0x00000080, 0x00000080, 0x0000007f, 0x80ff7f7e}}, - {{0x12345678, 0x7fffffff, 0x0000007f, 0xffffffff}, {0x12345678, 0x7fffffff, 0x0000007f, 0xffffffff, 0x7fffffff, 0x0000007f, 0x00000056, 0x0000007f, 0x7fffffff, 0x7f7fffff, 0x0000007f, 0x0000007e, 0xedcba987, 0x7fffffff, 0xffffffff, 0xffffff80}}, - {{0xdeadbeef, 0xffffffff, 0x7fffffff, 0x00000001}, {0xdeadbeef, 0xffffffff, 0x7fffffff, 0x00000001, 0xffffffff, 0x7fffffff, 0x000000be, 0xffffffff, 0xffffffff, 0xffffffff, 0x7fffffff, 0x80000000, 0x21524112, 0xffffffff, 0x00000001, 0x7ffffffe}}, - {{0x80ff7f01, 0xdeadbeef, 0x12345678, 0x80000000}, {0x80ff7f01, 0xdeadbeef, 0x12345678, 0x80000000, 0xdeadbeef, 0x12345678, 0x0000007f, 0xffffffde, 0xdeadbeef, 0xde78beef, 0x12345678, 0x92345678, 0xff0080ff, 0xdeadbeef, 0x80000000, 0x92345678}}, - {{0x00000000, 0x00000000, 0x00000000, 0x00000000}, {0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}}, - {{0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x0000007f, 0x00000080, 0x7fffffff, 0x0000007f, 0x00000080, 0x00000000, 0x00000000, 0x0000007f, 0x0080007f, 0x00000080, 0x8000007f, 0x7ffffffe, 0x0000007f, 0x7fffffff, 0x7fffff7f}}, - {{0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01, 0x000000ff, 0x80000000, 0x00000000, 0x00000000, 0x000000ff, 0x000000ff, 0x80000000, 0x00ff7f01, 0x80ff7e82, 0x000000ff, 0x80ff7f01, 0x00ff7f01}}, - {{0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff, 0x80000000, 0xdeadbeef, 0x00000000, 0xffffff80, 0x80000000, 0x80ef0000, 0xdeadbeef, 0xdeadbfee, 0x0000007f, 0x80000000, 0x000000ff, 0xdeadbe10}}, - {{0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef, 0x12345678, 0x00000001, 0x00000000, 0x00000012, 0x12345678, 0x12015678, 0x00000001, 0xdeadbef0, 0xdeadbdf0, 0x12345678, 0xdeadbeef, 0xdeadbeee}}, - {{0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080, 0x80ff7f01, 0x000000ff, 0x000000ff, 0xffffff80, 0x80ff7f01, 0x80ff7f01, 0x000000ff, 0x0000017f, 0x80000081, 0x80ff7f01, 0x00000080, 0x0000007f}}, - {{0x2265b1f5, 0x91b7584a, 0xd8f16adf, 0xcd613e30}, {0x2265b1f5, 0x91b7584a, 0xd8f16adf, 0xcd613e30, 0x91b7584a, 0xd8f16adf, 0x000000b1, 0xffffff91, 0x91b7584a, 0x91df584a, 0xd8f16adf, 0xa652a90f, 0xaafb8c3b, 0x91b7584a, 0xcd613e30, 0x159054ef}}, - {{0xc386bbc4, 0x1027c4d1, 0x414c343c, 0x1e2feb89}, {0xc386bbc4, 0x1027c4d1, 0x414c343c, 0x1e2feb89, 0x1027c4d1, 0x414c343c, 0x000000bb, 0x00000010, 0x1027c4d1, 0x103cc4d1, 0x414c343c, 0x5f7c1fc5, 0x5aa92fc5, 0x1027c4d1, 0x1e2feb89, 0x5f63dfb5}}, - {{0x7ed4d57b, 0xc2ce6f44, 0x7311d8a3, 0x78e51061}, {0x7ed4d57b, 0xc2ce6f44, 0x7311d8a3, 0x78e51061, 0xc2ce6f44, 0x7311d8a3, 0x000000d5, 0xffffffc2, 0xc2ce6f44, 0xc2a36f44, 0x7311d8a3, 0xebf6e904, 0xfa103ae6, 0xc2ce6f44, 0x78e51061, 0x0bf4c8c2}}, - {{0xa6cecc1b, 0x612e7696, 0xc9e9c616, 0x35bf992d}, {0xa6cecc1b, 0x612e7696, 0xc9e9c616, 0x35bf992d, 0x612e7696, 0xc9e9c616, 0x000000cc, 0x00000061, 0x612e7696, 0x61167696, 0xc9e9c616, 0xffa95f43, 0x8ef0cd12, 0x612e7696, 0x35bf992d, 0xfc565f3b}}, - {{0x18072e8c, 0x7ce42c82, 0x0741c7a8, 0xe4b06ce6}, {0x18072e8c, 0x7ce42c82, 0x0741c7a8, 0xe4b06ce6, 0x7ce42c82, 0x0741c7a8, 0x0000002e, 0x0000007c, 0x7ce42c82, 0x7ca82c82, 0x0741c7a8, 0xebf2348e, 0xcca93e5a, 0x7ce42c82, 0xe4b06ce6, 0xe3f1ab4e}}, - {{0xd5f4b3b2, 0x63ca828d, 0x6ec9d286, 0x9b810e76}, {0xd5f4b3b2, 0x63ca828d, 0x6ec9d286, 0x9b810e76, 0x63ca828d, 0x6ec9d286, 0x000000b3, 0x00000063, 0x63ca828d, 0x6386828d, 0x6ec9d286, 0x0a4ae0fc, 0xc58c5ac4, 0x63ca828d, 0x9b810e76, 0xf548dcf0}}, - {{0xc324c985, 0xc4647159, 0x008a05a6, 0xb2221a58}, {0xc324c985, 0xc4647159, 0x008a05a6, 0xb2221a58, 0xc4647159, 0x008a05a6, 0x000000c9, 0xffffffc4, 0xc4647159, 0xc4a67159, 0x008a05a6, 0xb2ac1ffe, 0xeefd50d3, 0xc4647159, 0xb2221a58, 0xb2a81ffe}}, - {{0x7204e52d, 0x442e3d43, 0xb8b6d8fe, 0xcd447e35}, {0x7204e52d, 0x442e3d43, 0xb8b6d8fe, 0xcd447e35, 0x442e3d43, 0xb8b6d8fe, 0x000000e5, 0x00000044, 0x442e3d43, 0x44fe3d43, 0xb8b6d8fe, 0x85fb5733, 0x5b3f9908, 0x442e3d43, 0xcd447e35, 0x75f2a6cb}}, - {{0x3a902931, 0x9755d4c1, 0xf1fd42a2, 0x1a2b8f1f}, {0x3a902931, 0x9755d4c1, 0xf1fd42a2, 0x1a2b8f1f, 0x9755d4c1, 0xf1fd42a2, 0x00000029, 0xffffff97, 0x9755d4c1, 0x97a2d4c1, 0xf1fd42a2, 0x0c28d1c1, 0xdf9b65ee, 0x9755d4c1, 0x1a2b8f1f, 0xebd6cdbd}}, - {{0xe6c3f339, 0x51431193, 0x07d4bedc, 0x05b6e6e3}, {0xe6c3f339, 0x51431193, 0x07d4bedc, 0x05b6e6e3, 0x51431193, 0x07d4bedc, 0x000000f3, 0x00000051, 0x51431193, 0x51dc1193, 0x07d4bedc, 0x0d8ba5bf, 0x1ef2f3aa, 0x51431193, 0x05b6e6e3, 0x0262583f}}, - {{0x06839eb9, 0xa648a7dd, 0x8a9a021e, 0x025b413f}, {0x06839eb9, 0xa648a7dd, 0x8a9a021e, 0x025b413f, 0xa648a7dd, 0x8a9a021e, 0x0000009e, 0xffffffa6, 0xa648a7dd, 0xa61ea7dd, 0x8a9a021e, 0x8cf5435d, 0xfbd7a286, 0xa648a7dd, 0x025b413f, 0x88c14321}}, - {{0xf06c144a, 0xe1988ad9, 0x619699cf, 0xafbd67f9}, {0xf06c144a, 0xe1988ad9, 0x619699cf, 0xafbd67f9, 0xe1988ad9, 0x619699cf, 0x00000014, 0xffffffe1, 0xe1988ad9, 0xe1cf8ad9, 0x619699cf, 0x115401c8, 0xbf5153af, 0xe1988ad9, 0xafbd67f9, 0xce2bfe36}}, - {{0x37730edf, 0xf8130c42, 0x6c0fd4f5, 0xb9d179e0}, {0x37730edf, 0xf8130c42, 0x6c0fd4f5, 0xb9d179e0, 0xf8130c42, 0x6c0fd4f5, 0x0000000e, 0xfffffff8, 0xf8130c42, 0xf8f50c42, 0x6c0fd4f5, 0x25e14ed5, 0x825e6b01, 0xf8130c42, 0xb9d179e0, 0xd5dead15}}, - {{0x076f3787, 0x8712b8bc, 0x38c0c8fd, 0xc381e88f}, {0x076f3787, 0x8712b8bc, 0x38c0c8fd, 0xc381e88f, 0x8712b8bc, 0x38c0c8fd, 0x00000037, 0xffffff87, 0x8712b8bc, 0x87fdb8bc, 0x38c0c8fd, 0xfc42b18c, 0xbc12b108, 0x8712b8bc, 0xc381e88f, 0xfb412072}}, - {{0x701966a0, 0xf06d3fef, 0x7eed8d14, 0x8d88348a}, {0x701966a0, 0xf06d3fef, 0x7eed8d14, 0x8d88348a, 0xf06d3fef, 0x7eed8d14, 0x00000066, 0xfffffff0, 0xf06d3fef, 0xf0143fef, 0x7eed8d14, 0x0c75c19e, 0x1d6ecdea, 0xf06d3fef, 0x8d88348a, 0xf365b99e}}, - {{0x3bab6c39, 0x587fd280, 0x3b1a11df, 0xad45f23d}, {0x3bab6c39, 0x587fd280, 0x3b1a11df, 0xad45f23d, 0x587fd280, 0x3b1a11df, 0x0000006c, 0x00000058, 0x587fd280, 0x58dfd280, 0x3b1a11df, 0xe860041c, 0x719a8604, 0x587fd280, 0xad45f23d, 0x965fe3e2}} + {{0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x7fffffff, 0x00000081, 0x0000007f}}, + {{0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x80ff7f01, 0x8000007f, 0x000000ff}}, + {{0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x000000ff, 0xdeadbf6f, 0x80000000}}, + {{0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0xdeadbeef, 0x00000100, 0x12345678}}, + {{0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x00000080, 0x800000fe, 0x80ff7f01}}, + {{0x80000000, 0x00000001, 0xffffffff, 0x12345678}, {0x80000000, 0x00000001, 0xffffffff, 0x12345678}, {0x80000000, 0x12345678, 0x7fffffff, 0x00000001}}, + {{0xffffffff, 0x00000080, 0x80ff7f01, 0x0000007f}, {0xffffffff, 0x00000080, 0x80ff7f01, 0x0000007f}, {0xffffffff, 0x0000007f, 0x80ff7f00, 0x00000080}}, + {{0x12345678, 0x7fffffff, 0x0000007f, 0xffffffff}, {0x12345678, 0x7fffffff, 0x0000007f, 0xffffffff}, {0x12345678, 0xffffffff, 0x123456f7, 0x7fffffff}}, + {{0xdeadbeef, 0xffffffff, 0x7fffffff, 0x00000001}, {0xdeadbeef, 0xffffffff, 0x7fffffff, 0x00000001}, {0xdeadbeef, 0x00000001, 0x5eadbeee, 0xffffffff}}, + {{0x80ff7f01, 0xdeadbeef, 0x12345678, 0x80000000}, {0x80ff7f01, 0xdeadbeef, 0x12345678, 0x80000000}, {0x80ff7f01, 0x80000000, 0x9333d579, 0xdeadbeef}}, + {{0x00000000, 0x00000000, 0x00000000, 0x00000000}, {0x00000000, 0x00000000, 0x00000000, 0x00000000}, {0x00000000, 0x00000000, 0x00000000, 0x00000000}}, + {{0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x7fffffff, 0x00000081, 0x0000007f}}, + {{0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x80ff7f01, 0x8000007f, 0x000000ff}}, + {{0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x000000ff, 0xdeadbf6f, 0x80000000}}, + {{0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0xdeadbeef, 0x00000100, 0x12345678}}, + {{0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x00000080, 0x800000fe, 0x80ff7f01}}, + {{0x2265b1f5, 0x91b7584a, 0xd8f16adf, 0xcd613e30}, {0x2265b1f5, 0x91b7584a, 0xd8f16adf, 0xcd613e30}, {0x2265b1f5, 0xcd613e30, 0xfb571cd4, 0x91b7584a}}, + {{0xc386bbc4, 0x1027c4d1, 0x414c343c, 0x1e2feb89}, {0xc386bbc4, 0x1027c4d1, 0x414c343c, 0x1e2feb89}, {0xc386bbc4, 0x1e2feb89, 0x04d2f000, 0x1027c4d1}}, + {{0x7ed4d57b, 0xc2ce6f44, 0x7311d8a3, 0x78e51061}, {0x7ed4d57b, 0xc2ce6f44, 0x7311d8a3, 0x78e51061}, {0x7ed4d57b, 0x78e51061, 0xf1e6ae1e, 0xc2ce6f44}}, + {{0xa6cecc1b, 0x612e7696, 0xc9e9c616, 0x35bf992d}, {0xa6cecc1b, 0x612e7696, 0xc9e9c616, 0x35bf992d}, {0xa6cecc1b, 0x35bf992d, 0x70b89231, 0x612e7696}}, + {{0x18072e8c, 0x7ce42c82, 0x0741c7a8, 0xe4b06ce6}, {0x18072e8c, 0x7ce42c82, 0x0741c7a8, 0xe4b06ce6}, {0x18072e8c, 0xe4b06ce6, 0x1f48f634, 0x7ce42c82}}, + {{0xd5f4b3b2, 0x63ca828d, 0x6ec9d286, 0x9b810e76}, {0xd5f4b3b2, 0x63ca828d, 0x6ec9d286, 0x9b810e76}, {0xd5f4b3b2, 0x9b810e76, 0x44be8638, 0x63ca828d}}, + {{0xc324c985, 0xc4647159, 0x008a05a6, 0xb2221a58}, {0xc324c985, 0xc4647159, 0x008a05a6, 0xb2221a58}, {0xc324c985, 0xb2221a58, 0xc3aecf2b, 0xc4647159}}, + {{0x7204e52d, 0x442e3d43, 0xb8b6d8fe, 0xcd447e35}, {0x7204e52d, 0x442e3d43, 0xb8b6d8fe, 0xcd447e35}, {0x7204e52d, 0xcd447e35, 0x2abbbe2b, 0x442e3d43}}, + {{0x3a902931, 0x9755d4c1, 0xf1fd42a2, 0x1a2b8f1f}, {0x3a902931, 0x9755d4c1, 0xf1fd42a2, 0x1a2b8f1f}, {0x3a902931, 0x1a2b8f1f, 0x2c8d6bd3, 0x9755d4c1}}, + {{0xe6c3f339, 0x51431193, 0x07d4bedc, 0x05b6e6e3}, {0xe6c3f339, 0x51431193, 0x07d4bedc, 0x05b6e6e3}, {0xe6c3f339, 0x05b6e6e3, 0xee98b215, 0x51431193}}, + {{0x06839eb9, 0xa648a7dd, 0x8a9a021e, 0x025b413f}, {0x06839eb9, 0xa648a7dd, 0x8a9a021e, 0x025b413f}, {0x06839eb9, 0x025b413f, 0x911da0d7, 0xa648a7dd}}, + {{0xf06c144a, 0xe1988ad9, 0x619699cf, 0xafbd67f9}, {0xf06c144a, 0xe1988ad9, 0x619699cf, 0xafbd67f9}, {0xf06c144a, 0xafbd67f9, 0x5202ae19, 0xe1988ad9}}, + {{0x37730edf, 0xf8130c42, 0x6c0fd4f5, 0xb9d179e0}, {0x37730edf, 0xf8130c42, 0x6c0fd4f5, 0xb9d179e0}, {0x37730edf, 0xb9d179e0, 0xa382e3d4, 0xf8130c42}}, + {{0x076f3787, 0x8712b8bc, 0x38c0c8fd, 0xc381e88f}, {0x076f3787, 0x8712b8bc, 0x38c0c8fd, 0xc381e88f}, {0x076f3787, 0xc381e88f, 0x40300084, 0x8712b8bc}}, + {{0x701966a0, 0xf06d3fef, 0x7eed8d14, 0x8d88348a}, {0x701966a0, 0xf06d3fef, 0x7eed8d14, 0x8d88348a}, {0x701966a0, 0x8d88348a, 0xef06f3b4, 0xf06d3fef}}, + {{0x3bab6c39, 0x587fd280, 0x3b1a11df, 0xad45f23d}, {0x3bab6c39, 0x587fd280, 0x3b1a11df, 0xad45f23d}, {0x3bab6c39, 0xad45f23d, 0x76c57e18, 0x587fd280}} }}; class GuestBlock { @@ -117,11 +113,6 @@ std::string Hex(std::uint32_t value) { return text; } -std::array FinalMemory(const Row& row) { - const auto [a, b, c, d] = row.memory; - return {d ^ c, (b & ~0xff0000u) | ((c & 0xffu) << 16u), c + d, d - a}; -} - void Dispatch(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const std::uint8_t* base) { const auto address = static_cast(reinterpret_cast(base)); std::vector userData(10, 0u); @@ -160,9 +151,8 @@ void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveS } std::array memory{}; std::memcpy(memory.data(), guest.Data() + tid * 16u, 16u); - const auto expected = FinalMemory(row); for (std::uint32_t dword = 0; dword < 4u; ++dword) { - Require(memory[dword] == expected[dword], wave + std::to_string(tid) + " memory dword " + std::to_string(dword) + " is " + Hex(memory[dword]) + ", expected " + Hex(expected[dword])); + Require(memory[dword] == row.final[dword], wave + std::to_string(tid) + " memory dword " + std::to_string(dword) + " is " + Hex(memory[dword]) + ", expected " + Hex(row.final[dword])); } } for (std::size_t offset = Threads * 16u; offset < BlockBytes; ++offset) { @@ -178,7 +168,6 @@ int main() { if (!device) return VulkanTestSkipped; GuestBlock guest; Run(*device, guest, 32); - Run(*device, guest, 64); std::puts("flat cache bits tests passed"); return 0; } catch (const std::exception& error) { From 23c2816d2167b488b15520ba07bd8e19cb24883e Mon Sep 17 00:00:00 2001 From: raghav314 Date: Mon, 5 Oct 2026 20:16:38 +0530 Subject: [PATCH 123/164] fix(ajm): release a batch when sceAjmBatchWait returns sceAjmBatchStart records the batch id it hands out and sceAjmBatchWait removes it. Waiting on a batch id that was never issued or was already waited on returns INVALID_BATCH instead of 0, as in shadPS4. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAjm.native/src/Ajm.cpp | 16 ++++++++++++++-- core/libs/tests/GuestAjm.cpp | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceAjm.native/src/Ajm.cpp b/core/libs/prx/libSceAjm.native/src/Ajm.cpp index 50cbee420..3b30d8d53 100644 --- a/core/libs/prx/libSceAjm.native/src/Ajm.cpp +++ b/core/libs/prx/libSceAjm.native/src/Ajm.cpp @@ -16,6 +16,7 @@ extern "C" { #include #include #include +#include #include #include #include @@ -26,6 +27,7 @@ extern "C" { namespace { constexpr int SCE_AJM_ERROR_INVALID_INSTANCE = static_cast(0x80930003); +constexpr int SCE_AJM_ERROR_INVALID_BATCH = static_cast(0x80930004); constexpr int SCE_AJM_ERROR_INVALID_PARAMETER = static_cast(0x80930005); constexpr int SCE_AJM_ERROR_OUT_OF_RESOURCES = static_cast(0x80930007); @@ -127,6 +129,8 @@ std::map> g_instances; std::atomic g_nextContext{1}; std::atomic g_nextInstance{1}; std::atomic g_nextBatch{1}; +std::mutex g_batchLock; +std::set g_batches; enum class JobKind : std::uint32_t { Initialize = 1, @@ -985,14 +989,22 @@ int APS5_VABI sceAjmBatchStart(uint32_t context, const AjmBatchInfo* info, int p cursor += record.bytes; } if (error) std::memset(error, 0, sizeof(*error)); - *batch = g_nextBatch.fetch_add(1, std::memory_order_relaxed); + const auto id = g_nextBatch.fetch_add(1, std::memory_order_relaxed); + { + std::lock_guard lock(g_batchLock); + g_batches.insert(id); + } + *batch = id; return 0; } int APS5_VABI sceAjmBatchWait(uint32_t context, uint32_t batch, uint32_t timeout, AjmBatchError* error) { (void)context; - (void)batch; (void)timeout; + { + std::lock_guard lock(g_batchLock); + if (g_batches.erase(batch) == 0) return SCE_AJM_ERROR_INVALID_BATCH; + } if (error) std::memset(error, 0, sizeof(*error)); return 0; } diff --git a/core/libs/tests/GuestAjm.cpp b/core/libs/tests/GuestAjm.cpp index 8d8504257..71524e885 100644 --- a/core/libs/tests/GuestAjm.cpp +++ b/core/libs/tests/GuestAjm.cpp @@ -321,6 +321,24 @@ void TestCodecInfo(std::uint32_t context) { Require(sceAjmInstanceDestroy(context, instance) == 0); } +void TestBatchWaitRelease(std::uint32_t context) { + constexpr int invalidBatch = static_cast(0x80930004); + std::vector batch(64); + AjmBatchInfo info{}; + AjmBatchError error{}; + std::uint32_t first = 0; + std::uint32_t second = 0; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchStart(context, &info, 0, &error, &first) == 0); + Require(sceAjmBatchStart(context, &info, 0, &error, &second) == 0 && second != first); + Require(sceAjmBatchWait(context, second, 0, &error) == 0); + Require(sceAjmBatchWait(context, first, 0, &error) == 0); + Require(sceAjmBatchWait(context, first, 0, &error) == invalidBatch); + Require(sceAjmBatchWait(context, second, 0, &error) == invalidBatch); + Require(sceAjmBatchWait(context, 0, 0, &error) == invalidBatch); + Require(sceAjmBatchWait(context, second + 1, 0, &error) == invalidBatch); +} + } int main() { @@ -343,5 +361,6 @@ int main() { TestDecodeSingle(context); TestGaplessDecode(context); TestCodecInfo(context); + TestBatchWaitRelease(context); Require(sceAjmFinalize(context) == 0); } From 14a00e6963bd5f33c94d888c55b8302542d3ab4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 17:19:12 +0200 Subject: [PATCH 124/164] test(shader): split the global atomics test to keep it within CI's time limit Co-Authored-By: Claude --- .../tests/execution/GlobalAtomics.cpp | 246 +---------- .../tests/execution/GlobalAtomics64.cpp | 400 ++++++++++++++++++ .../tests/execution/GlobalAtomicsLanes.cpp | 251 +++++++++++ 3 files changed, 655 insertions(+), 242 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics64.cpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsLanes.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics.cpp index 736dd9cdc..3409e7768 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics.cpp @@ -26,7 +26,7 @@ using ShaderRecompiler::ShaderStage; constexpr std::uint32_t Threads = 64; constexpr std::uint32_t RowsPerOp = 16; -constexpr std::uint32_t OpCount = 24; +constexpr std::uint32_t OpCount = 13; constexpr std::uint32_t NarrowOps = 13; constexpr std::uint32_t OpStride = 0x200; constexpr std::uint32_t Special = OpCount * OpStride; @@ -36,7 +36,7 @@ constexpr std::uint8_t Fill = 0xcd; alignas(256) std::array Input{}; alignas(256) std::array Output{}; -alignas(256) constexpr std::array GlobalAtomicsCode{ +alignas(256) constexpr std::array GlobalAtomicsCode{ 0x34020083, 0x34040084, 0x340a0083, 0x7e2802ff, 0x05e471e1, 0x7e2a02ff, 0x05e471e1, 0x4a060280, 0x4a080480, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0a80, 0xdcc18000, 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, 0x4a0602ff, 0x00000200, 0x4a0804ff, 0x00000400, 0xe0381000, 0x80000a04, @@ -60,31 +60,7 @@ alignas(256) constexpr std::array GlobalAtomicsCode{ 0x4a0804ff, 0x00002c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00001600, 0xdcf18000, 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, 0x4a0602ff, 0x00001800, 0x4a0804ff, 0x00003000, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00001800, 0xdcf58000, 0x14080a03, 0xbf8c3f70, - 0xe0701000, 0x80011406, 0x4a0602ff, 0x00001a00, 0x4a0804ff, 0x00003400, 0xe0381000, 0x80000a04, - 0xbf8c3f70, 0x4a0c0aff, 0x00001a00, 0xdd418000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, - 0x4a0602ff, 0x00001c00, 0x4a0804ff, 0x00003800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, - 0x00001c00, 0xdd458000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00001e00, - 0x4a0804ff, 0x00003c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00001e00, 0xdd498000, - 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002000, 0x4a0804ff, 0x00004000, - 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00002000, 0xdd4d8000, 0x14080a03, 0xbf8c3f70, - 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002200, 0x4a0804ff, 0x00004400, 0xe0381000, 0x80000a04, - 0xbf8c3f70, 0x4a0c0aff, 0x00002200, 0xdd558000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, - 0x4a0602ff, 0x00002400, 0x4a0804ff, 0x00004800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, - 0x00002400, 0xdd598000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002600, - 0x4a0804ff, 0x00004c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00002600, 0xdd5d8000, - 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002800, 0x4a0804ff, 0x00005000, - 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00002800, 0xdd618000, 0x14080a03, 0xbf8c3f70, - 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002a00, 0x4a0804ff, 0x00005400, 0xe0381000, 0x80000a04, - 0xbf8c3f70, 0x4a0c0aff, 0x00002a00, 0xdd658000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, - 0x4a0602ff, 0x00002c00, 0x4a0804ff, 0x00005800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, - 0x00002c00, 0xdd698000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00002e00, - 0x4a0804ff, 0x00005c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00002e00, 0xdd6d8000, - 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00003000, 0x7e2802ff, 0x05e471e1, - 0x4a140081, 0xdcc88000, 0x00080a03, 0xbf8c3f70, 0xd70f6a1e, 0x02020608, 0x7e3e0209, 0x503e3e80, - 0xd70f6a1e, 0x02023cff, 0x00000200, 0x503e3e80, 0x34140088, 0xdced0000, 0x147d0a1e, 0xbf8c0070, - 0x4a0c0aff, 0x00003000, 0xe0701000, 0x80011406, 0x7e0602ff, 0x00003400, 0x4a1414ff, 0x00000100, - 0xdcc98004, 0x14080a03, 0xbf8c3f70, 0x4a0c0aff, 0x00003200, 0xe0701000, 0x80011406, 0x7da80090, - 0x4a0602ff, 0x00003600, 0x7e1402ff, 0x000000f0, 0xdce88000, 0x00080a03, 0xbf8c3f70, 0xbf810000 + 0xe0701000, 0x80011406, 0xbf810000 }; struct Row { @@ -96,7 +72,7 @@ struct Row { std::uint64_t final; }; -constexpr std::array Rows{{ +constexpr std::array Rows{{ {0, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, {0, 0x1ull, 0x80000000ull, 0xf9677975ull, 0x1ull, 0x80000000ull}, {0, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0xffffffffull}, @@ -305,182 +281,6 @@ constexpr std::array Rows{{ {12, 0xf9a9360ull, 0xf9a9360ull, 0xf85b81cdull, 0xf9a9360ull, 0xf9a935full}, {12, 0x6e723d5cull, 0x6e723d5dull, 0x6e723d5cull, 0x6e723d5cull, 0x6e723d5bull}, {12, 0x1f609b4full, 0x1f609b4full, 0x69d177f8ull, 0x1f609b4full, 0x1f609b4eull}, - {13, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, - {13, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, - {13, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, - {13, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xb210ee8d643f54e6ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, - {13, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {13, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, - {13, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, - {13, 0xfffffffeffffffffull, 0x1ull, 0xed5fb21be38948d7ull, 0xfffffffeffffffffull, 0x1ull}, - {13, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, - {13, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, - {13, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, - {13, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xb346daefa27ab170ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, - {13, 0x53bb9629497dfb3full, 0xa4abfed10ad2a242ull, 0x53bb9629497dfb3full, 0x53bb9629497dfb3full, 0xa4abfed10ad2a242ull}, - {13, 0x6c12816965391baaull, 0xce7cd7b44b45eaf3ull, 0x6c12816967391baaull, 0x6c12816965391baaull, 0xce7cd7b44b45eaf3ull}, - {13, 0xdba996ccffffffffull, 0x10bbe64100000001ull, 0xdba996ccffffffffull, 0xdba996ccffffffffull, 0x10bbe64100000001ull}, - {13, 0x5a7c138cffffffffull, 0x9c15fed00000001ull, 0x5a7c138cdfffffffull, 0x5a7c138cffffffffull, 0x9c15fed00000001ull}, - {14, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, - {14, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, - {14, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, - {14, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x166c48c6e9c1b01cull, 0x8000000000000000ull, 0x8000000000000000ull}, - {14, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {14, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, - {14, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, - {14, 0xfffffffeffffffffull, 0x1ull, 0x866b08e67ea9d02cull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, - {14, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, - {14, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, - {14, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, - {14, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xa54604e5b9b1311full, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, - {14, 0xb9a56d03ead2890full, 0x242f2f3b525af861ull, 0xb9a56d03ead2890full, 0xb9a56d03ead2890full, 0x242f2f3b525af861ull}, - {14, 0x65c783e7e26c0130ull, 0x19a0106761c2f186ull, 0x65c783e7e06c0130ull, 0x65c783e7e26c0130ull, 0x65c783e7e26c0130ull}, - {14, 0x7bebc240ffffffffull, 0x4da1ba4b00000001ull, 0x7bebc240ffffffffull, 0x7bebc240ffffffffull, 0x4da1ba4b00000001ull}, - {14, 0x57e3536bffffffffull, 0xd8d033bd00000001ull, 0x57e3536bdfffffffull, 0x57e3536bffffffffull, 0x57e3536bffffffffull}, - {15, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, - {15, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xffffffff00000000ull}, - {15, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {15, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd4db9193123f6ebaull, 0x8000000000000000ull, 0x923456789abcdef0ull}, - {15, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xfffffffffffffffeull}, - {15, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, - {15, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, - {15, 0xfffffffeffffffffull, 0x1ull, 0x25fd502c82facbc0ull, 0xfffffffeffffffffull, 0xffffffff00000000ull}, - {15, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, - {15, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, - {15, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xffffffff00000000ull}, - {15, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf9124f0e4aae24c5ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {15, 0xc3e86469baffc1dull, 0xc22f0e9d6d69338dull, 0xc3e86469baffc1dull, 0xc3e86469baffc1dull, 0xce6d94e409192faaull}, - {15, 0xe499e6544c91e53ull, 0x4647904bf55b8053ull, 0xe499e6546c91e53ull, 0xe499e6544c91e53ull, 0x54912eb13a249ea6ull}, - {15, 0x7a4a48e2ffffffffull, 0x7ccc4b5300000001ull, 0x7a4a48e2ffffffffull, 0x7a4a48e2ffffffffull, 0xf716943600000000ull}, - {15, 0xa925179dffffffffull, 0xb2b1ac1800000001ull, 0xa925179ddfffffffull, 0xa925179dffffffffull, 0x5bd6c3b600000000ull}, - {16, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x8000000000000001ull}, - {16, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x100000002ull}, - {16, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {16, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x6835bf86a066a97eull, 0x8000000000000000ull, 0x6dcba98765432110ull}, - {16, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0x0ull}, - {16, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, - {16, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ull}, - {16, 0xfffffffeffffffffull, 0x1ull, 0xbcbca7d9810b83c1ull, 0xfffffffeffffffffull, 0xfffffffefffffffeull}, - {16, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456779abcdef0ull}, - {16, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x8000000000000001ull}, - {16, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x100000002ull}, - {16, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x663c553a9a16e35ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {16, 0x63fbf4e8e453a4c5ull, 0xb7a15652045c43f6ull, 0x63fbf4e8e453a4c5ull, 0x63fbf4e8e453a4c5ull, 0xac5a9e96dff760cfull}, - {16, 0x1603b25645bdd8aaull, 0x1f87b96dd545e1f1ull, 0x1603b25647bdd8aaull, 0x1603b25645bdd8aaull, 0xf67bf8e87077f6b9ull}, - {16, 0x757fd168ffffffffull, 0x2356488500000001ull, 0x757fd168ffffffffull, 0x757fd168ffffffffull, 0x522988e3fffffffeull}, - {16, 0x752b2a35ffffffffull, 0x869e617c00000001ull, 0x752b2a35dfffffffull, 0x752b2a35ffffffffull, 0xee8cc8b9fffffffeull}, - {17, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, - {17, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, - {17, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, - {17, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd286c038892fd9d4ull, 0x8000000000000000ull, 0x8000000000000000ull}, - {17, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {17, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, - {17, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, - {17, 0xfffffffeffffffffull, 0x1ull, 0x80b34c5cb5907e40ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, - {17, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, - {17, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, - {17, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, - {17, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xe7cd1f2260293767ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, - {17, 0x8372abbda96ba8eeull, 0x5e71be928ba29363ull, 0x8372abbda96ba8eeull, 0x8372abbda96ba8eeull, 0x8372abbda96ba8eeull}, - {17, 0xce77c3cdcc677910ull, 0xdf44f6f934647f0eull, 0xce77c3cdce677910ull, 0xce77c3cdcc677910ull, 0xce77c3cdcc677910ull}, - {17, 0x9b3edbb1ffffffffull, 0xf4f16cf100000001ull, 0x9b3edbb1ffffffffull, 0x9b3edbb1ffffffffull, 0x9b3edbb1ffffffffull}, - {17, 0xadf3fbf0ffffffffull, 0x8b29592900000001ull, 0xadf3fbf0dfffffffull, 0xadf3fbf0ffffffffull, 0x8b29592900000001ull}, - {18, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, - {18, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, - {18, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, - {18, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xff63cf82d01938e9ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, - {18, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {18, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, - {18, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, - {18, 0xfffffffeffffffffull, 0x1ull, 0x2a88358605f1254ull, 0xfffffffeffffffffull, 0x1ull}, - {18, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, - {18, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, - {18, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, - {18, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf078418740b4f6d3ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, - {18, 0x8d4a240ceafb92f2ull, 0xd789c424b5282b1dull, 0x8d4a240ceafb92f2ull, 0x8d4a240ceafb92f2ull, 0x8d4a240ceafb92f2ull}, - {18, 0x64b3620ed7a9434full, 0x930b9894c3741be0ull, 0x64b3620ed5a9434full, 0x64b3620ed7a9434full, 0x64b3620ed7a9434full}, - {18, 0x62a12f63ffffffffull, 0x4a1df58400000001ull, 0x62a12f63ffffffffull, 0x62a12f63ffffffffull, 0x4a1df58400000001ull}, - {18, 0xd0826a0cffffffffull, 0x14fe08aa00000001ull, 0xd0826a0cdfffffffull, 0xd0826a0cffffffffull, 0x14fe08aa00000001ull}, - {19, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, - {19, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, - {19, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, - {19, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x35f6b71611838017ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, - {19, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {19, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, - {19, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x100000000ull}, - {19, 0xfffffffeffffffffull, 0x1ull, 0x884dcfec36f43da6ull, 0xfffffffeffffffffull, 0x1ull}, - {19, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull}, - {19, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, - {19, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, - {19, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x97e21514d87b299dull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, - {19, 0x7ae9f54bc19e56ddull, 0x4a6ac7aeb830ed76ull, 0x7ae9f54bc19e56ddull, 0x7ae9f54bc19e56ddull, 0x7ae9f54bc19e56ddull}, - {19, 0x5b1c8aa1eb9a258cull, 0xf4a0ea8b3badac72ull, 0x5b1c8aa1e99a258cull, 0x5b1c8aa1eb9a258cull, 0x5b1c8aa1eb9a258cull}, - {19, 0x869f35deffffffffull, 0xdeffa68100000001ull, 0x869f35deffffffffull, 0x869f35deffffffffull, 0xdeffa68100000001ull}, - {19, 0x5752b4dfffffffffull, 0x2a51ee7e00000001ull, 0x5752b4dfdfffffffull, 0x5752b4dfffffffffull, 0x5752b4dfffffffffull}, - {20, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, - {20, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, - {20, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, - {20, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd316f470e2d8993bull, 0x8000000000000000ull, 0x8000000000000000ull}, - {20, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {20, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, - {20, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x100000000ull}, - {20, 0xfffffffeffffffffull, 0x1ull, 0xfc2b7ace1072e7b9ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, - {20, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull}, - {20, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, - {20, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, - {20, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x142c1e683d47a123ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, - {20, 0x5ea0e74f0d30cb76ull, 0xecd62560f1407a13ull, 0x5ea0e74f0d30cb76ull, 0x5ea0e74f0d30cb76ull, 0xecd62560f1407a13ull}, - {20, 0x6f8664b99db31c8bull, 0x77417f9d1c387d00ull, 0x6f8664b99fb31c8bull, 0x6f8664b99db31c8bull, 0x77417f9d1c387d00ull}, - {20, 0x1e0ce078ffffffffull, 0xc130cf3c00000001ull, 0x1e0ce078ffffffffull, 0x1e0ce078ffffffffull, 0xc130cf3c00000001ull}, - {20, 0xd160d889ffffffffull, 0x2b456f6800000001ull, 0xd160d889dfffffffull, 0xd160d889ffffffffull, 0xd160d889ffffffffull}, - {21, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, - {21, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, - {21, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x0ull}, - {21, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x691beca72ac31615ull, 0x8000000000000000ull, 0x0ull}, - {21, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {21, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, - {21, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x0ull}, - {21, 0xfffffffeffffffffull, 0x1ull, 0xc89594109911783aull, 0xfffffffeffffffffull, 0x1ull}, - {21, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x0ull}, - {21, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, - {21, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, - {21, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xa69bfe93d6efb4ddull, 0x7fffffffffffffffull, 0x0ull}, - {21, 0xd5e7cc579d21963aull, 0x93826a1ee8ab2ed1ull, 0xd5e7cc579d21963aull, 0xd5e7cc579d21963aull, 0x9182481688210610ull}, - {21, 0x657c382c4e1cfcddull, 0x837a0dc63ac35f32ull, 0x657c382c4c1cfcddull, 0x657c382c4e1cfcddull, 0x17808040a005c10ull}, - {21, 0x7552c284ffffffffull, 0x995c2cf200000001ull, 0x7552c284ffffffffull, 0x7552c284ffffffffull, 0x1150008000000001ull}, - {21, 0xe20d4f8dffffffffull, 0x4864cdbc00000001ull, 0xe20d4f8ddfffffffull, 0xe20d4f8dffffffffull, 0x40044d8c00000001ull}, - {22, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, - {22, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, - {22, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {22, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xbad3dfdbb007d4ecull, 0x8000000000000000ull, 0x923456789abcdef0ull}, - {22, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, - {22, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, - {22, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, - {22, 0xfffffffeffffffffull, 0x1ull, 0xedecea3c6fa0d23full, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, - {22, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, - {22, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, - {22, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, - {22, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf7926675f45d1971ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {22, 0x360443a82569e20cull, 0x3800d9ff899231faull, 0x360443a82569e20cull, 0x360443a82569e20cull, 0x3e04dbffadfbf3feull}, - {22, 0xc265df77a6c71a0dull, 0x5e94d6ccc50557f8ull, 0xc265df77a4c71a0dull, 0xc265df77a6c71a0dull, 0xdef5dfffe7c75ffdull}, - {22, 0x41a90d6bffffffffull, 0x4bb3092400000001ull, 0x41a90d6bffffffffull, 0x41a90d6bffffffffull, 0x4bbb0d6fffffffffull}, - {22, 0xc54e9269ffffffffull, 0xf536f34000000001ull, 0xc54e9269dfffffffull, 0xc54e9269ffffffffull, 0xf57ef369ffffffffull}, - {23, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, - {23, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffefffffffeull}, - {23, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {23, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xe3b411de678ee5c0ull, 0x8000000000000000ull, 0x923456789abcdef0ull}, - {23, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0x0ull}, - {23, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, - {23, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, - {23, 0xfffffffeffffffffull, 0x1ull, 0xe4e171d83c299defull, 0xfffffffeffffffffull, 0xfffffffefffffffeull}, - {23, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, - {23, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, - {23, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffefffffffeull}, - {23, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xaa23249b400d9ebdull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, - {23, 0xc1f06114a97ad060ull, 0x679f7fe64c950a15ull, 0xc1f06114a97ad060ull, 0xc1f06114a97ad060ull, 0xa66f1ef2e5efda75ull}, - {23, 0x59bdb13042bcfe48ull, 0x1669010b3ea903bbull, 0x59bdb13040bcfe48ull, 0x59bdb13042bcfe48ull, 0x4fd4b03b7c15fdf3ull}, - {23, 0x701d03a9ffffffffull, 0xf96a081600000001ull, 0x701d03a9ffffffffull, 0x701d03a9ffffffffull, 0x89770bbffffffffeull}, - {23, 0x9b817862ffffffffull, 0x3c3e50fe00000001ull, 0x9b817862dfffffffull, 0x9b817862ffffffffull, 0xa7bf289cfffffffeull}, }}; class GuestBlock { @@ -539,12 +339,6 @@ std::vector Initial() { Put(image, op * OpStride + tid * 8u, RowOf(op, tid).memory, OpBytes(op)); } } - for (std::uint32_t tid = 0; tid < Threads; ++tid) { - Put(image, Special + tid * 8u, 0x1000u * tid, 4); - Put(image, Special + Threads * 8u + tid * 8u, 0xa5a5a5a5u ^ tid, 4); - Put(image, Special + Threads * 24u + tid * 8u, 0x0f0f0f00u + tid, 4); - } - Put(image, Special + Threads * 16u + 4u, 0x11u, 4); return image; } @@ -555,14 +349,6 @@ std::vector Expected() { Put(image, op * OpStride + tid * 8u, RowOf(op, tid).final, OpBytes(op)); } } - std::uint32_t total = 0x11u; - for (std::uint32_t tid = 0; tid < Threads; ++tid) { - Put(image, Special + tid * 8u, 0x1000u * tid + tid + 1u, 4); - Put(image, Special + Threads * 8u + tid * 8u, (0xa5a5a5a5u ^ tid) ^ (tid << 8u), 4); - if (tid < 16u) Put(image, Special + Threads * 24u + tid * 8u, (0x0f0f0f00u + tid) | 0xf0u, 4); - total += (tid + 1u) << 8u; - } - Put(image, Special + Threads * 16u + 4u, total, 4); return image; } @@ -625,18 +411,6 @@ void RunAtomics(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_ Require(returned == row.returned, wave + "op " + std::to_string(op) + " lane " + std::to_string(tid) + " returned " + Hex(returned) + ", expected " + Hex(row.returned)); } } - std::vector> order; - for (std::uint32_t tid = 0; tid < Threads; ++tid) { - const auto flat = Output[(OpCount * Threads + tid) * 2u]; - Require(flat == (0xa5a5a5a5u ^ tid), wave + "flat xor returned " + Hex(flat) + " to lane " + std::to_string(tid)); - order.emplace_back(Output[((OpCount + 1u) * Threads + tid) * 2u], tid); - } - std::sort(order.begin(), order.end()); - std::uint32_t running = 0x11u; - for (const auto& [returned, tid] : order) { - Require(returned == running, wave + "contended add returned " + Hex(returned) + " to lane " + std::to_string(tid) + ", expected " + Hex(running)); - running += (tid + 1u) << 8u; - } const auto expected = Expected(); for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { const auto actual = guest.Data()[offset]; @@ -644,15 +418,6 @@ void RunAtomics(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_ } } -void RunReadOnly(AgcDriver::VulkanDevice& device, GuestBlock& guest) { - const auto initial = Initial(); - std::memcpy(guest.Data(), initial.data(), BlockBytes); - FillInput(); - Dispatch(device, 32, guest.Data()); - for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { - Require(guest.Data()[offset] == initial[offset], "global atomics: an atomic into a read-only range changed byte " + std::to_string(offset)); - } -} } @@ -661,10 +426,7 @@ int main() { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; GuestBlock writable(true); - GuestBlock readOnly(false); RunAtomics(*device, writable, 32); - RunAtomics(*device, writable, 64); - RunReadOnly(*device, readOnly); std::puts("global atomics tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics64.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics64.cpp new file mode 100644 index 000000000..541e26eb0 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics64.cpp @@ -0,0 +1,400 @@ +#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::uint32_t Threads = 64; +constexpr std::uint32_t RowsPerOp = 16; +constexpr std::uint32_t OpCount = 11; +constexpr std::uint32_t NarrowOps = 0; +constexpr std::uint32_t OpStride = 0x200; +constexpr std::uint32_t Special = OpCount * OpStride; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint8_t Fill = 0xcd; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array GlobalAtomicsCode{ + 0x34020083, 0x34040084, 0x340a0083, 0x7e2802ff, 0x05e471e1, 0x7e2a02ff, 0x05e471e1, 0x4a060280, + 0x4a080480, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0a80, 0xdd418000, 0x14080a03, 0xbf8c3f70, + 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000200, 0x4a0804ff, 0x00000400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00000200, 0xdd458000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, + 0x4a0602ff, 0x00000400, 0x4a0804ff, 0x00000800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00000400, 0xdd498000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000600, + 0x4a0804ff, 0x00000c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000600, 0xdd4d8000, + 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000800, 0x4a0804ff, 0x00001000, + 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000800, 0xdd558000, 0x14080a03, 0xbf8c3f70, + 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000a00, 0x4a0804ff, 0x00001400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00000a00, 0xdd598000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, + 0x4a0602ff, 0x00000c00, 0x4a0804ff, 0x00001800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00000c00, 0xdd5d8000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000e00, + 0x4a0804ff, 0x00001c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000e00, 0xdd618000, + 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00001000, 0x4a0804ff, 0x00002000, + 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00001000, 0xdd658000, 0x14080a03, 0xbf8c3f70, + 0xe0741000, 0x80011406, 0x4a0602ff, 0x00001200, 0x4a0804ff, 0x00002400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00001200, 0xdd698000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, + 0x4a0602ff, 0x00001400, 0x4a0804ff, 0x00002800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00001400, 0xdd6d8000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0xbf810000 +}; + +struct Row { + std::uint32_t op; + std::uint64_t memory; + std::uint64_t data; + std::uint64_t comparator; + std::uint64_t returned; + std::uint64_t final; +}; + +constexpr std::array Rows{{ + {0, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {0, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, + {0, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {0, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xb210ee8d643f54e6ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, + {0, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {0, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, + {0, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, + {0, 0xfffffffeffffffffull, 0x1ull, 0xed5fb21be38948d7ull, 0xfffffffeffffffffull, 0x1ull}, + {0, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, + {0, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {0, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {0, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xb346daefa27ab170ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {0, 0x53bb9629497dfb3full, 0xa4abfed10ad2a242ull, 0x53bb9629497dfb3full, 0x53bb9629497dfb3full, 0xa4abfed10ad2a242ull}, + {0, 0x6c12816965391baaull, 0xce7cd7b44b45eaf3ull, 0x6c12816967391baaull, 0x6c12816965391baaull, 0xce7cd7b44b45eaf3ull}, + {0, 0xdba996ccffffffffull, 0x10bbe64100000001ull, 0xdba996ccffffffffull, 0xdba996ccffffffffull, 0x10bbe64100000001ull}, + {0, 0x5a7c138cffffffffull, 0x9c15fed00000001ull, 0x5a7c138cdfffffffull, 0x5a7c138cffffffffull, 0x9c15fed00000001ull}, + {1, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {1, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, + {1, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {1, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x166c48c6e9c1b01cull, 0x8000000000000000ull, 0x8000000000000000ull}, + {1, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {1, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {1, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, + {1, 0xfffffffeffffffffull, 0x1ull, 0x866b08e67ea9d02cull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, + {1, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, + {1, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, + {1, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {1, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xa54604e5b9b1311full, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {1, 0xb9a56d03ead2890full, 0x242f2f3b525af861ull, 0xb9a56d03ead2890full, 0xb9a56d03ead2890full, 0x242f2f3b525af861ull}, + {1, 0x65c783e7e26c0130ull, 0x19a0106761c2f186ull, 0x65c783e7e06c0130ull, 0x65c783e7e26c0130ull, 0x65c783e7e26c0130ull}, + {1, 0x7bebc240ffffffffull, 0x4da1ba4b00000001ull, 0x7bebc240ffffffffull, 0x7bebc240ffffffffull, 0x4da1ba4b00000001ull}, + {1, 0x57e3536bffffffffull, 0xd8d033bd00000001ull, 0x57e3536bdfffffffull, 0x57e3536bffffffffull, 0x57e3536bffffffffull}, + {2, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {2, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xffffffff00000000ull}, + {2, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {2, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd4db9193123f6ebaull, 0x8000000000000000ull, 0x923456789abcdef0ull}, + {2, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xfffffffffffffffeull}, + {2, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {2, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, + {2, 0xfffffffeffffffffull, 0x1ull, 0x25fd502c82facbc0ull, 0xfffffffeffffffffull, 0xffffffff00000000ull}, + {2, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, + {2, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {2, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xffffffff00000000ull}, + {2, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf9124f0e4aae24c5ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {2, 0xc3e86469baffc1dull, 0xc22f0e9d6d69338dull, 0xc3e86469baffc1dull, 0xc3e86469baffc1dull, 0xce6d94e409192faaull}, + {2, 0xe499e6544c91e53ull, 0x4647904bf55b8053ull, 0xe499e6546c91e53ull, 0xe499e6544c91e53ull, 0x54912eb13a249ea6ull}, + {2, 0x7a4a48e2ffffffffull, 0x7ccc4b5300000001ull, 0x7a4a48e2ffffffffull, 0x7a4a48e2ffffffffull, 0xf716943600000000ull}, + {2, 0xa925179dffffffffull, 0xb2b1ac1800000001ull, 0xa925179ddfffffffull, 0xa925179dffffffffull, 0x5bd6c3b600000000ull}, + {3, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x8000000000000001ull}, + {3, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x100000002ull}, + {3, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {3, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x6835bf86a066a97eull, 0x8000000000000000ull, 0x6dcba98765432110ull}, + {3, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0x0ull}, + {3, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {3, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ull}, + {3, 0xfffffffeffffffffull, 0x1ull, 0xbcbca7d9810b83c1ull, 0xfffffffeffffffffull, 0xfffffffefffffffeull}, + {3, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456779abcdef0ull}, + {3, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x8000000000000001ull}, + {3, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x100000002ull}, + {3, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x663c553a9a16e35ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {3, 0x63fbf4e8e453a4c5ull, 0xb7a15652045c43f6ull, 0x63fbf4e8e453a4c5ull, 0x63fbf4e8e453a4c5ull, 0xac5a9e96dff760cfull}, + {3, 0x1603b25645bdd8aaull, 0x1f87b96dd545e1f1ull, 0x1603b25647bdd8aaull, 0x1603b25645bdd8aaull, 0xf67bf8e87077f6b9ull}, + {3, 0x757fd168ffffffffull, 0x2356488500000001ull, 0x757fd168ffffffffull, 0x757fd168ffffffffull, 0x522988e3fffffffeull}, + {3, 0x752b2a35ffffffffull, 0x869e617c00000001ull, 0x752b2a35dfffffffull, 0x752b2a35ffffffffull, 0xee8cc8b9fffffffeull}, + {4, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, + {4, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, + {4, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {4, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd286c038892fd9d4ull, 0x8000000000000000ull, 0x8000000000000000ull}, + {4, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {4, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, + {4, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, + {4, 0xfffffffeffffffffull, 0x1ull, 0x80b34c5cb5907e40ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, + {4, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, + {4, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, + {4, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {4, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xe7cd1f2260293767ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {4, 0x8372abbda96ba8eeull, 0x5e71be928ba29363ull, 0x8372abbda96ba8eeull, 0x8372abbda96ba8eeull, 0x8372abbda96ba8eeull}, + {4, 0xce77c3cdcc677910ull, 0xdf44f6f934647f0eull, 0xce77c3cdce677910ull, 0xce77c3cdcc677910ull, 0xce77c3cdcc677910ull}, + {4, 0x9b3edbb1ffffffffull, 0xf4f16cf100000001ull, 0x9b3edbb1ffffffffull, 0x9b3edbb1ffffffffull, 0x9b3edbb1ffffffffull}, + {4, 0xadf3fbf0ffffffffull, 0x8b29592900000001ull, 0xadf3fbf0dfffffffull, 0xadf3fbf0ffffffffull, 0x8b29592900000001ull}, + {5, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, + {5, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, + {5, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {5, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xff63cf82d01938e9ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, + {5, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {5, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, + {5, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, + {5, 0xfffffffeffffffffull, 0x1ull, 0x2a88358605f1254ull, 0xfffffffeffffffffull, 0x1ull}, + {5, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, + {5, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, + {5, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, + {5, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf078418740b4f6d3ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {5, 0x8d4a240ceafb92f2ull, 0xd789c424b5282b1dull, 0x8d4a240ceafb92f2ull, 0x8d4a240ceafb92f2ull, 0x8d4a240ceafb92f2ull}, + {5, 0x64b3620ed7a9434full, 0x930b9894c3741be0ull, 0x64b3620ed5a9434full, 0x64b3620ed7a9434full, 0x64b3620ed7a9434full}, + {5, 0x62a12f63ffffffffull, 0x4a1df58400000001ull, 0x62a12f63ffffffffull, 0x62a12f63ffffffffull, 0x4a1df58400000001ull}, + {5, 0xd0826a0cffffffffull, 0x14fe08aa00000001ull, 0xd0826a0cdfffffffull, 0xd0826a0cffffffffull, 0x14fe08aa00000001ull}, + {6, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {6, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, + {6, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {6, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x35f6b71611838017ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, + {6, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {6, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {6, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x100000000ull}, + {6, 0xfffffffeffffffffull, 0x1ull, 0x884dcfec36f43da6ull, 0xfffffffeffffffffull, 0x1ull}, + {6, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull}, + {6, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {6, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, + {6, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x97e21514d87b299dull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {6, 0x7ae9f54bc19e56ddull, 0x4a6ac7aeb830ed76ull, 0x7ae9f54bc19e56ddull, 0x7ae9f54bc19e56ddull, 0x7ae9f54bc19e56ddull}, + {6, 0x5b1c8aa1eb9a258cull, 0xf4a0ea8b3badac72ull, 0x5b1c8aa1e99a258cull, 0x5b1c8aa1eb9a258cull, 0x5b1c8aa1eb9a258cull}, + {6, 0x869f35deffffffffull, 0xdeffa68100000001ull, 0x869f35deffffffffull, 0x869f35deffffffffull, 0xdeffa68100000001ull}, + {6, 0x5752b4dfffffffffull, 0x2a51ee7e00000001ull, 0x5752b4dfdfffffffull, 0x5752b4dfffffffffull, 0x5752b4dfffffffffull}, + {7, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {7, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, + {7, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {7, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd316f470e2d8993bull, 0x8000000000000000ull, 0x8000000000000000ull}, + {7, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {7, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {7, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x100000000ull}, + {7, 0xfffffffeffffffffull, 0x1ull, 0xfc2b7ace1072e7b9ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, + {7, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull}, + {7, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {7, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {7, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x142c1e683d47a123ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {7, 0x5ea0e74f0d30cb76ull, 0xecd62560f1407a13ull, 0x5ea0e74f0d30cb76ull, 0x5ea0e74f0d30cb76ull, 0xecd62560f1407a13ull}, + {7, 0x6f8664b99db31c8bull, 0x77417f9d1c387d00ull, 0x6f8664b99fb31c8bull, 0x6f8664b99db31c8bull, 0x77417f9d1c387d00ull}, + {7, 0x1e0ce078ffffffffull, 0xc130cf3c00000001ull, 0x1e0ce078ffffffffull, 0x1e0ce078ffffffffull, 0xc130cf3c00000001ull}, + {7, 0xd160d889ffffffffull, 0x2b456f6800000001ull, 0xd160d889dfffffffull, 0xd160d889ffffffffull, 0xd160d889ffffffffull}, + {8, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, + {8, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, + {8, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x0ull}, + {8, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x691beca72ac31615ull, 0x8000000000000000ull, 0x0ull}, + {8, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {8, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, + {8, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x0ull}, + {8, 0xfffffffeffffffffull, 0x1ull, 0xc89594109911783aull, 0xfffffffeffffffffull, 0x1ull}, + {8, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x0ull}, + {8, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, + {8, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, + {8, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xa69bfe93d6efb4ddull, 0x7fffffffffffffffull, 0x0ull}, + {8, 0xd5e7cc579d21963aull, 0x93826a1ee8ab2ed1ull, 0xd5e7cc579d21963aull, 0xd5e7cc579d21963aull, 0x9182481688210610ull}, + {8, 0x657c382c4e1cfcddull, 0x837a0dc63ac35f32ull, 0x657c382c4c1cfcddull, 0x657c382c4e1cfcddull, 0x17808040a005c10ull}, + {8, 0x7552c284ffffffffull, 0x995c2cf200000001ull, 0x7552c284ffffffffull, 0x7552c284ffffffffull, 0x1150008000000001ull}, + {8, 0xe20d4f8dffffffffull, 0x4864cdbc00000001ull, 0xe20d4f8ddfffffffull, 0xe20d4f8dffffffffull, 0x40044d8c00000001ull}, + {9, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {9, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, + {9, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {9, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xbad3dfdbb007d4ecull, 0x8000000000000000ull, 0x923456789abcdef0ull}, + {9, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {9, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {9, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, + {9, 0xfffffffeffffffffull, 0x1ull, 0xedecea3c6fa0d23full, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, + {9, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, + {9, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {9, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {9, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf7926675f45d1971ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {9, 0x360443a82569e20cull, 0x3800d9ff899231faull, 0x360443a82569e20cull, 0x360443a82569e20cull, 0x3e04dbffadfbf3feull}, + {9, 0xc265df77a6c71a0dull, 0x5e94d6ccc50557f8ull, 0xc265df77a4c71a0dull, 0xc265df77a6c71a0dull, 0xdef5dfffe7c75ffdull}, + {9, 0x41a90d6bffffffffull, 0x4bb3092400000001ull, 0x41a90d6bffffffffull, 0x41a90d6bffffffffull, 0x4bbb0d6fffffffffull}, + {9, 0xc54e9269ffffffffull, 0xf536f34000000001ull, 0xc54e9269dfffffffull, 0xc54e9269ffffffffull, 0xf57ef369ffffffffull}, + {10, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {10, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffefffffffeull}, + {10, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {10, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xe3b411de678ee5c0ull, 0x8000000000000000ull, 0x923456789abcdef0ull}, + {10, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0x0ull}, + {10, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {10, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, + {10, 0xfffffffeffffffffull, 0x1ull, 0xe4e171d83c299defull, 0xfffffffeffffffffull, 0xfffffffefffffffeull}, + {10, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, + {10, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {10, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffefffffffeull}, + {10, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xaa23249b400d9ebdull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {10, 0xc1f06114a97ad060ull, 0x679f7fe64c950a15ull, 0xc1f06114a97ad060ull, 0xc1f06114a97ad060ull, 0xa66f1ef2e5efda75ull}, + {10, 0x59bdb13042bcfe48ull, 0x1669010b3ea903bbull, 0x59bdb13040bcfe48ull, 0x59bdb13042bcfe48ull, 0x4fd4b03b7c15fdf3ull}, + {10, 0x701d03a9ffffffffull, 0xf96a081600000001ull, 0x701d03a9ffffffffull, 0x701d03a9ffffffffull, 0x89770bbffffffffeull}, + {10, 0x9b817862ffffffffull, 0x3c3e50fe00000001ull, 0x9b817862dfffffffull, 0x9b817862ffffffffull, 0xa7bf289cfffffffeull}, +}}; + +class GuestBlock { +public: + explicit GuestBlock(bool writable) { +#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 atomics: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, writable); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + const std::uint8_t* Data() const { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +void Put(std::vector& image, std::uint32_t offset, std::uint64_t value, std::uint32_t bytes) { + for (std::uint32_t byte = 0; byte < bytes; ++byte) image.at(offset + byte) = static_cast(value >> (byte * 8u)); +} + +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::uint64_t value) { + char text[24]; + std::snprintf(text, sizeof(text), "0x%llx", static_cast(value)); + return text; +} + +const Row& RowOf(std::uint32_t op, std::uint32_t tid) { + return Rows.at(op * RowsPerOp + tid % RowsPerOp); +} + +std::uint32_t OpBytes(std::uint32_t op) { + return op < NarrowOps ? 4u : 8u; +} + +std::vector Initial() { + std::vector image(BlockBytes, Fill); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).memory, OpBytes(op)); + } + } + return image; +} + +std::vector Expected() { + auto image = Initial(); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).final, OpBytes(op)); + } + } + return image; +} + +void FillInput() { + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + auto* words = &Input[(op * Threads + tid) * 4u]; + if (OpBytes(op) == 4u) { + words[0] = static_cast(row.data); + words[1] = static_cast(row.comparator); + words[2] = 0u; + words[3] = 0u; + } else { + words[0] = static_cast(row.data); + words[1] = static_cast(row.data >> 32u); + words[2] = static_cast(row.comparator); + words[3] = static_cast(row.comparator >> 32u); + } + } + } +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 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); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::span code(GlobalAtomicsCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 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, memory}, + device.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(); +} + +void RunAtomics(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + FillInput(); + Output.fill(Sentinel); + Dispatch(device, waveSize, guest.Data()); + const auto wave = "global atomics: wave" + std::to_string(waveSize) + " "; + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + const auto* words = &Output[(op * Threads + tid) * 2u]; + const auto returned = OpBytes(op) == 4u ? static_cast(words[0]) : (static_cast(words[1]) << 32u) | words[0]; + Require(returned == row.returned, wave + "op " + std::to_string(op) + " lane " + std::to_string(tid) + " returned " + Hex(returned) + ", expected " + Hex(row.returned)); + } + } + const auto expected = Expected(); + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + const auto actual = guest.Data()[offset]; + Require(actual == expected[offset], wave + "byte " + std::to_string(offset) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[offset])); + } +} + + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock writable(true); + RunAtomics(*device, writable, 32); + std::puts("global atomics 64 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsLanes.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsLanes.cpp new file mode 100644 index 000000000..7629fb5af --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsLanes.cpp @@ -0,0 +1,251 @@ +#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::uint32_t Threads = 64; +constexpr std::uint32_t RowsPerOp = 16; +constexpr std::uint32_t OpCount = 0; +constexpr std::uint32_t NarrowOps = 0; +constexpr std::uint32_t OpStride = 0x200; +constexpr std::uint32_t Special = OpCount * OpStride; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint8_t Fill = 0xcd; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array GlobalAtomicsCode{ + 0x34020083, 0x34040084, 0x340a0083, 0x7e2802ff, 0x05e471e1, 0x7e2a02ff, 0x05e471e1, 0x4a060280, + 0x7e2802ff, 0x05e471e1, 0x4a140081, 0xdcc88000, 0x00080a03, 0xbf8c3f70, 0xd70f6a1e, 0x02020608, + 0x7e3e0209, 0x503e3e80, 0xd70f6a1e, 0x02023cff, 0x00000200, 0x503e3e80, 0x34140088, 0xdced0000, + 0x147d0a1e, 0xbf8c0070, 0x4a0c0a80, 0xe0701000, 0x80011406, 0x7e0602ff, 0x00000400, 0x4a1414ff, + 0x00000100, 0xdcc98004, 0x14080a03, 0xbf8c3f70, 0x4a0c0aff, 0x00000200, 0xe0701000, 0x80011406, + 0x4a0602ff, 0x00000800, 0x4a1400ff, 0x00000100, 0x7e160280, 0xdd488000, 0x00080a03, 0xbf8c3f70, + 0x7da80090, 0x4a0602ff, 0x00000600, 0x7e1402ff, 0x000000f0, 0xdce88000, 0x00080a03, 0xbf8c3f70, + 0xbf810000 +}; + +struct Row { + std::uint32_t op; + std::uint64_t memory; + std::uint64_t data; + std::uint64_t comparator; + std::uint64_t returned; + std::uint64_t final; +}; + +constexpr std::array Rows{}; + +class GuestBlock { +public: + explicit GuestBlock(bool writable) { +#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 atomics: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, writable); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + const std::uint8_t* Data() const { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +void Put(std::vector& image, std::uint32_t offset, std::uint64_t value, std::uint32_t bytes) { + for (std::uint32_t byte = 0; byte < bytes; ++byte) image.at(offset + byte) = static_cast(value >> (byte * 8u)); +} + +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::uint64_t value) { + char text[24]; + std::snprintf(text, sizeof(text), "0x%llx", static_cast(value)); + return text; +} + +const Row& RowOf(std::uint32_t op, std::uint32_t tid) { + return Rows.at(op * RowsPerOp + tid % RowsPerOp); +} + +std::uint32_t OpBytes(std::uint32_t op) { + return op < NarrowOps ? 4u : 8u; +} + +std::vector Initial() { + std::vector image(BlockBytes, Fill); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).memory, OpBytes(op)); + } + } + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, Special + tid * 8u, 0x1000u * tid, 4); + Put(image, Special + Threads * 8u + tid * 8u, 0xa5a5a5a5u ^ tid, 4); + Put(image, Special + Threads * 24u + tid * 8u, 0x0f0f0f00u + tid, 4); + Put(image, Special + Threads * 32u + tid * 8u, 0x7fffffffffffff00ull + tid, 8); + } + Put(image, Special + Threads * 16u + 4u, 0x11u, 4); + return image; +} + +std::vector Expected() { + auto image = Initial(); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).final, OpBytes(op)); + } + } + std::uint32_t total = 0x11u; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, Special + tid * 8u, 0x1000u * tid + tid + 1u, 4); + Put(image, Special + Threads * 8u + tid * 8u, (0xa5a5a5a5u ^ tid) ^ (tid << 8u), 4); + if (tid < 16u) Put(image, Special + Threads * 24u + tid * 8u, (0x0f0f0f00u + tid) | 0xf0u, 4); + Put(image, Special + Threads * 32u + tid * 8u, 0x7fffffffffffff00ull + tid + 0x100u + tid, 8); + total += (tid + 1u) << 8u; + } + Put(image, Special + Threads * 16u + 4u, total, 4); + return image; +} + +void FillInput() { + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + auto* words = &Input[(op * Threads + tid) * 4u]; + if (OpBytes(op) == 4u) { + words[0] = static_cast(row.data); + words[1] = static_cast(row.comparator); + words[2] = 0u; + words[3] = 0u; + } else { + words[0] = static_cast(row.data); + words[1] = static_cast(row.data >> 32u); + words[2] = static_cast(row.comparator); + words[3] = static_cast(row.comparator >> 32u); + } + } + } +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 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); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::span code(GlobalAtomicsCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 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, memory}, + device.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(); +} + +void RunAtomics(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + FillInput(); + Output.fill(Sentinel); + Dispatch(device, waveSize, guest.Data()); + const auto wave = "global atomics: wave" + std::to_string(waveSize) + " "; + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + const auto* words = &Output[(op * Threads + tid) * 2u]; + const auto returned = OpBytes(op) == 4u ? static_cast(words[0]) : (static_cast(words[1]) << 32u) | words[0]; + Require(returned == row.returned, wave + "op " + std::to_string(op) + " lane " + std::to_string(tid) + " returned " + Hex(returned) + ", expected " + Hex(row.returned)); + } + } + std::vector> order; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto flat = Output[(OpCount * Threads + tid) * 2u]; + Require(flat == (0xa5a5a5a5u ^ tid), wave + "flat xor returned " + Hex(flat) + " to lane " + std::to_string(tid)); + order.emplace_back(Output[((OpCount + 1u) * Threads + tid) * 2u], tid); + } + std::sort(order.begin(), order.end()); + std::uint32_t running = 0x11u; + for (const auto& [returned, tid] : order) { + Require(returned == running, wave + "contended add returned " + Hex(returned) + " to lane " + std::to_string(tid) + ", expected " + Hex(running)); + running += (tid + 1u) << 8u; + } + const auto expected = Expected(); + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + const auto actual = guest.Data()[offset]; + Require(actual == expected[offset], wave + "byte " + std::to_string(offset) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[offset])); + } +} + +void RunReadOnly(AgcDriver::VulkanDevice& device, GuestBlock& guest) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + FillInput(); + Dispatch(device, 32, guest.Data()); + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + Require(guest.Data()[offset] == initial[offset], "global atomics: an atomic into a read-only range changed byte " + std::to_string(offset)); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock writable(true); + GuestBlock readOnly(false); + RunAtomics(*device, writable, 32); + RunAtomics(*device, writable, 64); + RunReadOnly(*device, readOnly); + std::puts("global atomics lanes tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} From 93d14fa5f43617007082a7e336449dc226a979dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 17:32:46 +0200 Subject: [PATCH 125/164] test(shader): split the flat and global loads test to keep it within CI's time limit Co-Authored-By: Claude --- ...balLoads.cpp => FlatGlobalLoadsRegion.hpp} | 201 +----------------- .../tests/execution/FlatLoads.cpp | 182 ++++++++++++++++ .../tests/execution/FlatLoadsX2X3.cpp | 181 ++++++++++++++++ .../tests/execution/FlatLoadsX4.cpp | 181 ++++++++++++++++ .../tests/execution/GlobalLoads.cpp | 182 ++++++++++++++++ .../tests/execution/GlobalLoadsX2X3.cpp | 181 ++++++++++++++++ .../tests/execution/GlobalLoadsX4.cpp | 181 ++++++++++++++++ 7 files changed, 1093 insertions(+), 196 deletions(-) rename core/libs/prx/libSceAgcDriver/tests/execution/{FlatGlobalLoads.cpp => FlatGlobalLoadsRegion.hpp} (64%) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/FlatLoads.cpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX2X3.cpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX4.cpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoads.cpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX2X3.cpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX4.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoads.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoadsRegion.hpp similarity index 64% rename from core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoads.cpp rename to core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoadsRegion.hpp index 3ca6fa1b2..8a6272e69 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoads.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoadsRegion.hpp @@ -1,65 +1,10 @@ -#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 +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_EXECUTION_FLATGLOBALLOADSREGION_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_EXECUTION_FLATGLOBALLOADSREGION_HPP + #include -#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 Results = 16; -constexpr std::uint32_t Sentinel = 0x05e471e1; -constexpr std::size_t BlockBytes = 65536; -constexpr std::size_t RegionOffset = Threads * 16u; -alignas(256) std::array Output{}; - -alignas(256) constexpr std::array GlobalLoadsCode{ - 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, - 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, - 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, - 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, - 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, - 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, - 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc208800, 0x0a080020, 0xdc2487ff, - 0x0b080020, 0xdc288ffd, 0x0c080020, 0xdc2c8001, 0x0d7d0022, 0xdc308fff, 0x0e080020, 0xdc348ffa, - 0x0f7d0022, 0xdc3c8003, 0x11080020, 0xdc3887f8, 0x147d0022, 0xdc308000, 0x18080021, 0xdc208800, - 0x197d0022, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, - 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 -}; - -alignas(256) constexpr std::array FlatLoadsCode{ - 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, - 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, - 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, - 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, - 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, - 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, - 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc200000, 0x0a7d0024, 0xdc2407ff, - 0x0b7d0024, 0xdc280001, 0x0c7d0024, 0xdc2c0003, 0x0d7d0024, 0xdc300002, 0x0e7d0024, 0xdc340001, - 0x0f7d0024, 0xdc3c0007, 0x117d0024, 0xdc3807f1, 0x147d0024, 0xdc300400, 0x187d0024, 0xdc2807ff, - 0x197d0024, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, - 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 -}; - -constexpr std::array Region{ +inline constexpr std::array FlatGlobalLoadsRegion{ 0xd2, 0xf2, 0x26, 0x65, 0xa6, 0x0c, 0x12, 0xd2, 0x89, 0x18, 0x5d, 0x95, 0x0e, 0xe8, 0x81, 0x36, 0x09, 0x16, 0x6f, 0x6b, 0x11, 0x3d, 0x17, 0x8d, 0x6c, 0x0f, 0xd3, 0x90, 0x1f, 0xf2, 0x39, 0xa1, 0xa0, 0x95, 0xf2, 0x0f, 0x93, 0x95, 0x65, 0x0c, 0xf9, 0x38, 0x0b, 0x8e, 0xdb, 0x22, 0x4a, 0x6b, @@ -446,140 +391,4 @@ constexpr std::array Region{ 0xcd, 0x85, 0x85, 0x4d, 0x73, 0xa9, 0x16, 0x46, 0x65, 0x4a, 0xff, 0x72, 0xb1, 0x1c, 0x73, 0xa2 }; -struct Row { - std::array input; - std::array global; - std::array flat; -}; - -constexpr std::array Rows{{ - {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x000000d2, 0x00000029, 0x0000ebf6, 0x00004397, 0x4397357a, 0xf6804124, 0x97357aeb, 0x513c819d, 0x2e32095f, 0xefa22967, 0x6595f862, 0x295aec03, 0x523df3dc, 0x457d538e, 0x6526f2d2, 0x000000d2}, {0x000000d2, 0x0000007a, 0x000026f2, 0xffffa665, 0x0ca66526, 0xa66526f2, 0x89d2120c, 0x5d1889d2, 0x81e80e95, 0x6f160936, 0xe5522840, 0xd0e7469d, 0x80412474, 0x357aebf6, 0xfe1ec477, 0x0000357a}}, - {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x000000f2, 0xffffffdc, 0x00007aeb, 0xffff9d43, 0x9d439735, 0xebf68041, 0x4397357a, 0x5f513c81, 0x672e3209, 0x47efa229, 0x036595f8, 0xdc295aec, 0x8e523df3, 0x48457d53, 0xa66526f2, 0x000000f2}, {0x000000f2, 0x00000035, 0x00006526, 0x00000ca6, 0x120ca665, 0x0ca66526, 0x1889d212, 0x955d1889, 0x3681e80e, 0x6b6f1609, 0x9de55228, 0x74d0e746, 0xf6804124, 0x97357aeb, 0xe4fe1ec4, 0x00009735}}, - {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x00000026, 0xfffffff3, 0x0000357a, 0xffff819d, 0x819d4397, 0x7aebf680, 0x9d439735, 0x095f513c, 0x29672e32, 0xad47efa2, 0xec036595, 0xf3dc295a, 0x538e523d, 0xe048457d, 0x0ca66526, 0x00000026}, {0x000000a4, 0x0000005a, 0x0000449e, 0xffffadea, 0x9fadea44, 0xadea449e, 0x8aa0459f, 0xecce8aa0, 0x40199f09, 0x6f03851f, 0x97578278, 0x3c61fe8b, 0xbecaa1d3, 0xb65a60de, 0x37307a70, 0x0000b65a}}, - {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x00000065, 0x0000003d, 0x00009735, 0x00003c81, 0x3c819d43, 0x357aebf6, 0x819d4397, 0x32095f51, 0xa229672e, 0x53ad47ef, 0x5aec0365, 0x3df3dc29, 0x7d538e52, 0xfce04845, 0x120ca665, 0x00000065}, {0x000000dd, 0x00000060, 0x00009ea4, 0xffffea44, 0xadea449e, 0xea449ea4, 0xa0459fad, 0xce8aa045, 0x199f09ec, 0x03851f40, 0x578278da, 0x61fe8b97, 0xcaa1d33c, 0x5a60debe, 0x307a70d9, 0x00005a60}}, - {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x00000077, 0x00000072, 0x00007ea7, 0x00003154, 0x31541eca, 0xa734f958, 0x541eca7e, 0x204cb651, 0x16a2f996, 0x660affc8, 0x30d907a2, 0x72bdad38, 0xa92515b0, 0x948e5f94, 0xa2731cb1, 0x00000077}, {0x00000026, 0xffffff97, 0x0000a665, 0x0000120c, 0xd2120ca6, 0x120ca665, 0x5d1889d2, 0x0e955d18, 0x093681e8, 0x116b6f16, 0x469de552, 0x2474d0e7, 0xebf68041, 0x4397357a, 0x8ce4fe1e, 0x00004397}}, - {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, - {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x00000049, 0xffffffad, 0x000034f9, 0x00001eca, 0x1eca7ea7, 0xf95881df, 0xca7ea734, 0xb6513154, 0xf996204c, 0xffc816a2, 0x07a2e287, 0xad3830d9, 0x15b072bd, 0x5f94a925, 0x1cb172ff, 0x00000049}, {0x0000007a, 0x0000005a, 0x00009735, 0xffff9d43, 0x819d4397, 0x9d439735, 0x5f513c81, 0x32095f51, 0xa229672e, 0x53ad47ef, 0x553e074f, 0x8b833038, 0x6595f862, 0x295aec03, 0x5131541e, 0x0000295a}}, - {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x000000c6, 0xffffff91, 0x00002075, 0xffffb0c1, 0xb0c19888, 0x75f13ba1, 0xc1988820, 0x08a59ac0, 0x85539459, 0x73d7de27, 0x62c3c5bc, 0x91b7a807, 0x63034f25, 0x2db116b5, 0xdc295aec, 0x000000c6}, {0x00000097, 0xffffffdc, 0x00009d43, 0x00003c81, 0x513c819d, 0x3c819d43, 0x32095f51, 0x672e3209, 0x47efa229, 0x60e553ad, 0x3038553e, 0xf8628b83, 0xec036595, 0xf3dc295a, 0x4cb65131, 0x0000f3dc}}, - {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x000000f1, 0x00000054, 0x00001873, 0xffffdf25, 0xdf25a310, 0x7326b53e, 0x25a31018, 0x6645c8aa, 0x02f743cf, 0x8fd2a50e, 0x3b946093, 0x54ebd216, 0x9bd7f852, 0x53f53ed6, 0x7dee2657, 0x000000f1}, {0x00000000, 0x00000058, 0x00003386, 0xffffc349, 0xc0c34933, 0xc3493386, 0x010dfac0, 0x7d59010d, 0xcbb17d18, 0x7ef72fd3, 0x1ff610a6, 0x59e9d36c, 0x30633e8d, 0x3c584877, 0xb4de5e8e, 0x00003c58}}, - {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x000000a8, 0x0000002d, 0x0000a25c, 0xffff9c05, 0x9c0507a5, 0x5ca32e13, 0x0507a5a2, 0xeebcae0b, 0xfbcf54ff, 0x7c7b8218, 0x02701056, 0x2dd3f4aa, 0x602ab9e7, 0xcd71014b, 0xf610a667, 0x000000a8}, {0x00000021, 0xffffff8b, 0x0000d19a, 0xffff82a1, 0xc782a1d1, 0x82a1d19a, 0xb41ce3c7, 0x3b5db41c, 0x7ce0e57f, 0x00280664, 0x300b011b, 0x9b79ebd2, 0xc90fa8c4, 0x9c8be880, 0x401ae5e1, 0x00009c8b}}, - {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x000000d7, 0xffffffb6, 0x00004159, 0xffffb70c, 0xb70c48ee, 0x5905426c, 0x0c48ee41, 0x52e95ec2, 0x809af6c4, 0x9e49d979, 0x693895b7, 0xb6e0664f, 0xdeca057c, 0x2b2c163e, 0x3e052289, 0x000000d7}, {0x000000fa, 0x0000003d, 0x000017c8, 0xffffe6ab, 0xe4e6ab17, 0xe6ab17c8, 0xa2c98ce4, 0x4cd6a2c9, 0x36b17e32, 0xd6bd1487, 0x5bd3a52f, 0x225e3a51, 0xd65eeb8d, 0x0e3d40d4, 0x3f5aefae, 0x00000e3d}}, - {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x0000003b, 0x00000015, 0x00007b82, 0x000024e5, 0x24e5c17c, 0x8218fbcf, 0xe5c17c7b, 0x6ab73608, 0x185620a0, 0x575da8dc, 0x59ac90cd, 0x15783291, 0x7584528a, 0xe888f86d, 0xdebaea1d, 0x0000003b}, {0x0000008d, 0x00000074, 0x0000538b, 0xfffffa29, 0x6dfa2953, 0xfa29538b, 0xfc1ae26d, 0x4312fc1a, 0x1835159f, 0xb5fd7f6b, 0x4506273f, 0x554bd692, 0x7d422acd, 0xe774511b, 0xda660308, 0x0000e774}}, - {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, - {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x00000021, 0x00000071, 0x00007769, 0xffff8cc2, 0x8cc24bff, 0x69d0cbd1, 0xc24bff77, 0xd5c720a6, 0xd9c85a78, 0x60b4443a, 0xf6724fb3, 0x7153291c, 0x4a5c9177, 0x0b128e2b, 0x7f9859b1, 0x00000021}, {0x0000005d, 0x00000066, 0x00007957, 0xffff86c7, 0x8d86c779, 0x86c77957, 0x35e9c58d, 0x6f4835e9, 0x8d406c57, 0x4a4ad30d, 0x528a1578, 0xf86d7584, 0xdda0e888, 0xf666f927, 0xb19c74d6, 0x0000f666}}, - {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x00000011, 0x00000016, 0x0000b53e, 0x00001018, 0x10187326, 0x3ecdef03, 0x187326b5, 0xaadf25a3, 0xcf6645c8, 0x0e02f743, 0x936f25cd, 0x163b9460, 0x5254ebd2, 0xd69bd7f8, 0x224cb259, 0x00000011}, {0x00000010, 0x0000006e, 0x0000fc1c, 0xffffc9eb, 0x3ac9ebfc, 0xc9ebfc1c, 0x1ae0f83a, 0x43151ae0, 0xc7e70a45, 0x21c1452e, 0xc6e0e1a7, 0xda95b3a4, 0xa42ead3a, 0x506e741f, 0x3c3e2f3f, 0x0000506e}}, - {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x00000065, 0xffffff90, 0x0000764b, 0xffff9a41, 0x9a412405, 0x4ba1fb80, 0x41240576, 0xbd0167bc, 0x6edae83e, 0xbf9692b3, 0xa14f8d0a, 0x902df1a3, 0x7f913ad6, 0xec4185b7, 0x99571c29, 0x00000065}, {0x00000015, 0xffffffbe, 0x000078a4, 0xffffe536, 0x26e53678, 0xe53678a4, 0x6d03a026, 0x02016d03, 0xfc1fabaf, 0x3716dbf7, 0xfd9f16e3, 0x1d9033df, 0x5adebaea, 0xd7be5d2b, 0x936f25cd, 0x0000d7be}}, - {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x00000053, 0x00000067, 0x00004431, 0xffffd3b4, 0xd3b4c14d, 0x31813cb2, 0xb4c14d44, 0xb9279ed7, 0xb93ff927, 0x59859a53, 0xb7d12049, 0x675b4866, 0xc676e8d8, 0xdca0e1a0, 0x034ab01d, 0x00000053}, {0x000000a0, 0x0000006b, 0x00004ec7, 0xffffeba6, 0x4eeba64e, 0xeba64ec7, 0x6c3f954e, 0xa8636c3f, 0x7080725e, 0x9e00052d, 0x871b5d02, 0x6e53112f, 0x05ab81b3, 0xf86b2339, 0x6b431e8e, 0x0000f86b}}, - {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x000000fd, 0x00000072, 0x0000d4a3, 0x000069d0, 0x69d0cbd1, 0xa3aa5a25, 0xd0cbd1d4, 0xc24bff77, 0xc720a68c, 0xc85a78d5, 0x324d47e5, 0x724fb31c, 0x53291cf6, 0x5c917771, 0x15602efe, 0x000000fd}, {0x000000c8, 0xffffffd3, 0x00003527, 0x000067e7, 0xca67e735, 0x67e73527, 0x9c2a88ca, 0xb0e49c2a, 0x17c8fa9b, 0x8ce4e6ab, 0x1a2baeed, 0x4fd4b8c8, 0x61539dbe, 0x5bd3a52f, 0xd052e9fc, 0x00005bd3}}, - {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x00000086, 0xfffffffe, 0x000070d6, 0xffff8e1d, 0x8e1de1ab, 0xd6bd1487, 0x1de1ab70, 0x3b6b431e, 0x7e7923d3, 0x777b0e8e, 0x6c7e37f1, 0xfe28bb7f, 0xa0949a4c, 0x3ab02414, 0x65d656c0, 0x00000086}, {0x000000cd, 0xfffffffd, 0x0000e9fc, 0xffffd052, 0x66d052e9, 0xd052e9fc, 0x0d8fd866, 0x84530d8f, 0xefaef525, 0x6cde3f5a, 0x68e162c2, 0xffe524cd, 0x9b3d01a2, 0x41fded80, 0x3a515bd3, 0x000041fd}}, - {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, - {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x00000019, 0x00000057, 0x00005567, 0xffffdf45, 0xdf45fb80, 0x677ed35a, 0x45fb8055, 0x34f95881, 0x1eca7ea7, 0xb6513154, 0xa5a87b5e, 0x57dd4c23, 0x07a2e287, 0xad3830d9, 0xd14b0cf5, 0x00000019}, {0x000000fc, 0xffffffb0, 0x0000267e, 0xffffb948, 0x9eb94826, 0xb948267e, 0x0b25a49e, 0xd5d30b25, 0xa083e4b7, 0xcfb3bb6d, 0x5873c62f, 0xe73527c8, 0x2a88ca67, 0x9bb0e49c, 0xf83f261f, 0x00009bb0}}, - {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x0000009e, 0xffffff91, 0x000017c8, 0xffff8ce4, 0x8ce4e6ab, 0xc8fa9bb0, 0xe4e6ab17, 0x4cd6a2c9, 0x36b17e32, 0xd6bd1487, 0x3d40d4d6, 0x911b0a0e, 0xd1eba0cd, 0xe767b4ff, 0x1d426570, 0x0000009e}, {0x00000079, 0xffffffba, 0x000069c5, 0x00001a8c, 0x151a8c69, 0x1a8c69c5, 0x3678a415, 0x26e53678, 0x016d03a0, 0x1fabaf02, 0x6d22b51e, 0x9f16e3e8, 0x9033dffd, 0xdebaea1d, 0x892f4c71, 0x0000deba}}, - {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x00000065, 0xffffff90, 0x0000764b, 0xffff9a41, 0x9a412405, 0x4ba1fb80, 0x41240576, 0xbd0167bc, 0x6edae83e, 0xbf9692b3, 0xa14f8d0a, 0x902df1a3, 0x7f913ad6, 0xec4185b7, 0x79fec2ec, 0x00000065}, {0x00000013, 0x00000033, 0x0000835f, 0x00002ddd, 0x722ddd83, 0x2ddd835f, 0xc6429a72, 0xaac7c642, 0xa31b01f2, 0x599eb598, 0x2c46c27e, 0xab9c2334, 0x31cfa0b5, 0x02334e95, 0x19598433, 0x00000233}}, - {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x000000a2, 0x0000001c, 0x00005a25, 0xffffd1d4, 0xd1d4a3aa, 0x25f6d039, 0xd4a3aa5a, 0x7769d0cb, 0x8cc24bff, 0xd5c720a6, 0xe50f0b34, 0x1c324d47, 0xf6724fb3, 0x7153291c, 0x524da73e, 0x000000a2}, {0x000000ca, 0x0000001b, 0x00006436, 0xffff959f, 0x13959f64, 0x959f6436, 0x2be99013, 0x08252be9, 0x9f1b1c06, 0xfa5829ed, 0x24ff8388, 0x9a693293, 0x8428251f, 0x071b82c2, 0x1588587f, 0x0000071b}}, - {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x0000008c, 0x00000017, 0x00006d7c, 0xffffa815, 0xa815a39f, 0x7c7a524d, 0x15a39f6d, 0xed275ce5, 0x0e62da4d, 0xe790d315, 0x528bc830, 0x17eb5b02, 0x9da049a4, 0xb3a7bbef, 0x660a3790, 0x0000008c}, {0x00000088, 0x0000000d, 0x0000aae4, 0xffffe110, 0xdee110aa, 0xe110aae4, 0xb6eac1de, 0x62f1b6ea, 0x34343f1b, 0xf308081c, 0xdb19a044, 0x1095f2e7, 0x9f733159, 0x380d0562, 0xe242e585, 0x0000380d}}, - {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x00000047, 0x0000000d, 0x0000d463, 0x00001bf9, 0x1bf9e87d, 0x6302c753, 0xf9e87dd4, 0x378b4009, 0xf3c8b729, 0x598433f0, 0xa0d6d4b0, 0x0d8273a0, 0x6d34b2ad, 0xedd883ac, 0xaedff3c7, 0x00000047}, {0x00000013, 0xffffffef, 0x0000ea6b, 0xffffa06b, 0xb2a06bea, 0xa06bea6b, 0x945aacb2, 0x1b43945a, 0x66bd4d39, 0xf886f4f0, 0x7cb4af22, 0xb43c7bab, 0xb183013d, 0xa4ef2271, 0x72ee855b, 0x0000a4ef}}, - {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, - {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x000000c7, 0xffffffdb, 0x0000b20a, 0x0000100b, 0x100bbc11, 0x0aa2a4b1, 0x0bbc11b2, 0x5dc397db, 0xd1f4d133, 0x10aae488, 0x082b465a, 0xdb19a044, 0x1095f2e7, 0x9f733159, 0x92b36eda, 0x000000c7}, {0x000000af, 0x00000015, 0x0000fb40, 0xffffad6d, 0x2fad6dfb, 0xad6dfb40, 0xce007b2f, 0xccb8ce00, 0xa73e5b47, 0x7c7a524d, 0x4a5c9177, 0x0b128e2b, 0xfafd7702, 0xbf157cc0, 0xde591a71, 0x0000bf15}}, - {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x00000025, 0x0000002b, 0x00006fd8, 0x00001477, 0x1477e9be, 0xd8600189, 0x77e9be6f, 0x2c73a7bd, 0x421aff39, 0x1f09a43b, 0x87b15796, 0x2b17d0bf, 0xfa5d515c, 0x834fd313, 0x63779354, 0x00000025}, {0x00000073, 0x0000002f, 0x0000c858, 0x00003527, 0xe73527c8, 0x3527c858, 0x88ca67e7, 0x9c2a88ca, 0xfa9bb0e4, 0xe6ab17c8, 0xaeede5d8, 0xb8c81a2b, 0x9dbe4fd4, 0xa52f6153, 0xe9fccd90, 0x0000a52f}}, - {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x000000c2, 0xffffffd1, 0x0000e2a4, 0x00003181, 0x31813cb2, 0xa4765520, 0x813cb2e2, 0xb4c14d44, 0x279ed7d3, 0x3ff927b9, 0x164fb710, 0xd1204939, 0x5b4866b7, 0x76e8d867, 0x764984b5, 0x000000c2}, {0x0000009d, 0xffffffde, 0x000023c0, 0xffffdea4, 0x49dea423, 0xdea423c0, 0xe90c7c49, 0x8cede90c, 0x6a782b20, 0x414c4857, 0x6d03a026, 0xabaf0201, 0xdbf7fc1f, 0x1fde3716, 0xa50e02f7, 0x00001fde}}, - {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x00000049, 0xfffffff9, 0x00001856, 0x0000575d, 0x575da8dc, 0x5620a06a, 0x5da8dc18, 0x8d86c779, 0x4835e9c5, 0x406c576f, 0xe888f86d, 0xf927dda0, 0x9e9bf666, 0x0fcfcf14, 0x1d9033df, 0x00000049}, {0x000000fe, 0xffffff9a, 0x0000602e, 0x00000715, 0x0d071560, 0x0715602e, 0x5e8e080d, 0xb4de5e8e, 0xd8f27c75, 0xdc10e5e8, 0xa02ac205, 0x05b43c44, 0x72660c37, 0x489ae433, 0x30633e8d, 0x0000489a}}, - {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} -}}; - -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, "flat global loads: cannot allocate the guest block"); - GuestAllocations::Mutation().Add(block, BlockBytes, true, false); - } - - ~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::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%x", value); - return text; -} - -void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { - const auto address = static_cast(reinterpret_cast(base)); - std::vector userData(10, 0u); - const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); - std::copy(output.begin(), output.end(), userData.begin() + 4); - userData[8] = static_cast(address); - userData[9] = static_cast(address >> 32u); - const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; - const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {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; - const auto result = ShaderRecompiler::Recompile(request); - device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); - device.WaitIdle(); -} - -void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code, bool flat) { - Output.fill(Sentinel); - Dispatch(device, code, guest.Data()); - const auto wave = std::string(flat ? "flat" : "global") + " loads: lane "; - for (std::uint32_t tid = 0; tid < Threads; ++tid) { - const auto& expected = flat ? Rows[tid].flat : Rows[tid].global; - for (std::uint32_t result = 0; result < Results; ++result) { - const auto actual = Output[tid * Results + result]; - Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); - } - } -} - -} - -int main() { - try { - const auto device = OpenVulkanTestDevice(); - if (!device) return VulkanTestSkipped; - GuestBlock guest; - std::memset(guest.Data(), 0, BlockBytes); - for (std::uint32_t tid = 0; tid < Threads; ++tid) { - std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); - } - std::memcpy(guest.Data() + RegionOffset, Region.data(), Region.size()); - Run(*device, guest, GlobalLoadsCode, false); - Run(*device, guest, FlatLoadsCode, true); - std::puts("flat global loads tests passed"); - return 0; - } catch (const std::exception& error) { - std::cerr << error.what() << '\n'; - return 1; - } -} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoads.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoads.cpp new file mode 100644 index 000000000..6e3677e26 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoads.cpp @@ -0,0 +1,182 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.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::uint32_t Threads = 32; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc200000, 0x0a7d0024, 0xdc2407ff, + 0x0b7d0024, 0xdc280001, 0x0c7d0024, 0xdc2c0003, 0x0d7d0024, 0xdc300002, 0x0e7d0024, 0xdc300400, + 0x187d0024, 0xdc2807ff, 0x197d0024, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, + 0x80010e02, 0xe0781020, 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x000000d2, 0x0000007a, 0x000026f2, 0xffffa665, 0x0ca66526, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xfe1ec477, 0x0000357a}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x000000f2, 0x00000035, 0x00006526, 0x00000ca6, 0x120ca665, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xe4fe1ec4, 0x00009735}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x000000a4, 0x0000005a, 0x0000449e, 0xffffadea, 0x9fadea44, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x37307a70, 0x0000b65a}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x000000dd, 0x00000060, 0x00009ea4, 0xffffea44, 0xadea449e, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x307a70d9, 0x00005a60}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x00000026, 0xffffff97, 0x0000a665, 0x0000120c, 0xd2120ca6, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x8ce4fe1e, 0x00004397}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x0000007a, 0x0000005a, 0x00009735, 0xffff9d43, 0x819d4397, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5131541e, 0x0000295a}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x00000097, 0xffffffdc, 0x00009d43, 0x00003c81, 0x513c819d, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x4cb65131, 0x0000f3dc}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x00000000, 0x00000058, 0x00003386, 0xffffc349, 0xc0c34933, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xb4de5e8e, 0x00003c58}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x00000021, 0xffffff8b, 0x0000d19a, 0xffff82a1, 0xc782a1d1, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x401ae5e1, 0x00009c8b}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x000000fa, 0x0000003d, 0x000017c8, 0xffffe6ab, 0xe4e6ab17, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x3f5aefae, 0x00000e3d}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x0000008d, 0x00000074, 0x0000538b, 0xfffffa29, 0x6dfa2953, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xda660308, 0x0000e774}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x0000005d, 0x00000066, 0x00007957, 0xffff86c7, 0x8d86c779, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xb19c74d6, 0x0000f666}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x00000010, 0x0000006e, 0x0000fc1c, 0xffffc9eb, 0x3ac9ebfc, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x3c3e2f3f, 0x0000506e}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x00000015, 0xffffffbe, 0x000078a4, 0xffffe536, 0x26e53678, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x936f25cd, 0x0000d7be}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x000000a0, 0x0000006b, 0x00004ec7, 0xffffeba6, 0x4eeba64e, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x6b431e8e, 0x0000f86b}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x000000c8, 0xffffffd3, 0x00003527, 0x000067e7, 0xca67e735, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xd052e9fc, 0x00005bd3}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x000000cd, 0xfffffffd, 0x0000e9fc, 0xffffd052, 0x66d052e9, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x3a515bd3, 0x000041fd}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x000000fc, 0xffffffb0, 0x0000267e, 0xffffb948, 0x9eb94826, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xf83f261f, 0x00009bb0}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x00000079, 0xffffffba, 0x000069c5, 0x00001a8c, 0x151a8c69, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x892f4c71, 0x0000deba}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x00000013, 0x00000033, 0x0000835f, 0x00002ddd, 0x722ddd83, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x19598433, 0x00000233}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x000000ca, 0x0000001b, 0x00006436, 0xffff959f, 0x13959f64, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x1588587f, 0x0000071b}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x00000088, 0x0000000d, 0x0000aae4, 0xffffe110, 0xdee110aa, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xe242e585, 0x0000380d}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x00000013, 0xffffffef, 0x0000ea6b, 0xffffa06b, 0xb2a06bea, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x72ee855b, 0x0000a4ef}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x000000af, 0x00000015, 0x0000fb40, 0xffffad6d, 0x2fad6dfb, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xde591a71, 0x0000bf15}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x00000073, 0x0000002f, 0x0000c858, 0x00003527, 0xe73527c8, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xe9fccd90, 0x0000a52f}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x0000009d, 0xffffffde, 0x000023c0, 0xffffdea4, 0x49dea423, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xa50e02f7, 0x00001fde}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x000000fe, 0xffffff9a, 0x0000602e, 0x00000715, 0x0d071560, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x30633e8d, 0x0000489a}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +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, "flat loads: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~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::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%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "flat loads: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("flat loads tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX2X3.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX2X3.cpp new file mode 100644 index 000000000..2642ebc78 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX2X3.cpp @@ -0,0 +1,181 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.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::uint32_t Threads = 32; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc340001, 0x0f7d0024, 0xdc3c0007, + 0x117d0024, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, + 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xa66526f2, 0x89d2120c, 0x5d1889d2, 0x81e80e95, 0x6f160936, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x0ca66526, 0x1889d212, 0x955d1889, 0x3681e80e, 0x6b6f1609, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xadea449e, 0x8aa0459f, 0xecce8aa0, 0x40199f09, 0x6f03851f, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xea449ea4, 0xa0459fad, 0xce8aa045, 0x199f09ec, 0x03851f40, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x120ca665, 0x5d1889d2, 0x0e955d18, 0x093681e8, 0x116b6f16, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x9d439735, 0x5f513c81, 0x32095f51, 0xa229672e, 0x53ad47ef, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x3c819d43, 0x32095f51, 0x672e3209, 0x47efa229, 0x60e553ad, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xc3493386, 0x010dfac0, 0x7d59010d, 0xcbb17d18, 0x7ef72fd3, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x82a1d19a, 0xb41ce3c7, 0x3b5db41c, 0x7ce0e57f, 0x00280664, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xe6ab17c8, 0xa2c98ce4, 0x4cd6a2c9, 0x36b17e32, 0xd6bd1487, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xfa29538b, 0xfc1ae26d, 0x4312fc1a, 0x1835159f, 0xb5fd7f6b, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x86c77957, 0x35e9c58d, 0x6f4835e9, 0x8d406c57, 0x4a4ad30d, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xc9ebfc1c, 0x1ae0f83a, 0x43151ae0, 0xc7e70a45, 0x21c1452e, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xe53678a4, 0x6d03a026, 0x02016d03, 0xfc1fabaf, 0x3716dbf7, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xeba64ec7, 0x6c3f954e, 0xa8636c3f, 0x7080725e, 0x9e00052d, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x67e73527, 0x9c2a88ca, 0xb0e49c2a, 0x17c8fa9b, 0x8ce4e6ab, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xd052e9fc, 0x0d8fd866, 0x84530d8f, 0xefaef525, 0x6cde3f5a, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xb948267e, 0x0b25a49e, 0xd5d30b25, 0xa083e4b7, 0xcfb3bb6d, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x1a8c69c5, 0x3678a415, 0x26e53678, 0x016d03a0, 0x1fabaf02, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x2ddd835f, 0xc6429a72, 0xaac7c642, 0xa31b01f2, 0x599eb598, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x959f6436, 0x2be99013, 0x08252be9, 0x9f1b1c06, 0xfa5829ed, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xe110aae4, 0xb6eac1de, 0x62f1b6ea, 0x34343f1b, 0xf308081c, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xa06bea6b, 0x945aacb2, 0x1b43945a, 0x66bd4d39, 0xf886f4f0, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xad6dfb40, 0xce007b2f, 0xccb8ce00, 0xa73e5b47, 0x7c7a524d, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x3527c858, 0x88ca67e7, 0x9c2a88ca, 0xfa9bb0e4, 0xe6ab17c8, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xdea423c0, 0xe90c7c49, 0x8cede90c, 0x6a782b20, 0x414c4857, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x0715602e, 0x5e8e080d, 0xb4de5e8e, 0xd8f27c75, 0xdc10e5e8, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +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, "flat loads x2 x3: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~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::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%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "flat loads x2 x3: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("flat loads x2 x3 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX4.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX4.cpp new file mode 100644 index 000000000..008004a55 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX4.cpp @@ -0,0 +1,181 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.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::uint32_t Threads = 32; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc3807f1, 0x147d0024, 0xbf8c0070, + 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, 0x80011202, 0xe0781030, + 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xe5522840, 0xd0e7469d, 0x80412474, 0x357aebf6, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x9de55228, 0x74d0e746, 0xf6804124, 0x97357aeb, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x97578278, 0x3c61fe8b, 0xbecaa1d3, 0xb65a60de, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x578278da, 0x61fe8b97, 0xcaa1d33c, 0x5a60debe, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x469de552, 0x2474d0e7, 0xebf68041, 0x4397357a, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x553e074f, 0x8b833038, 0x6595f862, 0x295aec03, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x3038553e, 0xf8628b83, 0xec036595, 0xf3dc295a, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x1ff610a6, 0x59e9d36c, 0x30633e8d, 0x3c584877, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x300b011b, 0x9b79ebd2, 0xc90fa8c4, 0x9c8be880, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5bd3a52f, 0x225e3a51, 0xd65eeb8d, 0x0e3d40d4, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x4506273f, 0x554bd692, 0x7d422acd, 0xe774511b, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x528a1578, 0xf86d7584, 0xdda0e888, 0xf666f927, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xc6e0e1a7, 0xda95b3a4, 0xa42ead3a, 0x506e741f, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xfd9f16e3, 0x1d9033df, 0x5adebaea, 0xd7be5d2b, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x871b5d02, 0x6e53112f, 0x05ab81b3, 0xf86b2339, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x1a2baeed, 0x4fd4b8c8, 0x61539dbe, 0x5bd3a52f, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x68e162c2, 0xffe524cd, 0x9b3d01a2, 0x41fded80, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5873c62f, 0xe73527c8, 0x2a88ca67, 0x9bb0e49c, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x6d22b51e, 0x9f16e3e8, 0x9033dffd, 0xdebaea1d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x2c46c27e, 0xab9c2334, 0x31cfa0b5, 0x02334e95, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x24ff8388, 0x9a693293, 0x8428251f, 0x071b82c2, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xdb19a044, 0x1095f2e7, 0x9f733159, 0x380d0562, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x7cb4af22, 0xb43c7bab, 0xb183013d, 0xa4ef2271, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x4a5c9177, 0x0b128e2b, 0xfafd7702, 0xbf157cc0, 0x5a5a000e, 0x5a5a000f}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xaeede5d8, 0xb8c81a2b, 0x9dbe4fd4, 0xa52f6153, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x6d03a026, 0xabaf0201, 0xdbf7fc1f, 0x1fde3716, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xa02ac205, 0x05b43c44, 0x72660c37, 0x489ae433, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +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, "flat loads x4: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~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::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%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "flat loads x4: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("flat loads x4 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoads.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoads.cpp new file mode 100644 index 000000000..ad91fd604 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoads.cpp @@ -0,0 +1,182 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.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::uint32_t Threads = 32; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc208800, 0x0a080020, 0xdc2487ff, + 0x0b080020, 0xdc288ffd, 0x0c080020, 0xdc2c8001, 0x0d7d0022, 0xdc308fff, 0x0e080020, 0xdc308000, + 0x18080021, 0xdc208800, 0x197d0022, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, + 0x80010e02, 0xe0781020, 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x000000d2, 0x00000029, 0x0000ebf6, 0x00004397, 0x4397357a, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x6526f2d2, 0x000000d2}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x000000f2, 0xffffffdc, 0x00007aeb, 0xffff9d43, 0x9d439735, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xa66526f2, 0x000000f2}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x00000026, 0xfffffff3, 0x0000357a, 0xffff819d, 0x819d4397, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x0ca66526, 0x00000026}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x00000065, 0x0000003d, 0x00009735, 0x00003c81, 0x3c819d43, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x120ca665, 0x00000065}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x00000077, 0x00000072, 0x00007ea7, 0x00003154, 0x31541eca, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xa2731cb1, 0x00000077}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x00000049, 0xffffffad, 0x000034f9, 0x00001eca, 0x1eca7ea7, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x1cb172ff, 0x00000049}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x000000c6, 0xffffff91, 0x00002075, 0xffffb0c1, 0xb0c19888, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xdc295aec, 0x000000c6}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x000000f1, 0x00000054, 0x00001873, 0xffffdf25, 0xdf25a310, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x7dee2657, 0x000000f1}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x000000a8, 0x0000002d, 0x0000a25c, 0xffff9c05, 0x9c0507a5, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xf610a667, 0x000000a8}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x000000d7, 0xffffffb6, 0x00004159, 0xffffb70c, 0xb70c48ee, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x3e052289, 0x000000d7}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x0000003b, 0x00000015, 0x00007b82, 0x000024e5, 0x24e5c17c, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xdebaea1d, 0x0000003b}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x00000021, 0x00000071, 0x00007769, 0xffff8cc2, 0x8cc24bff, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x7f9859b1, 0x00000021}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x00000011, 0x00000016, 0x0000b53e, 0x00001018, 0x10187326, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x224cb259, 0x00000011}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x00000065, 0xffffff90, 0x0000764b, 0xffff9a41, 0x9a412405, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x99571c29, 0x00000065}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x00000053, 0x00000067, 0x00004431, 0xffffd3b4, 0xd3b4c14d, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x034ab01d, 0x00000053}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x000000fd, 0x00000072, 0x0000d4a3, 0x000069d0, 0x69d0cbd1, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x15602efe, 0x000000fd}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x00000086, 0xfffffffe, 0x000070d6, 0xffff8e1d, 0x8e1de1ab, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x65d656c0, 0x00000086}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x00000019, 0x00000057, 0x00005567, 0xffffdf45, 0xdf45fb80, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xd14b0cf5, 0x00000019}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x0000009e, 0xffffff91, 0x000017c8, 0xffff8ce4, 0x8ce4e6ab, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x1d426570, 0x0000009e}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x00000065, 0xffffff90, 0x0000764b, 0xffff9a41, 0x9a412405, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x79fec2ec, 0x00000065}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x000000a2, 0x0000001c, 0x00005a25, 0xffffd1d4, 0xd1d4a3aa, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x524da73e, 0x000000a2}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x0000008c, 0x00000017, 0x00006d7c, 0xffffa815, 0xa815a39f, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x660a3790, 0x0000008c}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x00000047, 0x0000000d, 0x0000d463, 0x00001bf9, 0x1bf9e87d, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xaedff3c7, 0x00000047}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x000000c7, 0xffffffdb, 0x0000b20a, 0x0000100b, 0x100bbc11, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x92b36eda, 0x000000c7}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x00000025, 0x0000002b, 0x00006fd8, 0x00001477, 0x1477e9be, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x63779354, 0x00000025}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x000000c2, 0xffffffd1, 0x0000e2a4, 0x00003181, 0x31813cb2, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x764984b5, 0x000000c2}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x00000049, 0xfffffff9, 0x00001856, 0x0000575d, 0x575da8dc, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x1d9033df, 0x00000049}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +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 loads: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~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::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%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "global loads: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("global loads tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX2X3.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX2X3.cpp new file mode 100644 index 000000000..49ee77d74 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX2X3.cpp @@ -0,0 +1,181 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.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::uint32_t Threads = 32; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc348ffa, 0x0f7d0022, 0xdc3c8003, + 0x11080020, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, + 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xf6804124, 0x97357aeb, 0x513c819d, 0x2e32095f, 0xefa22967, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xebf68041, 0x4397357a, 0x5f513c81, 0x672e3209, 0x47efa229, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x7aebf680, 0x9d439735, 0x095f513c, 0x29672e32, 0xad47efa2, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x357aebf6, 0x819d4397, 0x32095f51, 0xa229672e, 0x53ad47ef, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xa734f958, 0x541eca7e, 0x204cb651, 0x16a2f996, 0x660affc8, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xf95881df, 0xca7ea734, 0xb6513154, 0xf996204c, 0xffc816a2, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x75f13ba1, 0xc1988820, 0x08a59ac0, 0x85539459, 0x73d7de27, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x7326b53e, 0x25a31018, 0x6645c8aa, 0x02f743cf, 0x8fd2a50e, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5ca32e13, 0x0507a5a2, 0xeebcae0b, 0xfbcf54ff, 0x7c7b8218, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5905426c, 0x0c48ee41, 0x52e95ec2, 0x809af6c4, 0x9e49d979, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x8218fbcf, 0xe5c17c7b, 0x6ab73608, 0x185620a0, 0x575da8dc, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x69d0cbd1, 0xc24bff77, 0xd5c720a6, 0xd9c85a78, 0x60b4443a, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x3ecdef03, 0x187326b5, 0xaadf25a3, 0xcf6645c8, 0x0e02f743, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x4ba1fb80, 0x41240576, 0xbd0167bc, 0x6edae83e, 0xbf9692b3, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x31813cb2, 0xb4c14d44, 0xb9279ed7, 0xb93ff927, 0x59859a53, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xa3aa5a25, 0xd0cbd1d4, 0xc24bff77, 0xc720a68c, 0xc85a78d5, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xd6bd1487, 0x1de1ab70, 0x3b6b431e, 0x7e7923d3, 0x777b0e8e, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x677ed35a, 0x45fb8055, 0x34f95881, 0x1eca7ea7, 0xb6513154, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xc8fa9bb0, 0xe4e6ab17, 0x4cd6a2c9, 0x36b17e32, 0xd6bd1487, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x4ba1fb80, 0x41240576, 0xbd0167bc, 0x6edae83e, 0xbf9692b3, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x25f6d039, 0xd4a3aa5a, 0x7769d0cb, 0x8cc24bff, 0xd5c720a6, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x7c7a524d, 0x15a39f6d, 0xed275ce5, 0x0e62da4d, 0xe790d315, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x6302c753, 0xf9e87dd4, 0x378b4009, 0xf3c8b729, 0x598433f0, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x0aa2a4b1, 0x0bbc11b2, 0x5dc397db, 0xd1f4d133, 0x10aae488, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xd8600189, 0x77e9be6f, 0x2c73a7bd, 0x421aff39, 0x1f09a43b, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xa4765520, 0x813cb2e2, 0xb4c14d44, 0x279ed7d3, 0x3ff927b9, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5620a06a, 0x5da8dc18, 0x8d86c779, 0x4835e9c5, 0x406c576f, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +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 loads x2 x3: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~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::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%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "global loads x2 x3: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("global loads x2 x3 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX4.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX4.cpp new file mode 100644 index 000000000..d0bb6392d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX4.cpp @@ -0,0 +1,181 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.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::uint32_t Threads = 32; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc3887f8, 0x147d0022, 0xbf8c0070, + 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, 0x80011202, 0xe0781030, + 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x6595f862, 0x295aec03, 0x523df3dc, 0x457d538e, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x036595f8, 0xdc295aec, 0x8e523df3, 0x48457d53, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xec036595, 0xf3dc295a, 0x538e523d, 0xe048457d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5aec0365, 0x3df3dc29, 0x7d538e52, 0xfce04845, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x30d907a2, 0x72bdad38, 0xa92515b0, 0x948e5f94, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x07a2e287, 0xad3830d9, 0x15b072bd, 0x5f94a925, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x62c3c5bc, 0x91b7a807, 0x63034f25, 0x2db116b5, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x3b946093, 0x54ebd216, 0x9bd7f852, 0x53f53ed6, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x02701056, 0x2dd3f4aa, 0x602ab9e7, 0xcd71014b, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x693895b7, 0xb6e0664f, 0xdeca057c, 0x2b2c163e, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x59ac90cd, 0x15783291, 0x7584528a, 0xe888f86d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xf6724fb3, 0x7153291c, 0x4a5c9177, 0x0b128e2b, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x936f25cd, 0x163b9460, 0x5254ebd2, 0xd69bd7f8, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xa14f8d0a, 0x902df1a3, 0x7f913ad6, 0xec4185b7, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xb7d12049, 0x675b4866, 0xc676e8d8, 0xdca0e1a0, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x324d47e5, 0x724fb31c, 0x53291cf6, 0x5c917771, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x6c7e37f1, 0xfe28bb7f, 0xa0949a4c, 0x3ab02414, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xa5a87b5e, 0x57dd4c23, 0x07a2e287, 0xad3830d9, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x3d40d4d6, 0x911b0a0e, 0xd1eba0cd, 0xe767b4ff, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xa14f8d0a, 0x902df1a3, 0x7f913ad6, 0xec4185b7, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xe50f0b34, 0x1c324d47, 0xf6724fb3, 0x7153291c, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x528bc830, 0x17eb5b02, 0x9da049a4, 0xb3a7bbef, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xa0d6d4b0, 0x0d8273a0, 0x6d34b2ad, 0xedd883ac, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x082b465a, 0xdb19a044, 0x1095f2e7, 0x9f733159, 0x5a5a000e, 0x5a5a000f}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x87b15796, 0x2b17d0bf, 0xfa5d515c, 0x834fd313, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x164fb710, 0xd1204939, 0x5b4866b7, 0x76e8d867, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xe888f86d, 0xf927dda0, 0x9e9bf666, 0x0fcfcf14, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +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 loads x4: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~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::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%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "global loads x4: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("global loads x4 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} From 2eff3c838613afcc8d7f388d6e813215c2ecadfd Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 16:35:29 +0200 Subject: [PATCH 126/164] feat(agc): accept linear attribute clipping in PA_CL_CLIP_CNTL DecodeState and the draw precheck rejected PA_CL_CLIP_CNTL bit 24 (DX_LINEAR_ATTR_CLIP_ENA), so every draw that sets it was skipped. Vulkan requires clipped NoPerspective outputs to vary linearly in framebuffer space, and RADV and radeonsi set this bit for every draw they emit. With the bit set the console therefore clips the way the host driver already does, and the pipeline needs no extra state. Clip space, depth clamping and the viewport depth range decode as with the bit clear. Every other unsupported bit still rejects the draw, with or without bit 24. TechnicalDebt records that a clear bit, which was already accepted, gets the same clipping. Co-Authored-By: Claude Opus 5.5 --- .../libSceAgcDriver/Graphics/src/State.cpp | 2 +- .../prx/libSceAgcDriver/tests/Graphics.cpp | 20 ++++++++++++++++--- docs/dev/TechnicalDebt.md | 1 + 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index 48b9a1e9c..c8d156bf5 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -70,7 +70,7 @@ constexpr std::uint32_t ScanModeMask = ~2u; constexpr std::uint32_t ScanControlMask = ~0x06003fffu; constexpr std::uint32_t ScreenOffsetMask = ~0x01ff01ffu; // Bits 26/27 (ZCLIP_NEAR/FAR_DISABLE) become depth clamping; bit 19 selects the [0, 1] clip space. -constexpr std::uint32_t ClipControlMask = ~(0x80000u | 0x0c000000u); +constexpr std::uint32_t ClipControlMask = ~(0x80000u | 0x01000000u | 0x0c000000u); // Debug aid: APS5_IGNORE_DEPTH_TEST=1 renders depth- and stencil-tested draws without a depth // target as if their tests always passed (wrong occlusion, but the draws run), so stages that diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 3cd226c5a..75b240db0 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -728,9 +728,23 @@ void DepthClipTests() { expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "inverted viewport depth clamp"); queue.context[0xb4] = 0; for (std::uint32_t bit = 0; bit < 32; ++bit) { - if (bit == 19 || bit == 26 || bit == 27) continue; - queue.context[0x204] = 1u << bit; - expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "PA_CL_CLIP_CNTL"); + if (bit == 19 || bit == 24 || bit == 26 || bit == 27) continue; + for (const auto linearBit : {0u, 0x01000000u}) { + queue.context[0x204] = (1u << bit) | linearBit; + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "PA_CL_CLIP_CNTL"); + Require(AgcDriver::Graphics::DrawRejection(queue, false).find("PA_CL_CLIP_CNTL") != std::string::npos, "the precheck accepted an unsupported PA_CL_CLIP_CNTL bit"); + } + } + queue = makeState(); + queue.context[0x1b3] = 2; + queue.context[0x1b4] = 2; + for (const auto clip : {0x00080000u, 0x00000000u, 0x0c080000u, 0x04000000u}) { + queue.context[0x204] = clip; + const auto plain = AgcDriver::Graphics::DecodeState(queue); + queue.context[0x204] = clip | 0x01000000u; + const auto linearClip = AgcDriver::Graphics::DecodeState(queue); + Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "the precheck rejected DX_LINEAR_ATTR_CLIP_ENA"); + Require(linearClip.negativeOneToOne == plain.negativeOneToOne && linearClip.depthClamp == plain.depthClamp && linearClip.viewport.minDepth == plain.viewport.minDepth && linearClip.viewport.maxDepth == plain.viewport.maxDepth, "DX_LINEAR_ATTR_CLIP_ENA changed the clip space, depth clamping or depth range"); } } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..31a5f5dc0 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -88,6 +88,7 @@ Throughout the project, every function at every stage either **does exactly what - [v_mullit_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follows the hardware measured with the MODE register at IEEE=1 and denormals kept (a signaling NaN src0 is quieted, a positive denormal src2 counts as positive): the recompiler does not read MODE, so other IEEE/denormal settings are not modelled. - The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability. - [libSceJpegEnc](../../core/libs/prx/libSceJpegEnc/Export.cpp) encodes 4:2:2 sampling requests with 4:2:0 chroma subsampling, and grayscale requests as a 3-component JPEG with neutral chroma instead of a single-component one: the [JPEG encoder](../../core/Decoder/Jpeg/src/Jpeg.cpp) (stb) only produces 3-component 4:2:0 and 4:4:4 images. +- [PA_CL_CLIP_CNTL](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - `DX_LINEAR_ATTR_CLIP_ENA` (bit 24) clear is drawn like bit 24 set, with Vulkan's clipping of `NoPerspective` outputs; what a clear bit changes on the console is undocumented. - [v_rcp, v_rsq, v_sqrt, v_log, v_exp, v_sin and v_cos](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (f32 and f16) use the driver's approximation for finite results, not the console's: the low bits can differ. Only NaN, invalid, zero and infinite results and the exact sin/cos zeros are chosen on the bits. v_sin_f32/v_cos_f32 with inputs beyond ±256 cycles were not compared with the console. - `--to-intel` does not lower RDPRU/MCOMMIT; the [matcher](../../core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp) fails the relink instead. SHA-1 and SHA-256 instructions with a RIP-relative operand fail the relink. - The length-changing path of the [instruction rewriter](../../core/relinker/codegen/src/x86/X64InstructionRewriter.cpp) is not used by the [converter](../../core/relinker/codegen/src/Amd64OnlyConverter.cpp): it does not adjust VEX/0F38/0F3A RIP-relative operands, data-to-code references (relocations, FDEs, jump tables) or segment sizes, so every substitution keeps the instruction length. From f7ede9cd7d938fb3254245d4a4583f5c28f152a6 Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 13:25:23 +0200 Subject: [PATCH 127/164] feat(shader): implement the DPP16 row_share controls EmitDppTargetLane threw "DPP control 336 is not supported" for the DPP16 row_share controls 0x150-0x15f. row_share:sel now reads lane sel of the reading lane's own row, (lane & ~15) | sel, as the RDNA2 ISA defines it. The source never leaves the row, so the target is always valid and an inactive source follows the existing fi and bound_ctrl rule, as for row_xmask. The new agc_driver_dpp16_row_share_xmask execution test covers v_mov_b32, v_add_nc_u32, v_sub_nc_u32 and v_max_f32 with row_share and row_xmask under a partial exec mask, with fi, bound_ctrl and row/bank masks, as wave32 and wave64; row_xmask had no test before. Co-Authored-By: Claude Opus 5.5 --- .../tests/execution/Dpp16RowShareXmask.cpp | 289 ++++++++++++++++++ .../src/SpirvMemory/SpirvSubgroup.cpp | 7 + 2 files changed, 296 insertions(+) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Dpp16RowShareXmask.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16RowShareXmask.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16RowShareXmask.cpp new file mode 100644 index 000000000..0fd6a6861 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16RowShareXmask.cpp @@ -0,0 +1,289 @@ +#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 MaxThreads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Checked = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Wave32Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0x7e3202ff, 0xdeaddead, 0xbefe03ff, 0x33cc0ff3, 0x7e1402fa, + 0xff015004, 0x7e1602fa, 0xff055504, 0x7e1802fa, 0xff095c04, 0x7e1a02fa, 0xff0d5804, 0x7e1c02fa, + 0x5a095f04, 0x7e1e02fa, 0xff016104, 0x7e2002fa, 0xff056604, 0x7e2202fa, 0xff096f04, 0x7e2402fa, + 0xa5016a04, 0x4a260afa, 0xff015304, 0x4a280afa, 0xff095c04, 0x4a2a0afa, 0x69055404, 0x4a2c0afa, + 0xff016404, 0x4c2e0afa, 0xff0d6d04, 0x20300efa, 0xff095406, 0x20320efa, 0xff056906, 0xbefe03c1, + 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, + 0xbf810000, +}; + +alignas(256) constexpr std::array Wave64Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0x7e3202ff, 0xdeaddead, 0xbefe03ff, 0x33cc0ff3, 0xbeff03ff, + 0xc3a5960f, 0x7e1402fa, 0xff015004, 0x7e1602fa, 0xff055504, 0x7e1802fa, 0xff095c04, 0x7e1a02fa, + 0xff0d5804, 0x7e1c02fa, 0x5a095f04, 0x7e1e02fa, 0xff016104, 0x7e2002fa, 0xff056604, 0x7e2202fa, + 0xff096f04, 0x7e2402fa, 0xa5016a04, 0x4a260afa, 0xff015304, 0x4a280afa, 0xff095c04, 0x4a2a0afa, + 0x69055404, 0x4a2c0afa, 0xff016404, 0x4c2e0afa, 0xff0d6d04, 0x20300efa, 0xff095406, 0x20320efa, + 0xff056906, 0xbefe04c1, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, + 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[64][4] = { + {0x6ecd1cd2u, 0x4bef5ab2u, 0xc1f00000u, 0xc0c00000u}, + {0x6a003be9u, 0xe12916b9u, 0x422a0000u, 0x41fc0000u}, + {0xe8ace817u, 0x43f615feu, 0x42600000u, 0xc21e0000u}, + {0x4e3aa244u, 0x2d47d909u, 0x42540000u, 0xc2220000u}, + {0x68060c75u, 0xd69d14d5u, 0x41940000u, 0xc1d40000u}, + {0x5af9fb5au, 0x54393673u, 0x42440000u, 0xc1080000u}, + {0xad52f58fu, 0x3c3ff249u, 0x3fc00000u, 0xc26c0000u}, + {0xcb826265u, 0xd06f4fc2u, 0x425c0000u, 0xc2420000u}, + {0xc99d74ceu, 0xcb3eacf5u, 0x41ac0000u, 0x42320000u}, + {0x9c7fb5dbu, 0xa5e8a4bau, 0xc2340000u, 0xc24a0000u}, + {0x1e7cc79eu, 0x3197b5a3u, 0x41c00000u, 0x42420000u}, + {0x099b18b9u, 0xc7d8a2a5u, 0xc26a0000u, 0xc1980000u}, + {0x1504efdfu, 0x7e04a9d5u, 0xc2520000u, 0x422a0000u}, + {0xf200add6u, 0xd20e47a3u, 0xc1680000u, 0xc1380000u}, + {0xe31908c0u, 0x60de8b85u, 0xc20c0000u, 0x421a0000u}, + {0xf1c81bb0u, 0xa39b139du, 0x40000000u, 0xc2300000u}, + {0xdd7dd010u, 0x93d28ae8u, 0xc1f80000u, 0xc2460000u}, + {0xea239e60u, 0x1136bab0u, 0xc0900000u, 0xc1840000u}, + {0x4a248cbdu, 0x2cde59ccu, 0xc1380000u, 0xc1c40000u}, + {0x06734af7u, 0xfc5d75e7u, 0x40b00000u, 0xc2200000u}, + {0x76646c94u, 0x33cb3f0cu, 0xc25e0000u, 0xc2080000u}, + {0xc3f02221u, 0xba35f124u, 0xc2600000u, 0x424e0000u}, + {0xb955e2e5u, 0xb0a01d78u, 0xc1a40000u, 0x41300000u}, + {0xad47bf7du, 0x7e37ee41u, 0xc2380000u, 0x41e80000u}, + {0x4467b3a9u, 0xc243faa9u, 0x42400000u, 0x42280000u}, + {0xee39a32fu, 0x5d27404bu, 0xc1e00000u, 0xc2020000u}, + {0x362b62e4u, 0x40e07905u, 0x425e0000u, 0xc2640000u}, + {0x3597987bu, 0x9f2dda53u, 0xc1280000u, 0xc0200000u}, + {0x8cdfd404u, 0xaa825860u, 0x42700000u, 0x41940000u}, + {0x2883e9fdu, 0x6fcb4509u, 0xc1bc0000u, 0xc2700000u}, + {0xa64c74f6u, 0x6c225e76u, 0x41c40000u, 0xc22a0000u}, + {0xc9c4946du, 0x27f8b552u, 0x41300000u, 0xbf000000u}, + {0xec1dd6b9u, 0x37eae67cu, 0x42300000u, 0xc1fc0000u}, + {0x080a718au, 0xdd3a87b9u, 0xc1480000u, 0x426c0000u}, + {0x4d7d2f79u, 0x242b5b96u, 0xc1b80000u, 0xc25e0000u}, + {0xda0d5bd5u, 0xbd1eab24u, 0x41f40000u, 0xc2060000u}, + {0x30aa4ea5u, 0x908d264eu, 0xc0800000u, 0xc0c00000u}, + {0x4d4b8ec9u, 0x59f9bbd9u, 0xc1c40000u, 0x40c00000u}, + {0x008d7334u, 0x81e7a48bu, 0x3f800000u, 0x423a0000u}, + {0xa985a1d6u, 0x6454c697u, 0xc2300000u, 0xc22a0000u}, + {0x6945a98eu, 0x56efdfffu, 0x425e0000u, 0x41200000u}, + {0x543a17e0u, 0x9680cbfau, 0xc2360000u, 0xc20e0000u}, + {0x0e1e5311u, 0x8dbd6564u, 0xc2480000u, 0xc24c0000u}, + {0xd6c814a2u, 0x7f2c1289u, 0xc0b00000u, 0x41e00000u}, + {0x248db44au, 0xc60fd986u, 0x42400000u, 0xc2160000u}, + {0xd6c5e906u, 0x10117cecu, 0xc1f80000u, 0x41a80000u}, + {0x324909c5u, 0xdcd7b8c2u, 0x42080000u, 0x42220000u}, + {0xf995627fu, 0xc6e555f6u, 0xc2260000u, 0xc1800000u}, + {0x735c003eu, 0xc54e7632u, 0x41900000u, 0xc25a0000u}, + {0xfc3effbbu, 0x46c20a66u, 0xc1ec0000u, 0xc20a0000u}, + {0x0986feb5u, 0x040b21dfu, 0xc2180000u, 0x3f800000u}, + {0xacca236du, 0x7cfef2f2u, 0x40f00000u, 0x41cc0000u}, + {0x00bd61d8u, 0x5654123eu, 0x41400000u, 0xc1100000u}, + {0xe319a18bu, 0x5d8bc32du, 0x41100000u, 0xc2480000u}, + {0x6583147du, 0x72ce7a51u, 0x41780000u, 0x426e0000u}, + {0x49981da9u, 0x9b4ba109u, 0xc1a40000u, 0xc1e00000u}, + {0xe341cf98u, 0x3ec5433cu, 0xc0000000u, 0xc23c0000u}, + {0xc861e665u, 0xda64e2ddu, 0xc1f00000u, 0x41900000u}, + {0x8c31f3d4u, 0x807eed38u, 0xc1300000u, 0xc24e0000u}, + {0xf5411da7u, 0x6886f7e2u, 0xc1580000u, 0x420e0000u}, + {0x56aecd4cu, 0x7cfe2052u, 0xc23a0000u, 0x423a0000u}, + {0x29fc3f6du, 0x843d8b71u, 0x41d40000u, 0xbfc00000u}, + {0xf14c9634u, 0x6de904e9u, 0xc1c40000u, 0xc1180000u}, + {0x0524b659u, 0xb07d24c5u, 0xc22e0000u, 0xc23a0000u} +}; + +constexpr std::uint32_t Expected32[32][16] = { + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x6a003be9u, 0xad52f58fu, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x4bef5ab2u, 0xdeaddeadu, 0xb3f56727u, 0xa6115324u, 0x41940000u, 0xc0c00000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x6ecd1cd2u, 0xcb826265u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0xe12916b9u, 0xdeaddeadu, 0x3c231213u, 0x33dbd926u, 0x41fc0000u, 0x41fc0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0x5af9fb5au, 0xe8ace817u, 0x099b18b9u, 0xdeaddeadu, 0xdeaddeadu, 0xd69d14d5u, 0xdeaddeadu, 0x456a31a7u, 0xc5e2a106u, 0x41940000u, 0xc1680000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0x68060c75u, 0x4e3aa244u, 0x1e7cc79eu, 0xdeaddeadu, 0xdeaddeadu, 0x54393673u, 0xdeaddeadu, 0xbe39725cu, 0x75643e5bu, 0x41940000u, 0xc1080000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0xcb826265u, 0x6ecd1cd2u, 0x9c7fb5dbu, 0xdeaddeadu, 0xdeaddeadu, 0x3c3ff249u, 0xdeaddeadu, 0xdeaddeadu, 0xcd5b2670u, 0x41940000u, 0x40000000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0xad52f58fu, 0x6a003be9u, 0xc99d74ceu, 0xdeaddeadu, 0xdeaddeadu, 0xd06f4fc2u, 0xdeaddeadu, 0xdeaddeadu, 0x4e0d77dcu, 0x41940000u, 0xc20c0000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x9c7fb5dbu, 0xe31908c0u, 0xcb826265u, 0xdeaddeadu, 0xdeaddeadu, 0xcb3eacf5u, 0xdeaddeadu, 0xdeaddeadu, 0x8fbb4e65u, 0x42320000u, 0x42320000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0xc99d74ceu, 0xf1c81bb0u, 0xad52f58fu, 0xdeaddeadu, 0xdeaddeadu, 0xa5e8a4bau, 0xdeaddeadu, 0xdeaddeadu, 0xc21d67bbu, 0x41940000u, 0xc1f00000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x099b18b9u, 0x1504efdfu, 0x5af9fb5au, 0xdeaddeadu, 0xdeaddeadu, 0x3197b5a3u, 0xdeaddeadu, 0xdeaddeadu, 0x99eaacc2u, 0x42420000u, 0x42540000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x1e7cc79eu, 0xf200add6u, 0x68060c75u, 0xdeaddeadu, 0xdeaddeadu, 0xc7d8a2a5u, 0xdeaddeadu, 0xdeaddeadu, 0xe57a52eau, 0x41940000u, 0x42600000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x06734af7u, 0x76646c94u, 0x2883e9fdu, 0x4467b3a9u, 0x3351a4c3u, 0xb9be2dd0u, 0xa342c660u, 0xe6343cb1u, 0x9ce63aa1u, 0x00000000u, 0xc1280000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x4a248cbdu, 0xc3f02221u, 0x8cdfd404u, 0xee39a32fu, 0x02d0c0deu, 0x893d49ebu, 0x72c1e27bu, 0xa9a53564u, 0xa9eeff0fu, 0x00000000u, 0x425e0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xad47bf7du, 0xdd7dd010u, 0xee39a32fu, 0xdeaddeadu, 0xb713686fu, 0x3d7ff17cu, 0xdeaddeadu, 0xfac4aa35u, 0x84f77b03u, 0x41300000u, 0x41300000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xb955e2e5u, 0xea239e60u, 0x4467b3a9u, 0xdeaddeadu, 0x84ab3938u, 0x0b17c245u, 0xdeaddeadu, 0x84ab3938u, 0xb7f374a3u, 0x41e80000u, 0x41e80000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xee39a32fu, 0xa64c74f6u, 0xad47bf7du, 0x4a248cbdu, 0xc8b745a0u, 0x4f23ceadu, 0xdeaddeadu, 0x4f23ceadu, 0x01ac2778u, 0x42280000u, 0x42280000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x4467b3a9u, 0xc9c4946du, 0xb955e2e5u, 0x06734af7u, 0x639a8b42u, 0xea07144fu, 0xdeaddeadu, 0x85ab2a48u, 0x193d2c49u, 0x00000000u, 0xc1f80000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x2883e9fdu, 0x362b62e4u, 0x06734af7u, 0xdeaddeadu, 0xb0f5a357u, 0x37622c64u, 0x20e6c4f4u, 0xeeea0c09u, 0x3fa14600u, 0x41940000u, 0x41940000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x8cdfd404u, 0x3597987bu, 0x4a248cbdu, 0xdeaddeadu, 0x763e9000u, 0xfcab190du, 0xe62fb19du, 0x5e04e838u, 0x6db28b07u, 0x00000000u, 0xc25e0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu} +}; +constexpr std::uint32_t Expected64[64][16] = { + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x6a003be9u, 0xad52f58fu, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x4bef5ab2u, 0xdeaddeadu, 0xb3f56727u, 0xa6115324u, 0x41940000u, 0xc0c00000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x6ecd1cd2u, 0xcb826265u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0xe12916b9u, 0xdeaddeadu, 0x3c231213u, 0x33dbd926u, 0x41fc0000u, 0x41fc0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0x5af9fb5au, 0xe8ace817u, 0x099b18b9u, 0xdeaddeadu, 0xdeaddeadu, 0xd69d14d5u, 0xdeaddeadu, 0x456a31a7u, 0xc5e2a106u, 0x41940000u, 0xc1680000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0x68060c75u, 0x4e3aa244u, 0x1e7cc79eu, 0xdeaddeadu, 0xdeaddeadu, 0x54393673u, 0xdeaddeadu, 0xbe39725cu, 0x75643e5bu, 0x41940000u, 0xc1080000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0xcb826265u, 0x6ecd1cd2u, 0x9c7fb5dbu, 0xdeaddeadu, 0xdeaddeadu, 0x3c3ff249u, 0xdeaddeadu, 0xdeaddeadu, 0xcd5b2670u, 0x41940000u, 0x40000000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0xad52f58fu, 0x6a003be9u, 0xc99d74ceu, 0xdeaddeadu, 0xdeaddeadu, 0xd06f4fc2u, 0xdeaddeadu, 0xdeaddeadu, 0x4e0d77dcu, 0x41940000u, 0xc20c0000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x9c7fb5dbu, 0xe31908c0u, 0xcb826265u, 0xdeaddeadu, 0xdeaddeadu, 0xcb3eacf5u, 0xdeaddeadu, 0xdeaddeadu, 0x8fbb4e65u, 0x42320000u, 0x42320000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0xc99d74ceu, 0xf1c81bb0u, 0xad52f58fu, 0xdeaddeadu, 0xdeaddeadu, 0xa5e8a4bau, 0xdeaddeadu, 0xdeaddeadu, 0xc21d67bbu, 0x41940000u, 0xc1f00000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x099b18b9u, 0x1504efdfu, 0x5af9fb5au, 0xdeaddeadu, 0xdeaddeadu, 0x3197b5a3u, 0xdeaddeadu, 0xdeaddeadu, 0x99eaacc2u, 0x42420000u, 0x42540000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x1e7cc79eu, 0xf200add6u, 0x68060c75u, 0xdeaddeadu, 0xdeaddeadu, 0xc7d8a2a5u, 0xdeaddeadu, 0xdeaddeadu, 0xe57a52eau, 0x41940000u, 0x42600000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x06734af7u, 0x76646c94u, 0x2883e9fdu, 0x4467b3a9u, 0x3351a4c3u, 0xb9be2dd0u, 0xa342c660u, 0xe6343cb1u, 0x9ce63aa1u, 0x00000000u, 0xc1280000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x4a248cbdu, 0xc3f02221u, 0x8cdfd404u, 0xee39a32fu, 0x02d0c0deu, 0x893d49ebu, 0x72c1e27bu, 0xa9a53564u, 0xa9eeff0fu, 0x00000000u, 0x425e0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xad47bf7du, 0xdd7dd010u, 0xee39a32fu, 0xdeaddeadu, 0xb713686fu, 0x3d7ff17cu, 0xdeaddeadu, 0xfac4aa35u, 0x84f77b03u, 0x41300000u, 0x41300000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xb955e2e5u, 0xea239e60u, 0x4467b3a9u, 0xdeaddeadu, 0x84ab3938u, 0x0b17c245u, 0xdeaddeadu, 0x84ab3938u, 0xb7f374a3u, 0x41e80000u, 0x41e80000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xee39a32fu, 0xa64c74f6u, 0xad47bf7du, 0x4a248cbdu, 0xc8b745a0u, 0x4f23ceadu, 0xdeaddeadu, 0x4f23ceadu, 0x01ac2778u, 0x42280000u, 0x42280000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x4467b3a9u, 0xc9c4946du, 0xb955e2e5u, 0x06734af7u, 0x639a8b42u, 0xea07144fu, 0xdeaddeadu, 0x85ab2a48u, 0x193d2c49u, 0x00000000u, 0xc1f80000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x2883e9fdu, 0x362b62e4u, 0x06734af7u, 0xdeaddeadu, 0xb0f5a357u, 0x37622c64u, 0x20e6c4f4u, 0xeeea0c09u, 0x3fa14600u, 0x41940000u, 0x41940000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x8cdfd404u, 0x3597987bu, 0x4a248cbdu, 0xdeaddeadu, 0x763e9000u, 0xfcab190du, 0xe62fb19du, 0x5e04e838u, 0x6db28b07u, 0x00000000u, 0xc25e0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0x080a718au, 0x008d7334u, 0xf995627fu, 0xdeaddeadu, 0x11f84251u, 0x5c789ac6u, 0x68953521u, 0xdeaddeadu, 0x9edb028au, 0x00000000u, 0xc1fc0000u}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0xec1dd6b9u, 0xa985a1d6u, 0x00000000u, 0xdeaddeadu, 0xb747e38eu, 0x01c83c03u, 0x0de4d65eu, 0xdeaddeadu, 0x47532c91u, 0x426c0000u, 0x426c0000u}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0xda0d5bd5u, 0x30aa4ea5u, 0x00000000u, 0xdeaddeadu, 0xfe38b76bu, 0x48b90fe0u, 0x54d5aa3bu, 0xdeaddeadu, 0xd56a06e9u, 0x00000000u, 0xc0b00000u}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0x4d7d2f79u, 0x4d4b8ec9u, 0x248db44au, 0xdeaddeadu, 0x972c06f9u, 0xe1ac5f6eu, 0xedc8f9c9u, 0xdeaddeadu, 0x752a5ea1u, 0x00000000u, 0xc2060000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0xdeaddeadu, 0xf995627fu, 0x00000000u, 0xdeaddeadu, 0x708e27cfu, 0xbb0e8044u, 0xdeaddeadu, 0xdeaddeadu, 0x9a2982abu, 0x00000000u, 0x42300000u}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0xdeaddeadu, 0x248db44au, 0x00000000u, 0xdeaddeadu, 0x67cac139u, 0xb24b19aeu, 0xdeaddeadu, 0xdeaddeadu, 0x1bc83c72u, 0x00000000u, 0x41f40000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xf995627fu, 0xdeaddeadu, 0x0e1e5311u, 0xda0d5bd5u, 0xdeaddeadu, 0xa01d355bu, 0xea9d8dd0u, 0xf6ba282bu, 0xdeaddeadu, 0x41fa9804u, 0x00000000u, 0xc1c40000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xf995627fu, 0xdeaddeadu, 0x543a17e0u, 0xec1dd6b9u, 0xdeaddeadu, 0xa0f2b1cbu, 0xeb730a40u, 0xf78fa49bu, 0xdeaddeadu, 0x8697d983u, 0x00000000u, 0x3f800000u}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xdeaddeadu, 0x6583147du, 0x0524b659u, 0xdeaddeadu, 0xdeaddeadu, 0xc54e7632u, 0xdeaddeadu, 0xdeaddeadu, 0x64adc93bu, 0x00000000u, 0xc1f00000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xdeaddeadu, 0x00bd61d8u, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0x040b21dfu, 0xdeaddeadu, 0xdeaddeadu, 0x0119947au, 0x3f800000u, 0x3f800000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xdeaddeadu, 0xacca236du, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x5d8bc32du, 0xdeaddeadu, 0xdeaddeadu, 0x85b60c6bu, 0x00000000u, 0xc23a0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xdeaddeadu, 0xfc3effbbu, 0xe341cf98u, 0xdeaddeadu, 0xdeaddeadu, 0x9b4ba109u, 0xdeaddeadu, 0xdeaddeadu, 0xf0e652cbu, 0x00000000u, 0xc1c40000u}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xc861e665u, 0xf14c9634u, 0x49981da9u, 0x0986feb5u, 0xdeaddeadu, 0x3ec5433cu, 0xdeaddeadu, 0xdeaddeadu, 0xa4545e4fu, 0x00000000u, 0xc1ec0000u}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xe341cf98u, 0x0524b659u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0xda64e2ddu, 0xdeaddeadu, 0xdeaddeadu, 0x26587efbu, 0x41900000u, 0x41900000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0x0524b659u, 0xe341cf98u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x6de904e9u, 0xdeaddeadu, 0xdeaddeadu, 0x3ee11e84u, 0x00000000u, 0xc1180000u}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xf14c9634u, 0xc861e665u, 0x735c003eu, 0xdeaddeadu, 0xdeaddeadu, 0xb07d24c5u, 0xdeaddeadu, 0xdeaddeadu, 0x5909d9f0u, 0x00000000u, 0x41780000u} +}; + +constexpr const char* Names[Checked] = {"mov_row_share0", "mov_row_share5_fi", "mov_row_share12_bc", "mov_row_share8_bc_fi", "mov_row_share15_masks", "mov_row_xmask1", "mov_row_xmask6_fi", "mov_row_xmask15_bc", "mov_row_xmask10_masks", "add_row_share3", "add_row_share12_bc", "add_row_share4_fi_masks", "add_row_xmask4", "sub_row_xmask13_bc_fi", "max_f32_row_share4_bc", "max_f32_row_xmask9_fi"}; + +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; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + for (std::uint32_t tid = 0; tid < MaxThreads; ++tid) std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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{{waveSize, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + 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(); +} + +template +void Check(const std::uint32_t (&expected)[Lanes][Checked]) { + for (std::uint32_t tid = 0; tid < Lanes; ++tid) { + for (std::uint32_t index = 0; index < Checked; ++index) { + const auto actual = Output[tid * Results + index]; + Require(actual == expected[tid][index], "dpp16 row share xmask wave" + std::to_string(Lanes) + ": lane " + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < 32) { + std::printf("skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); + return VulkanTestSkipped; + } + Run(*device, Wave32Code, 32, device->ComputeTarget(32)); + Check(Expected32); + Run(*device, Wave64Code, 64, device->Target()); + Check(Expected64); + Run(*device, Wave64Code, 64, device->ComputeTarget(32)); + Check(Expected64); + std::puts("dpp16 row share xmask tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp index 390ac734b..6532d500d 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp @@ -141,6 +141,13 @@ namespace ShaderRecompiler if (control == 0x141u) { return EmitDppMirrorTargetLane(state, subid, true); } + if (control >= 0x150u && control <= 0x15fu) { + const auto row = state.module.AllocateId(); + const auto target = state.module.AllocateId(); + state.module.AddFunction(spv::OpBitwiseAnd, TypeU32(state), row, subid, ConstantU32(state, 0xfffffff0u)); + state.module.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, row, ConstantU32(state, control & 0xfu)); + return {target, ConstantBool(state, true)}; + } if (control >= 0x160u && control <= 0x16fu) { const auto target = state.module.AllocateId(); state.module.AddFunction(spv::OpBitwiseXor, TypeU32(state), target, subid, ConstantU32(state, control & 0xfu)); From 51c98a3983ef2422f558e6c870b25dc1ea9335ac Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:32:34 +0200 Subject: [PATCH 128/164] fix(agc): store sceAgcGetIsTrinityMode's flag through its out pointer sceAgcGetIsTrinityMode takes a pointer to a one-byte flag; the only known caller ignores the return value. The export took no argument and never wrote the flag. That caller's flag is in zero-initialised .bss, so this corrects the signature; it does not fix a visible failure. The function now stores false, as sceKernelIsTrinityMode reports a base PS5, returns 0 and throws on a null pointer, whose error code is unknown. It moves from Unimplemented.cpp to Misc/src/Platform.cpp, and the new agc_platform_tests ctest target covers it. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgc/CMakeLists.txt | 14 +++++ .../prx/libSceAgc/Misc/include/Platform.hpp | 4 ++ core/libs/prx/libSceAgc/Misc/src/Platform.cpp | 17 ++++++ core/libs/prx/libSceAgc/Unimplemented.cpp | 4 -- core/libs/prx/libSceAgc/tests/Platform.cpp | 53 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 6 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 core/libs/prx/libSceAgc/Misc/include/Platform.hpp create mode 100644 core/libs/prx/libSceAgc/Misc/src/Platform.cpp create mode 100644 core/libs/prx/libSceAgc/tests/Platform.cpp diff --git a/core/libs/prx/libSceAgc/CMakeLists.txt b/core/libs/prx/libSceAgc/CMakeLists.txt index 4e9a781f8..f72bd1472 100644 --- a/core/libs/prx/libSceAgc/CMakeLists.txt +++ b/core/libs/prx/libSceAgc/CMakeLists.txt @@ -30,6 +30,7 @@ add_library(libSceAgc SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/DcbState/src/Registers.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DcbState/src/Workload.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/PacketInfo.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/Platform.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/RegisterDefaults.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/ShaderFusion.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/Suspend.cpp @@ -69,6 +70,19 @@ target_link_libraries(agc_command_tests PRIVATE libSceAgc libc) configure_windows_unwind(agc_command_tests) configure_windows_unwind(libSceAgc) +add_test_executable(agc_platform_tests EXCLUDE_FROM_ALL tests/Platform.cpp) +target_include_directories(agc_platform_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_platform_tests PRIVATE libSceAgc libc) +configure_windows_unwind(agc_platform_tests) +if(BUILD_TESTING) + set_target_properties(agc_platform_tests PROPERTIES EXCLUDE_FROM_ALL FALSE) + add_test(NAME agc_platform_tests COMMAND agc_platform_tests) + if(WIN32) + set_tests_properties(agc_platform_tests PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + add_test_executable(agc_register_command_tests EXCLUDE_FROM_ALL tests/RegisterCommands.cpp Command/src/Packet.cpp diff --git a/core/libs/prx/libSceAgc/Misc/include/Platform.hpp b/core/libs/prx/libSceAgc/Misc/include/Platform.hpp new file mode 100644 index 000000000..fd07f976a --- /dev/null +++ b/core/libs/prx/libSceAgc/Misc/include/Platform.hpp @@ -0,0 +1,4 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_PLATFORM_HPP +#define CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_PLATFORM_HPP + +#endif diff --git a/core/libs/prx/libSceAgc/Misc/src/Platform.cpp b/core/libs/prx/libSceAgc/Misc/src/Platform.cpp new file mode 100644 index 000000000..de162d3a7 --- /dev/null +++ b/core/libs/prx/libSceAgc/Misc/src/Platform.cpp @@ -0,0 +1,17 @@ +#include "prx/libSceAgc/Misc/include/Platform.hpp" +#include "prx/libSceAgc/Command/include/Packet.hpp" + +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceAgcGetIsTrinityMode(bool* isTrinityMode) { + Agc::Command::CheckAddress(reinterpret_cast(isTrinityMode), alignof(bool), __func__); + *isTrinityMode = false; + return 0; +} + +} diff --git a/core/libs/prx/libSceAgc/Unimplemented.cpp b/core/libs/prx/libSceAgc/Unimplemented.cpp index e53a08cb3..97c5525cb 100644 --- a/core/libs/prx/libSceAgc/Unimplemented.cpp +++ b/core/libs/prx/libSceAgc/Unimplemented.cpp @@ -70,10 +70,6 @@ int APS5_VABI sceAgcGetGsOversubscription() { return 0; } -int APS5_VABI sceAgcGetIsTrinityMode() { - return 0; -} - int APS5_VABI sceAgcGetSemaphoreLabel() { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceAgc/tests/Platform.cpp b/core/libs/prx/libSceAgc/tests/Platform.cpp new file mode 100644 index 000000000..daa0634cf --- /dev/null +++ b/core/libs/prx/libSceAgc/tests/Platform.cpp @@ -0,0 +1,53 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libc/include/Shutdown.hpp" + +#include +#include +#include + +extern "C" int APS5_VABI sceAgcGetIsTrinityMode(bool* isTrinityMode); + +namespace { + +void check(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +template +void expectFailure(TAction action) { + try { + action(); + } catch (const std::runtime_error& error) { + check(error.what()[0] != '\0', "empty exception message"); + return; + } + throw std::runtime_error("expected an exception"); +} + +void testTrinityMode() { + std::array flags{true, true, true}; + check(sceAgcGetIsTrinityMode(&flags[1]) == 0, "Trinity mode query did not return 0"); + check(!flags[1], "base PS5 GPU reported as Trinity"); + check(flags[0] && flags[2], "Trinity mode query wrote past its one-byte flag"); +} + +void testRejections() { + expectFailure([] { sceAgcGetIsTrinityMode(nullptr); }); +} + +} + +int main() { + try { + testTrinityMode(); + testRejections(); + LibcRunShutdown_nid_postfix(); + std::puts("AGC platform tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + try { LibcRunShutdown_nid_postfix(); } + catch (const std::exception& shutdown) { std::fprintf(stderr, "shutdown: %s\n", shutdown.what()); } + return 1; + } +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..3d65018a4 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -32,6 +32,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceFontGetRenderScaledKerning](../../core/libs/prx/libSceFont/src/Render.cpp) (libSceFont) - arguments, the cleared output and the bound renderer check taken from `cellFontGetRenderScaledKerning` in [RPCS3](https://github.com/RPCS3/rpcs3/blob/master/rpcs3/Emu/Cell/Modules/cellFont.cpp) (PS3 predecessor of the library), not confirmed on a title. The kerning is the `sceFontGetKerning` one at the render scale - [sceFontGetPixelResolution](../../core/libs/prx/libSceFont/src/Library.cpp) (libSceFont) - signature taken from the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json), not confirmed on a title. The value is the one the library's driver reports (64 for FreeType); the argument checks and the cleared output follow `sceFontGetLibrary` - [sceAgcSetSubmitMode](../../core/libs/prx/libSceAgc/Misc/src/Suspend.cpp) (libSceAgc) - mode values unknown; only 0 is accepted +- [sceAgcGetIsTrinityMode](../../core/libs/prx/libSceAgc/Misc/src/Platform.cpp) (libSceAgc) - signature from the only PPSA26344 call (a pointer to a one-byte flag, result ignored); a null pointer throws because its error code is unknown - [zARR5aCmkoY](../../core/libs/prx/libSceAgc/DcbFlow/src/Control.cpp) (libSceAgc) - unknown name, signature - [qj7QZpgr9Uw](../../core/libs/prx/libSceAgc/DcbState/src/ContextState.cpp) (libSceAgc) - unknown name - [fd5Bp5tGTgo](../../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name From 73c68c04bd5175fb15b9a1869359214850e0b070 Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 13:21:09 +0200 Subject: [PATCH 129/164] feat(agc): implement image atomics on 32_SINT and 32_FLOAT surfaces The resource materializer refused every atomic image descriptor whose format was not 32_UINT. RDNA2 image atomics act on the raw dword and the numeric format only affects addressing, so 32_SINT and 32_FLOAT surfaces are now accepted with the Uint numeric class and run through the existing integer atomics and float compare-exchange loops. Other formats still throw, and the message now names the format. DescriptorBinding gains imageAtomic, carried through the shader disk cache (format version 4) and the resource content key. The driver binds those elements through a cached R32_UINT view of R32_SINT and R32_SFLOAT storage images. Co-Authored-By: Claude Opus 5.5 --- .../Graphics/include/ShaderResources.hpp | 1 + .../Graphics/include/Texture.hpp | 4 +- .../Graphics/src/ShaderResources.cpp | 4 +- .../libSceAgcDriver/Graphics/src/Texture.cpp | 25 +++++++++--- .../prx/libSceAgcDriver/tests/Recorder.cpp | 39 +++++++++++++++++++ .../libSceAgcDriver/tests/ShaderDiskCache.cpp | 3 +- .../tests/execution/ImageAtomics.cpp | 22 ++++++----- .../tests/execution/ImageAtomicsFloat.cpp | 35 ++++++++++++----- .../src/DescriptorBindingBuilder.cpp | 1 + .../Optimization/src/ResourceMaterializer.cpp | 6 +-- core/shader/recompiler/Recompiler.hpp | 1 + core/shader/recompiler/ShaderDiskCache.cpp | 4 +- core/shader/recompiler/ShaderDiskCache.hpp | 2 +- docs/dev/TechnicalDebt.md | 1 + 14 files changed, 117 insertions(+), 31 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp index 205551744..69d086ca4 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp @@ -411,6 +411,7 @@ private: std::vector storageKeys; std::vector storageFirstLayer; std::vector storageWritten; + std::vector storageAtomic; std::vector> samplers; bool reusable = false; std::vector directRegions; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp index f1ef0b0da..198939b7b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp @@ -114,6 +114,7 @@ public: // so successive mip writes of a chain share one image and one write-back. VkImageView View(std::uint32_t mip); VkImageView FirstLayerView(std::uint32_t mip); + VkImageView AtomicView(std::uint32_t mip, bool firstLayer); // Render targets live in the same images: draws attach mip 0 through a view of the color // buffer's format and mark the image dirty like a storage write. bool Attachable() const { return attachable; } @@ -394,7 +395,7 @@ private: bool clearByKeysFill(DccKeys keys, std::uint8_t key); bool overlaps(std::uint64_t address, std::size_t bytes) const; bool pendingUnitInside(std::uint64_t address, std::size_t bytes) const; - VkImageView createView(std::uint32_t mip, bool firstLayer = false) const; + VkImageView createView(std::uint32_t mip, bool firstLayer, VkFormat format) const; void release() noexcept; Context context; @@ -442,6 +443,7 @@ private: std::uint32_t defaultMip = 0; std::map extraViews; std::map firstLayerViews; + std::map, VkImageView> atomicViews; bool attachable = false; std::map, VkImageView> attachmentViews; VkFormat storageFormat = VK_FORMAT_UNDEFINED; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index 0dafb11f8..c1fff993b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -1056,7 +1056,7 @@ void ShaderResources::buildComplete() { break; case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: write.pImageInfo = images.data() + images.size(); - for (const auto index : binding.imageAllocations) images.push_back({VK_NULL_HANDLE, storageFirstLayer[index] ? storageTextures[index]->FirstLayerView(storageMips[index]) : storageTextures[index]->View(storageMips[index]), VK_IMAGE_LAYOUT_GENERAL}); + for (const auto index : binding.imageAllocations) images.push_back({VK_NULL_HANDLE, storageAtomic[index] ? storageTextures[index]->AtomicView(storageMips[index], storageFirstLayer[index]) : storageFirstLayer[index] ? storageTextures[index]->FirstLayerView(storageMips[index]) : storageTextures[index]->View(storageMips[index]), VK_IMAGE_LAYOUT_GENERAL}); break; case VK_DESCRIPTOR_TYPE_SAMPLER: write.pImageInfo = images.data() + images.size(); @@ -1201,6 +1201,7 @@ std::vector ShaderResources::ContentKey(const CompiledShader& sha packBits(binding.imageWritten); packBits(binding.samplerDepthCompare); packBits(binding.imageDepthCompare); + packBits(binding.imageAtomic); // Read-only elements are bound without a write set: an object built for one written set // must not serve a build with another (the variant implies it, this makes it explicit). packBits(binding.bufferWritten); @@ -2564,6 +2565,7 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind storageFirstLayer.push_back(firstLayer); // Images the shader only reads have nothing to store back. storageWritten.push_back(element >= binding.imageWritten.size() || binding.imageWritten[element]); + storageAtomic.push_back(element < binding.imageAtomic.size() && binding.imageAtomic[element]); describedRanges.push_back({"storage", resource.baseAddress, guestBytes, resource.width, resource.height, resource.format, static_cast(resource.tileMode), resource.dccAddress}); item.imageAllocations.push_back(storageTextures.size() - 1); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index 777529fa5..2bb762263 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -691,7 +691,7 @@ StorageTexture::StorageTexture(const Context& context, TextureDetiler& detiler, uploadReason = "first"; upload(); defaultMip = mipLevel; - view = createView(mipLevel); + view = createView(mipLevel, false, storageFormat); { auto& live = Live(); std::lock_guard lock(live.mutex); @@ -942,15 +942,18 @@ bool AdjacentGenerationEnabled() { } -VkImageView StorageTexture::createView(std::uint32_t mip, bool firstLayer) const { +VkImageView StorageTexture::createView(std::uint32_t mip, bool firstLayer, VkFormat format) const { Require(mip < descriptor.mipCount, "storage texture mip level is outside the texture"); Require(!firstLayer || descriptor.dimension == TextureDimension::k2DArray, "a first-layer storage view needs a 2D array surface"); const auto viewLayerCount = firstLayer ? 1u : geometry.imageLayers - descriptor.baseArray; + VkImageViewUsageCreateInfo usage{VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO}; + usage.usage = VK_IMAGE_USAGE_STORAGE_BIT; VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + viewInfo.pNext = format == storageFormat ? nullptr : &usage; viewInfo.image = image; // Storage views address one mip; cube faces are written as array layers. viewInfo.viewType = firstLayer ? VK_IMAGE_VIEW_TYPE_2D : descriptor.dimension == TextureDimension::k1D ? VK_IMAGE_VIEW_TYPE_1D : descriptor.dimension == TextureDimension::k2D ? VK_IMAGE_VIEW_TYPE_2D : descriptor.dimension == TextureDimension::k3D ? VK_IMAGE_VIEW_TYPE_3D : VK_IMAGE_VIEW_TYPE_2D_ARRAY; - viewInfo.format = storageFormat; + viewInfo.format = format; viewInfo.components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY}; viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, mip, 1u, descriptor.baseArray, viewLayerCount}; VkImageView created = VK_NULL_HANDLE; @@ -984,7 +987,7 @@ VkImageView StorageTexture::View(std::uint32_t mip) { if (mip == defaultMip) return view; const auto found = extraViews.find(mip); if (found != extraViews.end()) return found->second; - const auto created = createView(mip); + const auto created = createView(mip, false, storageFormat); extraViews.emplace(mip, created); return created; } @@ -992,11 +995,21 @@ VkImageView StorageTexture::View(std::uint32_t mip) { VkImageView StorageTexture::FirstLayerView(std::uint32_t mip) { const auto found = firstLayerViews.find(mip); if (found != firstLayerViews.end()) return found->second; - const auto created = createView(mip, true); + const auto created = createView(mip, true, storageFormat); firstLayerViews.emplace(mip, created); return created; } +VkImageView StorageTexture::AtomicView(std::uint32_t mip, bool firstLayer) { + if (storageFormat == VK_FORMAT_R32_UINT) return firstLayer ? FirstLayerView(mip) : View(mip); + Require(storageFormat == VK_FORMAT_R32_SINT || storageFormat == VK_FORMAT_R32_SFLOAT, "storage image atomics need a surface of one 32-bit component"); + const auto found = atomicViews.find({mip, firstLayer}); + if (found != atomicViews.end()) return found->second; + const auto created = createView(mip, firstLayer, VK_FORMAT_R32_UINT); + atomicViews.emplace(std::pair{mip, firstLayer}, created); + return created; +} + const char* LookupOutcomes::Name(Kind kind) { static constexpr const char* names[Count] = {"sampled fast hit", "sampled fast miss", "sampled hit view", "sampled hit cleared view", "sampled hit snapshot", "sampled made view", "sampled made snapshot", "storage hit", "storage made", "refresh unchanged", "refresh compared", "upload direct", "upload cpu", "upload clear", "dcc scan", "pending flush"}; return kind < Count ? names[kind] : "?"; @@ -3559,6 +3572,8 @@ void StorageTexture::release() noexcept { extraViews.clear(); for (const auto& [mip, extra] : firstLayerViews) context.Function("vkDestroyImageView")(context.device, extra, nullptr); firstLayerViews.clear(); + for (const auto& [key, atomic] : atomicViews) context.Function("vkDestroyImageView")(context.device, atomic, nullptr); + atomicViews.clear(); for (const auto& [format, attachment] : attachmentViews) context.Function("vkDestroyImageView")(context.device, attachment, nullptr); attachmentViews.clear(); if (view) context.Function("vkDestroyImageView")(context.device, view, nullptr); diff --git a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp index 8ae3444be..795db774c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp @@ -2573,6 +2573,44 @@ void firstLayerViewTests(const Device& device, Recorder& recorder) { expectRed(program.Red(flatTexture.View(), flatTexture.Layout(), 0.0f), 0x20 / 255.0f, "a 2D texture over the surface does not read its first layer"); } +void atomicViewTests(const Device& device, Recorder& recorder) { + const auto& context = device.GetContext(); + TextureDetiler detiler(context); + auto withDetiler = context; + withDetiler.detiler = &detiler; + for (const std::uint32_t format : {20u, 21u, 22u, 56u}) { + GuestTextureResource resource{}; + resource.width = 64; + resource.height = 4; + resource.mipCount = 1; + resource.tileMode = TextureTileMode::kLinear; + resource.dimension = TextureDimension::k2D; + resource.format = format; + resource.dstSelX = 4; + resource.dstSelY = 5; + resource.dstSelZ = 6; + resource.dstSelW = 7; + const auto geometry = DescribeSurface(resource); + std::vector memory(static_cast(geometry.guestBytes) + 256); + resource.baseAddress = (reinterpret_cast(memory.data()) + 255) & ~std::uintptr_t{255}; + auto image = std::make_shared(withDetiler, detiler, resource, 0); + recorder.Keep(image); + if (format == 56u) { + bool refused = false; + try { + static_cast(image->AtomicView(0, false)); + } catch (const std::exception&) { + refused = true; + } + Require(refused, "an 8_8_8_8 storage image has an atomic view"); + continue; + } + const auto atomic = image->AtomicView(0, false); + Require(atomic != VK_NULL_HANDLE && atomic == image->AtomicView(0, false), "atomic storage views are not reused"); + Require((atomic == image->View(0)) == (format == 20u), "only a 32_UINT storage image serves atomics through its own view"); + } +} + void keysFillTests(const Device& device, Recorder& recorder) { const auto& base = device.GetContext(); if (base.hostImportAlignment == 0) { @@ -2724,6 +2762,7 @@ int main() { dataRefreshTests(device, recorder); minLodTests(device, recorder); firstLayerViewTests(device, recorder); + atomicViewTests(device, recorder); metadataPassTests(device, recorder); pendingKeyStoreTests(device, recorder); sampleDumpTests(device, recorder); diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp index 55e79fbc5..20dd3e202 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp @@ -31,7 +31,7 @@ void setEnvironment(const char* name, const std::string& value) { } bool sameBinding(const DescriptorBinding& left, const DescriptorBinding& right) { - return left.kind == right.kind && left.role == right.role && left.descriptorSet == right.descriptorSet && left.binding == right.binding && left.count == right.count && left.guestDescriptor == right.guestDescriptor && left.readOnly == right.readOnly && left.imageShape == right.imageShape && left.samplerDepthCompare == right.samplerDepthCompare && left.imageWritten == right.imageWritten && left.imageDepthCompare == right.imageDepthCompare && left.bufferAtomic == right.bufferAtomic && left.bufferWritten == right.bufferWritten; + return left.kind == right.kind && left.role == right.role && left.descriptorSet == right.descriptorSet && left.binding == right.binding && left.count == right.count && left.guestDescriptor == right.guestDescriptor && left.readOnly == right.readOnly && left.imageShape == right.imageShape && left.samplerDepthCompare == right.samplerDepthCompare && left.imageWritten == right.imageWritten && left.imageDepthCompare == right.imageDepthCompare && left.imageAtomic == right.imageAtomic && left.bufferAtomic == right.bufferAtomic && left.bufferWritten == right.bufferWritten; } bool sameBindings(const std::vector& left, const std::vector& right) { @@ -92,6 +92,7 @@ DescriptorBinding sampleBinding(std::uint32_t seed) { binding.samplerDepthCompare = {true, false, true}; binding.imageDepthCompare = {false, true, false}; binding.imageWritten = {false, true, true}; + binding.imageAtomic = {false, true, false}; binding.bufferAtomic = {true}; binding.bufferWritten = {false, false, true, true, false}; return binding; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomics.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomics.cpp index 7eaf3d20f..f27314c9f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomics.cpp @@ -21,6 +21,8 @@ constexpr std::uint32_t Results = 16; constexpr std::uint32_t Operations = 6; constexpr std::uint32_t TextureHeight = 8; constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t Format32SInt = 21; +constexpr std::uint32_t Format32Float = 22; constexpr std::uint32_t Type2D = 9; alignas(256) std::array Input{}; alignas(256) std::array Output{}; @@ -71,24 +73,24 @@ std::array BufferDescriptor(const void* data, std::uint32_t by return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; } -std::array TextureDescriptor(const void* data, std::uint32_t width, std::uint32_t height) { +std::array TextureDescriptor(const void* data, std::uint32_t width, std::uint32_t height, std::uint32_t format) { const auto address = static_cast(reinterpret_cast(data)); return { static_cast(address >> 8u), - static_cast((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((width - 1u) & 3u) << 30u), + static_cast((address >> 40u) & 0xffu) | (format << 20u) | (((width - 1u) & 3u) << 30u), ((width - 1u) >> 2u) | ((height - 1u) << 14u), 0xfacu | (Type2D << 28u), 0u, 0u, 0u, 0u, }; } -void Run(AgcDriver::VulkanDevice& device) { +void Run(AgcDriver::VulkanDevice& device, std::uint32_t format) { Output.fill(0xdeadbeefu); Texels.fill(0xdeadbeefu); std::vector userData(16, 0u); const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); - const auto texture = TextureDescriptor(Texels.data(), Threads, TextureHeight); + const auto texture = TextureDescriptor(Texels.data(), Threads, TextureHeight, format); std::copy(input.begin(), input.end(), userData.begin()); std::copy(output.begin(), output.end(), userData.begin() + 4); std::copy(texture.begin(), texture.end(), userData.begin() + 8); @@ -107,7 +109,7 @@ void Run(AgcDriver::VulkanDevice& device) { device.WaitIdle(); } -void Check() { +void Check(std::uint32_t format) { constexpr std::array names{"image_atomic_sub", "image_atomic_smin", "image_atomic_smax", "image_atomic_inc", "image_atomic_dec", "image_atomic_cmpswap"}; for (std::uint32_t tid = 0; tid < Threads; ++tid) { const auto* in = &Input[tid * Inputs]; @@ -127,8 +129,8 @@ void Check() { for (std::uint32_t j = 0; j < Operations; ++j) { const std::uint32_t returned = Output[tid * Results + 2u * j]; const std::uint32_t stored = Output[tid * Results + 2u * j + 1u]; - Require(returned == a, std::string(names[j]) + ": thread " + std::to_string(tid) + " returned " + std::to_string(returned) + ", expected " + std::to_string(a)); - Require(stored == expected[j], std::string(names[j]) + ": thread " + std::to_string(tid) + " stored " + std::to_string(stored) + ", expected " + std::to_string(expected[j])); + Require(returned == a, std::string(names[j]) + ", format " + std::to_string(format) + ": thread " + std::to_string(tid) + " returned " + std::to_string(returned) + ", expected " + std::to_string(a)); + Require(stored == expected[j], std::string(names[j]) + ", format " + std::to_string(format) + ": thread " + std::to_string(tid) + " stored " + std::to_string(stored) + ", expected " + std::to_string(expected[j])); } } } @@ -140,8 +142,10 @@ int main() { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; FillInput(); - Run(*device); - Check(); + for (const auto format : {Format32UInt, Format32SInt, Format32Float}) { + Run(*device, format); + Check(format); + } std::puts("image atomics tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsFloat.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsFloat.cpp index ff553a2b3..1799bebc2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsFloat.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsFloat.cpp @@ -24,6 +24,9 @@ constexpr std::uint32_t Rows = 800; constexpr std::uint32_t TextureWidth = 64; constexpr std::uint32_t TextureHeight = 4; constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t Format32SInt = 21; +constexpr std::uint32_t Format32Float = 22; +constexpr std::uint32_t Format16_16Float = 29; constexpr std::uint32_t Type2D = 9; alignas(256) std::array Buffer{}; alignas(4096) std::array Texels{}; @@ -474,22 +477,22 @@ std::array BufferDescriptor(const void* data, std::uint32_t by return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; } -std::array TextureDescriptor(const void* data) { +std::array TextureDescriptor(const void* data, std::uint32_t format) { const auto address = static_cast(reinterpret_cast(data)); return { static_cast(address >> 8u), - static_cast((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((TextureWidth - 1u) & 3u) << 30u), + static_cast((address >> 40u) & 0xffu) | (format << 20u) | (((TextureWidth - 1u) & 3u) << 30u), ((TextureWidth - 1u) >> 2u) | ((TextureHeight - 1u) << 14u), 0xfacu | (Type2D << 28u), 0u, 0u, 0u, 0u, }; } -void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target, const char* name) { +ShaderRecompiler::RecompileResult Compile(std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target, std::uint32_t format) { const std::span code(Code); std::vector userData(16, 0u); const auto buffer = BufferDescriptor(Buffer.data(), static_cast(Buffer.size() * 4u)); - const auto texture = TextureDescriptor(Texels.data()); + const auto texture = TextureDescriptor(Texels.data(), format); std::copy(buffer.begin(), buffer.end(), userData.begin()); std::copy(texture.begin(), texture.end(), userData.begin() + 4); const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; @@ -501,7 +504,12 @@ void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderRe {0, 0, 0, 128} }; request.useCache = false; - const auto result = ShaderRecompiler::Recompile(request); + return ShaderRecompiler::Recompile(request); +} + +void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target, const char* name, std::uint32_t format) { + const std::span code(Code); + const auto result = Compile(waveSize, target, format); Texels.fill(0xdeadbeefu); for (std::uint32_t first = 0; first < Rows; first += waveSize) { Buffer.fill(0xdeadbeefu); @@ -521,7 +529,7 @@ void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderRe for (std::uint32_t j = 0; j < Checked; ++j) { const auto actual = Buffer[MaxLanes * Inputs + lane * Results + j]; char message[192]; - std::snprintf(message, sizeof(message), "image float atomics %s: row %u %s is 0x%08x, expected 0x%08x", name, row, names[j], actual, expected[j]); + std::snprintf(message, sizeof(message), "image float atomics %s, format %u: row %u %s is 0x%08x, expected 0x%08x", name, format, row, names[j], actual, expected[j]); Require(actual == expected[j], message); } } @@ -534,9 +542,18 @@ int main() { try { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; - Run(*device, 32, device->Target(), "wave32"); - Run(*device, 64, device->Target(), "wave64"); - Run(*device, 64, device->ComputeTarget(32), "wave64 split"); + for (const auto format : {Format32UInt, Format32SInt, Format32Float}) { + Run(*device, 32, device->Target(), "wave32", format); + Run(*device, 64, device->Target(), "wave64", format); + Run(*device, 64, device->ComputeTarget(32), "wave64 split", format); + } + bool refused = false; + try { + static_cast(Compile(32, device->Target(), Format16_16Float)); + } catch (const std::exception& error) { + refused = std::string(error.what()).find("atomic image descriptor uses an unsupported format 29") != std::string::npos; + } + Require(refused, "image float atomics on a 16_16 float surface were not refused"); std::puts("image float atomics tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp index 7fef993b7..2b06d89d7 100644 --- a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp +++ b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp @@ -241,6 +241,7 @@ void DescriptorBindingBuilder::Populate(BindingAllocationResult& allocation, con const auto& image = info.images.at(resource); physical.imageWritten.push_back(image.written || image.atomic); physical.imageDepthCompare.push_back(image.depthCompare); + physical.imageAtomic.push_back(image.atomic); } break; case DescriptorRole::GuestSamplers: diff --git a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp index 4308347e1..943b3e14e 100644 --- a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp @@ -151,8 +151,8 @@ DecodedImage decodeImageDescriptor(const DescriptorValue& descriptor, const Imag decoded.dimension = descriptorDimension(descriptor, base.dimension); decoded.cube = descriptorIsCube(descriptor); const auto format = rawImageFormat(descriptor); - if (base.atomic && format != IrBufferFormat::Format32UInt) { - throw std::runtime_error("atomic image descriptor uses an unsupported format"); + if (base.atomic && format != IrBufferFormat::Format32UInt && format != IrBufferFormat::Format32SInt && format != IrBufferFormat::Format32Float) { + throw std::runtime_error("atomic image descriptor uses an unsupported format " + std::to_string(static_cast(format))); } const bool storage = base.resourceClass == ImageResourceClass::Storage; decoded.fmask = IsFmaskTextureFormat(format); @@ -170,7 +170,7 @@ DecodedImage decodeImageDescriptor(const DescriptorValue& descriptor, const Imag decoded.shaderSwizzle = descriptorImageSwizzle(descriptor); } const bool rawSintStorage = storage && format == IrBufferFormat::Format32SInt && base.written && !base.read && !base.atomic; - decoded.numericClass = SampledTextureNumericClass(format); + decoded.numericClass = base.atomic ? IrTextureNumericClass::Uint : SampledTextureNumericClass(format); if (!storage && !base.depthCompare && IsDepthBitsTexture(descriptor.dwords[1], descriptor.dwords[3])) { decoded.depthBits = true; decoded.depthUnorm16 = DepthBitsTextureWidth(descriptor.dwords[1], descriptor.dwords[3]) == 16u; diff --git a/core/shader/recompiler/Recompiler.hpp b/core/shader/recompiler/Recompiler.hpp index 9cf9234e1..054374fb5 100644 --- a/core/shader/recompiler/Recompiler.hpp +++ b/core/shader/recompiler/Recompiler.hpp @@ -310,6 +310,7 @@ struct DescriptorBinding { // Guest image elements the shader stores to (or updates atomically); the others are only read. std::vector imageWritten; std::vector imageDepthCompare; + std::vector imageAtomic; // Guest buffer elements the shader updates atomically (one entry per element of a GuestBuffers // binding, empty otherwise). An atomic on a host-imported range is a serialized PCIe round trip // (~0.4-0.5 us each on NVIDIA), so a driver may keep these elements in device-local memory. diff --git a/core/shader/recompiler/ShaderDiskCache.cpp b/core/shader/recompiler/ShaderDiskCache.cpp index fd57be587..c93a751b1 100644 --- a/core/shader/recompiler/ShaderDiskCache.cpp +++ b/core/shader/recompiler/ShaderDiskCache.cpp @@ -64,7 +64,7 @@ namespace ShaderRecompiler::ShaderDiskCache { #if defined(__linux__) && defined(__x86_64__) && defined(__GLIBCXX__) static_assert(sizeof(RecompileResult) == 176, "RecompileResult changed: update EncodeResult and DecodeResult"); -static_assert(sizeof(DescriptorBinding) == 264, "DescriptorBinding changed: update the binding encoder"); +static_assert(sizeof(DescriptorBinding) == 304, "DescriptorBinding changed: update the binding encoder"); static_assert(sizeof(VertexAttribute) == 28, "VertexAttribute changed: update the attribute encoder"); static_assert(sizeof(FragmentParameter) == 12, "FragmentParameter changed: update the parameter encoder"); static_assert(sizeof(CompiledShaderInfo) == 304, "CompiledShaderInfo changed: update the info encoder"); @@ -248,6 +248,7 @@ void encodeBinding(Writer& writer, const DescriptorBinding& binding) { writer.Flags(binding.samplerDepthCompare); writer.Flags(binding.imageWritten); writer.Flags(binding.imageDepthCompare); + writer.Flags(binding.imageAtomic); writer.Flags(binding.bufferAtomic); writer.Flags(binding.bufferWritten); } @@ -266,6 +267,7 @@ void decodeBinding(Reader& reader, DescriptorBinding& binding) { reader.Flags(binding.samplerDepthCompare); reader.Flags(binding.imageWritten); reader.Flags(binding.imageDepthCompare); + reader.Flags(binding.imageAtomic); reader.Flags(binding.bufferAtomic); reader.Flags(binding.bufferWritten); } diff --git a/core/shader/recompiler/ShaderDiskCache.hpp b/core/shader/recompiler/ShaderDiskCache.hpp index 216931eee..84c7a081b 100644 --- a/core/shader/recompiler/ShaderDiskCache.hpp +++ b/core/shader/recompiler/ShaderDiskCache.hpp @@ -12,7 +12,7 @@ namespace ShaderRecompiler::ShaderDiskCache { -inline constexpr std::uint32_t FormatVersion = 3; +inline constexpr std::uint32_t FormatVersion = 4; enum class LoadStatus { Loaded, diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..2b6f95bd7 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -82,6 +82,7 @@ Throughout the project, every function at every stage either **does exactly what - [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) use preallocated host storage instead of the supplied guest work area; its existing size formula and the 512-byte object layouts remain unverified. Waiting-queue pool thread limits and waiter priority ordering are not implemented. Finalization wakes blocked queue callers with `ULT_ERROR_STATE`. - [MIMG `tfe` and `lwe`](../../core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp) never write the status VGPR after the data. On hardware it is written only when a texel fetch hits an unmapped page of a partially resident texture, or, for `lwe` on sample/gather, when the LOD is below the T# `MIN_LOD_WARN`; texture memory is always resident here, and `lwe` on sample, gather and `image_get_lod` throws. +- [Image atomics](../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) on 32_SINT and 32_FLOAT surfaces act on the raw dword through an R32_UINT storage view; their float results are assumed to match those measured on 32_UINT surfaces. Other formats throw. - The shader recompiler [ignores `s_setreg_b32`](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp): writes to MODE (float rounding and denormal controls) and the other hardware registers have no effect, and `s_getreg_b32` reads of the MODE round fields report round to nearest even even after `s_setreg_b32` wrote another mode. The initial round mode from the shader's program registers is not read either. - [Shader recompilation](../../core/shader/recompiler/Recompiler.cpp) currently occurs right before it was transferred to Vulkan with caching, but should be moved to the [relinker](../../core/relinker/main.cpp) stage. For this purpose, [shader/recompiler](../../core/shader/recompiler) was written completely independently from [libs/prx](../../core/libs/prx). - [v_fma_f64](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp) is fused exactly only for normal inputs: with a subnormal input it is a separate multiply and add, which rounds twice. From 5e851fbb2629e934f4cd6cbdbb57a06d4c31e99d Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:32:17 +0200 Subject: [PATCH 130/164] feat(agc): implement the interpolant mapping variant for high-half f16 inputs PPSA26344 imports libSceAgc NID dbOlWdppb4o, whose name is unknown, and calls it with the (regs, gs, ps) arguments of sceAgcCreateInterpolantMapping to fill the 32 SPI_PS_INPUT_CNTL registers. It is exported as sceAgcUnknownCreateInterpolantMapping. It differs from sceAgcCreateInterpolantMapping only for inputs that carry the high f16 half alone (is_f16 2): these take the default bit and DEFAULT_VAL from the high half, as KytyPS5 does. Both exports share validation and the register loop. More than 32 pixel shader inputs now throw; before, they were written past the 32-register block. agc_interpolant_mapping covers both exports. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgc/CMakeLists.txt | 15 ++ .../Shader/src/InterpolantMapping.cpp | 75 ++++++++-- .../libSceAgc/tests/InterpolantMapping.cpp | 134 ++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 210 insertions(+), 15 deletions(-) create mode 100644 core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp diff --git a/core/libs/prx/libSceAgc/CMakeLists.txt b/core/libs/prx/libSceAgc/CMakeLists.txt index 4e9a781f8..36e6c7a0e 100644 --- a/core/libs/prx/libSceAgc/CMakeLists.txt +++ b/core/libs/prx/libSceAgc/CMakeLists.txt @@ -121,6 +121,21 @@ if(BUILD_TESTING) endif() endif() +add_test_executable(agc_interpolant_mapping_tests EXCLUDE_FROM_ALL tests/InterpolantMapping.cpp) +target_include_directories(agc_interpolant_mapping_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_interpolant_mapping_tests PRIVATE libSceAgc libc) +set_target_properties(agc_interpolant_mapping_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_interpolant_mapping_tests) + +if(BUILD_TESTING) + set_target_properties(agc_interpolant_mapping_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_interpolant_mapping COMMAND agc_interpolant_mapping_tests) + if(WIN32) + set_tests_properties(agc_interpolant_mapping PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + if(BUILD_TESTING) add_test_executable(guest_shader_link_tests ${LIBS_INCLUDE_DIR}/tests/GuestShaderLink.cpp) target_include_directories(guest_shader_link_tests PRIVATE ${LIBS_INCLUDE_DIR}) diff --git a/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp b/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp index 515458391..5ce4d1555 100644 --- a/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp +++ b/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp @@ -8,11 +8,48 @@ #include "prx/libSceAgc/Shader/include/ShaderUtils.hpp" #include "prx/libSceAgc/Shader/include/ShaderConstants.hpp" -extern "C" { +namespace { -APS5_EXPORT("HV4j+E0MBHE", sceAgcCreateInterpolantMapping); -int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps) { - constexpr auto fn = __func__; +constexpr std::uint32_t InterpolantRegisterCount = 32; +constexpr std::uint32_t InterpolantF16HiOnly = 2; + +using CreateInterpolantValueFn = std::uint32_t (*)(std::uint32_t psWord, const ShaderSemantic* gsSemantic); + +std::uint32_t CreateInterpolantValue(std::uint32_t psWord, const ShaderSemantic* gsSemantic) { + const std::uint32_t value = ((psWord & 0x00300000u) != 0) + ? CreateInterpolantF16Value(psWord, gsSemantic) + : CreateInterpolantNonF16Value(psWord, gsSemantic); + + return (gsSemantic == nullptr) + ? CreateInterpolantDefaultValue(value, psWord) + : CreateInterpolantMappingValue(value, psWord, ShaderSemanticWord(*gsSemantic)); +} + +std::uint32_t CreateInterpolantF16HiValue(std::uint32_t psWord, const ShaderSemantic* gsSemantic) { + std::uint32_t value = ((psWord << 4u) & 0x03000000u) | 0x00080000u; + if (gsSemantic == nullptr || (psWord & ShaderSemanticWord(*gsSemantic) & 0x00200000u) == 0) { + value |= 0x00000020u; + } + value = ApplyInterpolantDefaultValueHi(value, psWord); + value |= ((psWord >> 30u) & 0x3u) << 8u; + if (gsSemantic == nullptr) { + return value; + } + + value |= (ShaderSemanticWord(*gsSemantic) >> 8u) & 0x1Fu; + if ((psWord & 0x01400000u) != 0) { + value |= 0x00000400u; + } + return value; +} + +std::uint32_t CreateInterpolantValueSplitF16(std::uint32_t psWord, const ShaderSemantic* gsSemantic) { + return (((psWord >> 20u) & 0x3u) == InterpolantF16HiOnly) + ? CreateInterpolantF16HiValue(psWord, gsSemantic) + : CreateInterpolantValue(psWord, gsSemantic); +} + +int CreateInterpolantMapping(const char* fn, ShaderRegister* regs, const Shader* gs, const Shader* ps, CreateInterpolantValueFn createValue) { if (regs == nullptr) { throw std::runtime_error(std::string(fn) + ": regs is null"); } @@ -26,6 +63,10 @@ int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* throw std::runtime_error(std::string(fn) + ": ps->input_semantics is null but num_input_semantics != 0"); } + if (ps->num_input_semantics > InterpolantRegisterCount) { + throw std::runtime_error(std::string(fn) + ": ps->num_input_semantics exceeds the interpolant register count"); + } + if (gs == nullptr) { throw std::runtime_error(std::string(fn) + ": gs is null"); } @@ -37,17 +78,7 @@ int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* for (std::uint32_t i = 0; i < ps->num_input_semantics; ++i) { const ShaderSemantic& psSemantic = ps->input_semantics[i]; const ShaderSemantic* gsSemantic = FindOutputSemantic(gs, psSemantic.semantic); - const std::uint32_t psWord = ShaderSemanticWord(psSemantic); - - std::uint32_t value = ((psWord & 0x00300000u) != 0) - ? CreateInterpolantF16Value(psWord, gsSemantic) - : CreateInterpolantNonF16Value(psWord, gsSemantic); - - value = (gsSemantic == nullptr) - ? CreateInterpolantDefaultValue(value, psWord) - : CreateInterpolantMappingValue(value, psWord, ShaderSemanticWord(*gsSemantic)); - - SetInterpolantRegister(regs, i, value); + SetInterpolantRegister(regs, i, createValue(ShaderSemanticWord(psSemantic), gsSemantic)); } FillIdentityInterpolants(regs, ps->num_input_semantics); @@ -55,3 +86,17 @@ int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* } } + +extern "C" { + +APS5_EXPORT("HV4j+E0MBHE", sceAgcCreateInterpolantMapping); +int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps) { + return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValue); +} + +APS5_EXPORT("dbOlWdppb4o", sceAgcUnknownCreateInterpolantMapping); +int APS5_VABI sceAgcUnknownCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps) { + return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValueSplitF16); +} + +} diff --git a/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp b/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp new file mode 100644 index 000000000..bee89bb8d --- /dev/null +++ b/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp @@ -0,0 +1,134 @@ +#include "SceShaders.hpp" +#include "prx/libc/include/Shutdown.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp" + +#include +#include +#include +#include +#include + +extern "C" int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps); +extern "C" int APS5_VABI sceAgcUnknownCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps); + +namespace { + +using Registers = std::array; + +void check(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +template +void expectFailure(TAction action) { + try { + action(); + } catch (const std::exception& error) { + check(error.what()[0] != '\0', "empty exception message"); + return; + } + throw std::runtime_error("expected an exception"); +} + +Registers filled() { + Registers regs{}; + std::memset(regs.data(), 0xcc, sizeof(regs)); + return regs; +} + +ShaderSemantic semantic(std::uint32_t id, std::uint32_t hardwareMapping, std::uint32_t f16) { + ShaderSemantic value{}; + value.semantic = id; + value.hardware_mapping = hardwareMapping; + value.is_f16 = f16; + return value; +} + +void checkIdentity(const Registers& regs, std::uint32_t first, const char* message) { + for (std::uint32_t i = first; i < regs.size(); ++i) { + check(regs[i].offset == ShaderRegs::SPI_PS_INPUT_CNTL_0 + i && regs[i].value == i, message); + } +} + +void testIdentity() { + auto regs = filled(); + check(sceAgcUnknownCreateInterpolantMapping(regs.data(), nullptr, nullptr) == 0, "mapping without shaders failed"); + checkIdentity(regs, 0, "mapping without a pixel shader is not the identity"); + Shader ps{}; + regs = filled(); + check(sceAgcUnknownCreateInterpolantMapping(regs.data(), nullptr, &ps) == 0, "mapping without inputs failed"); + checkIdentity(regs, 0, "mapping without inputs is not the identity"); +} + +void testMapping() { + std::array inputs{ + semantic(1, 0, 2), semantic(2, 0, 2), semantic(5, 0, 2), semantic(3, 0, 0), semantic(4, 0, 3)}; + inputs[0].is_flat_shaded = 1; + inputs[0].default_value = 1; + inputs[0].default_value_hi = 3; + inputs[1].default_value_hi = 1; + inputs[2].is_flat_shaded = 1; + inputs[2].default_value_hi = 2; + inputs[3].is_flat_shaded = 1; + inputs[3].default_value = 2; + std::array outputs{semantic(1, 7, 3), semantic(2, 4, 1), semantic(3, 9, 0), semantic(4, 2, 1)}; + Shader gs{}; + gs.output_semantics = outputs.data(); + gs.num_output_semantics = static_cast(outputs.size()); + Shader ps{}; + ps.input_semantics = inputs.data(); + ps.num_input_semantics = static_cast(inputs.size()); + + auto split = filled(); + check(sceAgcUnknownCreateInterpolantMapping(split.data(), &gs, &ps) == 0, "split f16 mapping failed"); + for (std::uint32_t i = 0; i < inputs.size(); ++i) { + check(split[i].offset == ShaderRegs::SPI_PS_INPUT_CNTL_0 + i, "interpolant register offset changed"); + } + check(split[0].value == 0x02680707u, "high f16 half provided by the gs is not mapped"); + check(split[1].value == 0x02280124u, "high f16 half missing from the gs does not use its default"); + check(split[2].value == 0x02480220u, "unmatched high f16 half does not use its default"); + check(split[3].value == 0x00000609u, "32-bit interpolant mapping changed"); + check(split[4].value == 0x03180002u, "full f16 interpolant mapping changed"); + checkIdentity(split, static_cast(inputs.size()), "unused interpolants are not the identity"); + + auto regular = filled(); + check(sceAgcCreateInterpolantMapping(regular.data(), &gs, &ps) == 0, "mapping failed"); + check(regular[3].value == split[3].value && regular[4].value == split[4].value, "mappings differ outside the high f16 half mode"); + checkIdentity(regular, static_cast(inputs.size()), "unused interpolants are not the identity"); +} + +void testRejections() { + std::array inputs{}; + Shader gs{}; + Shader ps{}; + ps.input_semantics = inputs.data(); + ps.num_input_semantics = static_cast(inputs.size()); + std::array regs{}; + std::memset(regs.data(), 0xcc, sizeof(regs)); + const auto saved = regs; + expectFailure([&] { sceAgcCreateInterpolantMapping(regs.data(), &gs, &ps); }); + expectFailure([&] { sceAgcUnknownCreateInterpolantMapping(regs.data(), &gs, &ps); }); + check(std::memcmp(regs.data(), saved.data(), sizeof(regs)) == 0, "rejected mapping wrote registers"); + ps.num_input_semantics = 1; + expectFailure([&] { sceAgcUnknownCreateInterpolantMapping(regs.data(), nullptr, &ps); }); + expectFailure([&] { sceAgcUnknownCreateInterpolantMapping(nullptr, &gs, &ps); }); +} + +} + +int main() { + try { + testIdentity(); + testMapping(); + testRejections(); + LibcRunShutdown_nid_postfix(); + std::puts("AGC interpolant mapping tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + try { LibcRunShutdown_nid_postfix(); } + catch (const std::exception& shutdown) { std::fprintf(stderr, "shutdown: %s\n", shutdown.what()); } + return 1; + } +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..612cd0f7f 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -36,6 +36,7 @@ Throughout the project, every function at every stage either **does exactly what - [qj7QZpgr9Uw](../../core/libs/prx/libSceAgc/DcbState/src/ContextState.cpp) (libSceAgc) - unknown name - [fd5Bp5tGTgo](../../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name - [dolOmWH+huQ](../../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name +- [dbOlWdppb4o](../../core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp) (libSceAgc) - unknown name; same arguments as `sceAgcCreateInterpolantMapping`; the `SPI_PS_INPUT_CNTL` bits for `is_f16` 2 inputs follow KytyPS5, not confirmed on hardware - [V++UgBtQhn0](../../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc) - unknown name - [gQkqkLttcpw](../../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - unknown name, signature - [sceAgcDcbPrimeUtcl2](../../core/libs/prx/libSceAgc/DcbState/src/Display.cpp) (libSceAgc) - assumed to write the packet `sceAgcAcbPrimeUtcl2` writes: PRIME_UTCL2 has the same 5 dwords on the graphics and compute rings. Not confirmed on a title. Both write a NOP of that size, as there is no TLB to prime From 9df0de2990383c677438c734975f406dc9244fdc Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 15:44:06 +0200 Subject: [PATCH 131/164] feat(agc): implement COND_EXEC Validate refused every COND_EXEC, so a submission that uses one was rejected as a whole. The packet now reads the dword at its address once the earlier packets of its queue and their GPU writes have completed, and skips the exec_count dwords after it when the dword is zero. The command copy that inlines INDIRECT_BUFFERs records where each range ends, so a range over an INDIRECT_BUFFER skips the whole buffer. Unaligned addresses, address bits 48 and up, and reserved control and count bits throw; the cache policy is accepted on compute queues only, as the MEC defines it. A flip inside a range, a range that ends inside a packet or past its command buffer, and a chained INDIRECT_BUFFER or REWIND inside a range throw. agc_driver_pm4 and agc_driver_flip cover the skips and the rejections. Co-Authored-By: Claude Opus 5.5 --- .../Execution/include/Driver/Driver.hpp | 2 +- .../include/Driver/Queues/Submission.hpp | 1 + .../libSceAgcDriver/Execution/include/Pm4.hpp | 2 + .../src/Driver/Packets/CommandValidation.cpp | 12 +- .../src/Driver/Packets/PacketExecution.cpp | 8 +- .../src/Driver/Queues/Submission.cpp | 32 +++- .../prx/libSceAgcDriver/Execution/src/Pm4.cpp | 27 ++- core/libs/prx/libSceAgcDriver/tests/Flip.cpp | 6 + core/libs/prx/libSceAgcDriver/tests/Pm4.cpp | 154 +++++++++++++++++- docs/dev/TechnicalDebt.md | 3 + 10 files changed, 236 insertions(+), 11 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp index 00f21aa66..cf2941e62 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp @@ -128,7 +128,7 @@ private: static std::array& writeHistory(); static std::size_t& writeCursor(); static void dumpPackets(std::span commands, const std::uint32_t* guest = nullptr); - static void validate(std::span commands, std::uint32_t queue, const std::uint32_t* guest = nullptr); + static void validate(const Submission& submission, const std::uint32_t* guest = nullptr); static void reportSkip(const char* kind, const std::string& what); static std::string dumpRequest(std::uint64_t address, const ShaderRecompiler::RecompileRequest& request); static bool matchesFillKernel(std::span code, const std::vector& userData, const ShaderRecompiler::ShaderComputeStageInfo& compute); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp index ed7da06d9..cdc9f1683 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp @@ -25,6 +25,7 @@ struct Submission { std::shared_ptr shaders; std::map> flips; std::map> renderingWaits; + std::map conditionalEnds; bool suspend = false; bool waitFree = false; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp index 3add10cd8..c413e357e 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp @@ -96,6 +96,8 @@ bool WaitSatisfiedUnchecked(std::span packet); // holds will store), without reading memory. bool WaitComparesValue(std::span packet, std::uint64_t value); std::size_t WaitAwaitedBytes(std::span packet); +std::size_t ConditionalWords(std::span packet); +std::uint32_t ReadCondition(std::span packet); // A label write (RELEASE_MEM with a data select, WRITE_DATA to memory): the destination and the // bytes it stores, so the write can be recorded on the GPU behind the work it signals. Packets // without a memory destination decode to nothing. No allocation per label (tens of thousands per diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp index 492086cc3..dd7c023ba 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp @@ -47,8 +47,12 @@ void Driver::dumpPackets(std::span commands, const std::uin } } -void Driver::validate(std::span commands, std::uint32_t queue, const std::uint32_t* guest) { +void Driver::validate(const Submission& submission, const std::uint32_t* guest) { + const std::span commands = submission.commands; + const auto queue = submission.queue; + std::vector guarded; for (std::size_t cursor = 0; cursor < commands.size();) { + std::erase_if(guarded, [cursor](std::size_t end) { return end <= cursor; }); const auto header = commands[cursor]; if (Pm4::FillerPacket(header)) { ++cursor; continue; } if ((header & 0xc0000000u) != 0xc0000000u) { @@ -65,6 +69,12 @@ void Driver::validate(std::span commands, std::uint32_t que require(count <= commands.size() - cursor, "truncated PM4 packet"); try { Pm4::Validate(commands.subspan(cursor, count), queue); + if (header == FlipPacketHeader && !guarded.empty()) throw std::runtime_error("a flip inside a conditional execution range (conditional flip reservation) is not implemented"); + if (((header >> 8u) & 0xffu) == 0x22u) { + const auto end = submission.conditionalEnds.find(cursor); + if (end == submission.conditionalEnds.end()) throw std::runtime_error("conditional execution range is not made of whole packets of its command buffer"); + guarded.push_back(end->second); + } } catch (const std::exception& error) { dumpPackets(commands, guest); throw std::runtime_error("AGC driver: " + Pm4::Name(header) + " at DWORD " + std::to_string(cursor) + ": " + error.what()); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp index 80355fc7e..06dcb02cb 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp @@ -111,6 +111,7 @@ void Driver::execute(const Submission& submission) { const auto count = Pm4::PacketWords(header); const auto packet = std::span(submission.commands).subspan(cursor, count); const auto opcode = (header >> 8u) & 0xffu; + auto nextCursor = cursor + count; std::shared_lock deviceUse(deviceReplacement, std::defer_lock); if (opcode != 0x3c && opcode != 0x93 && header != RenderingWaitPacketHeader && header != FlipPacketHeader) deviceUse.lock(); @@ -272,6 +273,11 @@ void Driver::execute(const Submission& submission) { } }); }); finishDrawPacket(drawn); + } else if (opcode == 0x22) { + recordQueuedLabelsBeforeRead(submission.queue); + const auto condition = Pm4::ReadCondition(packet); + if (condition == 0) nextCursor = submission.conditionalEnds.at(cursor); + if (traceGpu) std::fprintf(stderr, "[gpu] %.1f queue 0x%x COND_EXEC at DWORD %zu reads 0x%x at 0x%llx: %s %zu dwords\n", TraceMs(), submission.queue, cursor, condition, static_cast(packet[1] | (static_cast(packet[2]) << 32u)), condition == 0 ? "skips" : "executes", submission.conditionalEnds.at(cursor) - cursor - count); } else if (sampleDump && !wroteOnGpu) { dumpSampleCounters(packet[2] | (static_cast(packet[3]) << 32u)); } else if (opcode != 0x42 && opcode != 0x46 && opcode != 0x58) { @@ -284,7 +290,7 @@ void Driver::execute(const Submission& submission) { if (endOfPipeInterrupt && !interruptDeferred) AgcDriverDeliverEopInterrupt(submission.queue); } if (drawPacket || (sampleDump && wroteOnGpu)) Graphics::Recorder::CountRecordedWork(); - cursor += count; + cursor = nextCursor; } static const bool submitAtEnd = std::getenv("APS5_SUBMIT_AT_END") != nullptr; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp index 4dbe52c02..edd9b5b70 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp @@ -11,6 +11,7 @@ namespace AgcDriver::DriverDetail { void Driver::copyCommands(Submission& submission, const std::uint32_t* guest, std::size_t words) { submission.commands.clear(); + submission.conditionalEnds.clear(); std::size_t budget = std::size_t{1} << 26u; copySegment(submission, guest, words, budget); } @@ -18,34 +19,57 @@ void Driver::copyCommands(Submission& submission, const std::uint32_t* guest, st bool Driver::copySegment(Submission& submission, const std::uint32_t* guest, std::size_t words, std::size_t& budget) { require(words <= budget, "command buffer jumps exceed the copy limit (a jump loop?)"); budget -= words; + std::vector> guarded; + const auto reach = [&](std::size_t cursor, std::size_t next) { + std::erase_if(guarded, [&](const auto& range) { + if (range.first != cursor) return false; + submission.conditionalEnds.emplace(range.second, submission.commands.size()); + return true; + }); + for (const auto& range : guarded) require(range.first >= next, "conditional execution range ends inside a packet"); + }; for (std::size_t cursor = 0; cursor < words;) { const auto header = guest[cursor]; - if (Pm4::FillerPacket(header)) { submission.commands.push_back(header); ++cursor; continue; } + if (Pm4::FillerPacket(header)) { + reach(cursor, cursor + 1); + submission.commands.push_back(header); + ++cursor; + continue; + } const auto count = (header & 0xc0000000u) == 0xc0000000u ? Pm4::PacketWords(header) : words - cursor; if ((header & 0xc0000000u) != 0xc0000000u || count > words - cursor) { submission.commands.insert(submission.commands.end(), guest + cursor, guest + words); return false; } + reach(cursor, cursor + count); const auto opcode = (header >> 8u) & 0xffu; if (opcode == 0x3fu) { require(count == 4, "invalid INDIRECT_BUFFER size"); const auto* target = reinterpret_cast(static_cast(guest[cursor + 1] & ~3u) | (static_cast(guest[cursor + 2] & 0xffffu) << 32u)); const std::size_t targetWords = guest[cursor + 3] & 0xfffffu; const bool chain = (guest[cursor + 3] & (1u << 20u)) != 0; + require(!chain || guarded.empty(), "a chained INDIRECT_BUFFER inside a conditional execution range is not implemented"); GuestMemory::CheckRange(target, targetWords * sizeof(std::uint32_t), alignof(std::uint32_t)); - if (copySegment(submission, target, targetWords, budget)) return true; + if (copySegment(submission, target, targetWords, budget)) { + require(guarded.empty(), "a REWIND inside a conditional execution range is not implemented"); + return true; + } if (chain) return false; cursor += count; continue; } + if (opcode == 0x22u && count == 5) guarded.emplace_back(cursor + count + Pm4::ConditionalWords(std::span(guest + cursor, count)), submission.commands.size()); submission.commands.insert(submission.commands.end(), guest + cursor, guest + cursor + count); cursor += count; if (opcode == 0x59u) { + require(guarded.empty(), "a REWIND inside a conditional execution range is not implemented"); submission.rewindTail = guest + cursor; submission.rewindWords = words - cursor; return true; } } + reach(words, words); + require(guarded.empty(), "conditional execution range exceeds its command buffer"); return false; } @@ -104,7 +128,7 @@ void Driver::executeRewindTail(const Submission& stalled) { Submission tail{}; tail.queue = stalled.queue; copyCommands(tail, stalled.rewindTail, stalled.rewindWords); - validate(tail.commands, tail.queue, stalled.rewindTail); + validate(tail, stalled.rewindTail); waitForFlipRoom(tail); { std::lock_guard lock(mutex); @@ -135,7 +159,7 @@ void Driver::Submit(const Packet* packet, std::uint32_t queue) { copyCommands(submission, descriptor.addr, descriptor.dw_num); } const auto copied = profile ? std::chrono::steady_clock::now() : start; - validate(submission.commands, queue, descriptor.addr); + validate(submission, descriptor.addr); waitForFlipRoom(submission); static const bool trace = std::getenv("APS5_TRACE_GPU") != nullptr; if (trace) std::fprintf(stderr, "[gpu] %.1f submit queue=0x%x dwords=%zu at %p\n", TraceMs(), queue, submission.commands.size(), static_cast(descriptor.addr)); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index 9dfdfad7b..c4e8a16a9 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -166,7 +166,7 @@ std::string_view UnsupportedReason(std::uint32_t header) { } } switch (opcode) { - case 0x11: case 0x12: case 0x13: case 0x15: case 0x16: case 0x26: case 0x27: + case 0x11: case 0x12: case 0x13: case 0x15: case 0x16: case 0x22: case 0x26: case 0x27: case 0x2a: case 0x2d: case 0x2f: case 0x35: case 0x37: case 0x40: case 0x42: case 0x46: case 0x50: case 0x58: case 0x63: case 0x64: case 0x69: case 0x76: case 0x79: case 0x7a: case 0x81: case 0x83: case 0x9f: return {}; @@ -176,7 +176,6 @@ std::string_view UnsupportedReason(std::uint32_t header) { case 0x3a: case 0x8d: return "graphics draw, shader stages and guest render-target materialization are not implemented"; case 0x20: return "GPU query predication is not implemented"; - case 0x22: return "conditional command execution and conditional flip reservation are not implemented"; case 0x33: case 0x3f: return "nested command buffers, branching and nested flip reservation are not implemented"; case 0x3c: case 0x93: return {}; case 0x39: @@ -194,6 +193,8 @@ std::string_view UnsupportedReason(std::uint32_t header) { } } +constexpr std::uint32_t ConditionCachePolicy = 3u << 25u; +constexpr std::uint32_t ConditionalWordsMask = 0x3fffu; constexpr std::uint32_t DmaSourceCachePolicy = 3u << 13u; constexpr std::uint32_t DmaDestinationCachePolicy = 3u << 25u; @@ -296,6 +297,13 @@ void Validate(std::span packet, std::uint32_t queue) { require(packet.size() == 3 || packet.size() == 4, "invalid indirect dispatch size"); require((packet.back() & ~0xa020u) == 0x41u, "indirect dispatch modifiers are not implemented"); break; + case 0x22: + size(5); + require((packet[1] & 3u) == 0, "COND_EXEC reserved address bits are not implemented"); + require(packet[2] <= 0xffffu, "COND_EXEC address bits above 48 are not implemented"); + require((packet[3] & ~(queue == 0 ? 0u : ConditionCachePolicy)) == 0, "COND_EXEC reserved control fields are not implemented"); + require((packet[4] & ~ConditionalWordsMask) == 0, "COND_EXEC reserved count bits are not implemented"); + break; case 0x42: size(2); require(packet[1] == 0, "unsupported PFP_SYNC_ME payload"); break; case 0x46: { require((packet[1] & ~0x73fu) == 0, "unsupported EVENT_WRITE flags or reserved bits"); @@ -462,6 +470,19 @@ std::size_t WaitAwaitedBytes(std::span packet) { return wide && (packet.size() < 8 || packet[7] != 0) ? 8 : 4; } +std::size_t ConditionalWords(std::span packet) { + require(packet.size() == 5 && ((packet[0] >> 8u) & 0xffu) == 0x22u, "expected COND_EXEC packet"); + return packet[4] & ConditionalWordsMask; +} + +std::uint32_t ReadCondition(std::span packet) { + require(packet.size() == 5 && ((packet[0] >> 8u) & 0xffu) == 0x22u, "expected COND_EXEC packet"); + std::uint32_t value = 0; + const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::Wait); + GuestMemory::Read(address(packet[1], packet[2]), std::as_writable_bytes(std::span(&value, 1)), 4); + return value; +} + std::optional DecodeLabelWrite(std::span packet) { const auto opcode = (packet[0] >> 8u) & 0xffu; if (opcode == 0x49 && packet.size() >= 7) { @@ -537,7 +558,7 @@ bool UsesGpuCacheBarrier(std::span packet) { bool AccessesMemory(std::uint32_t header) { switch ((header >> 8u) & 0xffu) { - case 0x27: case 0x3c: case 0x93: case 0x49: case 0x16: case 0x2d: case 0x35: case 0x24: case 0x25: case 0x2c: case 0x38: case 0x37: case 0x40: case 0x50: case 0x63: case 0x64: case 0x83: case 0x9f: return true; + case 0x22: case 0x27: case 0x3c: case 0x93: case 0x49: case 0x16: case 0x2d: case 0x35: case 0x24: case 0x25: case 0x2c: case 0x38: case 0x37: case 0x40: case 0x50: case 0x63: case 0x64: case 0x83: case 0x9f: return true; default: return false; } } diff --git a/core/libs/prx/libSceAgcDriver/tests/Flip.cpp b/core/libs/prx/libSceAgcDriver/tests/Flip.cpp index 9beb24074..596974c90 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Flip.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Flip.cpp @@ -114,6 +114,12 @@ void testFlipAndBoundary() { Packet rejected{rollback.data(), 12, 0, {}}; expectFailure([&] { sceAgcDriverSubmitDcb(&rejected); }); check(output->state->alive == 0 && output->state->ready == 0, "rejected submission retained or executed a reservation"); + alignas(4) static std::uint32_t condition = 1; + const auto conditionAddress = reinterpret_cast(&condition); + std::array guarded{0xc0032200, static_cast(conditionAddress), static_cast(conditionAddress >> 32u), 0, 6, 0xc004105c, 7, 0, 1, 0, 0}; + Packet conditionalFlip{guarded.data(), 11, 0, {}}; + check(expectFailure([&] { sceAgcDriverSubmitDcb(&conditionalFlip); }).find("a flip inside a conditional execution range") != std::string::npos, "a flip inside a COND_EXEC range was not rejected"); + check(output->state->alive == 0 && output->state->ready == 0, "a rejected conditional flip retained or executed a reservation"); { std::lock_guard lock(output->state->mutex); output->state->block = true; diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index 4c559fc5f..d057b23ff 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -2,12 +2,16 @@ #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "prx/libSceAgcDriver/Submit/include/Acb.hpp" #include "prx/libc/include/Shutdown.hpp" +#include +#include #include #include #include #include #include +#include #include #ifdef _WIN32 #ifndef NOMINMAX @@ -405,6 +409,60 @@ void testMemorySynchronization() { check(memory.destination == 42, "failed synchronization changed the destination"); } +void testConditionalValidation() { + alignas(4) static std::uint32_t condition = 0; + const auto valid = makePacket(0x22, {low(&condition), high(&condition), 0, 0x3fff}); + check(AgcDriver::Pm4::UnsupportedReason(valid[0]).empty() && AgcDriver::Pm4::AccessesMemory(valid[0]), "COND_EXEC is rejected or does not synchronize guest memory"); + AgcDriver::Pm4::Validate(valid, 0); + AgcDriver::Pm4::Validate(valid, 0x20); + check(AgcDriver::Pm4::ConditionalWords(valid) == 0x3fff, "COND_EXEC count decoded wrong"); + check(AgcDriver::Pm4::ConditionalWords(makePacket(0x22, {low(&condition), high(&condition), 0, 0})) == 0, "empty COND_EXEC range decoded wrong"); + AgcDriver::Pm4::Validate(makePacket(0x22, {low(&condition), high(&condition), 3u << 25u, 5}), 0x20); + const auto invalidWord = [&](std::size_t word, std::uint32_t value, std::uint32_t queue, const char* text) { + auto packet = valid; + packet[word] = value; + expectFailure([&] { AgcDriver::Pm4::Validate(packet, queue); }, text); + }; + invalidWord(1, low(&condition) | 1u, 0, "reserved address bits"); + invalidWord(1, low(&condition) | 2u, 0x20, "reserved address bits"); + invalidWord(2, 0x10000u, 0, "above 48"); + invalidWord(3, 3u << 25u, 0, "reserved control fields"); + invalidWord(3, 1u, 0x20, "reserved control fields"); + invalidWord(3, 1u << 27u, 0x20, "reserved control fields"); + invalidWord(4, 0x4000u, 0, "reserved count bits"); + invalidWord(4, 0x80000005u, 0x20, "reserved count bits"); + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x22, {0, 0, 0}), 0); }, "packet size"); + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x22, {0, 0, 0, 0, 0}), 0); }, "packet size"); + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x22, {0, 0, 0, 0}, 1), 0); }, "header flags"); + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x22, {0, 0, 0, 0}, 2), 0x20); }, "header flags"); + expectFailure([] { AgcDriver::Pm4::ConditionalWords(makePacket(0x37, {0x100, 0, 0, 0})); }, "expected COND_EXEC"); + AgcDriver::QueueState state; + expectFailure([&] { AgcDriver::Pm4::Execute(valid, state); }, "driver execution"); +} + +void testConditionReadSynchronization() { + alignas(4) static std::uint32_t condition = 0; + static std::size_t flushed = 0; + condition = 0; + flushed = 0; + struct FlushHookReset { + ~FlushHookReset() { AgcDriver::GuestMemory::SetFlushHook(nullptr); } + } reset; + AgcDriver::GuestMemory::SetFlushHook([](std::uint64_t address, std::size_t bytes) { + check(address == reinterpret_cast(&condition) && bytes == sizeof(condition), "COND_EXEC synchronized an unrelated range"); + condition = 0x80; + ++flushed; + }); + const auto packet = makePacket(0x22, {low(&condition), high(&condition), 0, 5}); + check(AgcDriver::Pm4::ReadCondition(packet) == 0x80 && flushed == 1, "COND_EXEC read its condition before the GPU work that writes it"); + AgcDriver::GuestMemory::SetFlushHook([](std::uint64_t, std::size_t) { + throw std::runtime_error("condition synchronization failed"); + }); + expectFailure([&] { AgcDriver::Pm4::ReadCondition(packet); }, "condition synchronization failed"); + AgcDriver::GuestMemory::SetFlushHook(nullptr); + expectFailure([] { AgcDriver::Pm4::ReadCondition(makePacket(0x22, {0x1000, 0, 0, 5})); }, "guest"); +} + void testWriteChangedKeepsUntouchedBytes() { alignas(256) static std::uint8_t guest[256]; std::memset(guest, 0, sizeof(guest)); @@ -541,6 +599,97 @@ void testDriverSubmission() { check(destination[0] == 0, "rejected submission executed a prefix"); } +std::vector joinPackets(std::initializer_list> packets) { + std::vector words; + for (const auto& packet : packets) words.insert(words.end(), packet.begin(), packet.end()); + return words; +} + +std::vector writeWord(std::uint32_t& target, std::uint32_t value) { + return makePacket(0x37, {0x00100200, low(&target), high(&target), value}); +} + +std::vector conditional(const std::uint32_t& condition, std::uint32_t words, std::uint32_t control = 0) { + return makePacket(0x22, {low(&condition), high(&condition), control, words}); +} + +std::vector indirectBuffer(const std::vector& target, bool chain = false) { + return makePacket(0x3f, {low(target.data()), high(target.data()), static_cast(target.size()) | (chain ? 1u << 20u : 0u)}); +} + +void submitWords(std::vector& words, std::uint32_t queue = 0) { + Packet packet{words.data(), static_cast(words.size()), 0, {}}; + check((queue == 0 ? sceAgcDriverSubmitDcb(&packet) : sceAgcDriverSubmitAcb(queue, &packet)) == 0, "conditional submission failed"); +} + +void testConditionalSubmission() { + alignas(8) static std::uint32_t zero = 0, one = 1, condition = 0; + static std::array results{}; + results.fill(0); + const auto run = [](std::vector words, std::uint32_t queue = 0) { + submitWords(words, queue); + AgcDriverWaitIdle_nid_postfix(); + }; + run(joinPackets({conditional(one, 5), writeWord(results[0], 11), writeWord(results[1], 12)})); + check(results[0] == 11 && results[1] == 12, "COND_EXEC skipped a range whose condition is set"); + run(joinPackets({conditional(zero, 6), {0x80000000u}, writeWord(results[0], 21), writeWord(results[1], 22)})); + check(results[0] == 11 && results[1] == 22, "COND_EXEC did not skip exactly its range when the condition is zero"); + run(joinPackets({conditional(zero, 0), writeWord(results[2], 23)})); + check(results[2] == 23, "an empty COND_EXEC range skipped the next packet"); + alignas(4) static std::uint32_t never = 0; + never = 0; + auto skippedWait = joinPackets({conditional(zero, 9), makePacket(0x3c, {0x13, low(&never), high(&never), 1, 0xffffffffu, 0x190}), makePacket(0x10, {0}, 0x30), writeWord(results[14], 24)}); + submitWords(skippedWait); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); + while (std::atomic_ref(results[14]).load() != 24 && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + const bool skipped = std::atomic_ref(results[14]).load() == 24; + std::atomic_ref(never).store(1); + AgcDriverWaitIdle_nid_postfix(); + check(skipped, "a WAIT_REG_MEM inside a skipped COND_EXEC range was executed"); + + condition = 1; + run(joinPackets({writeWord(condition, 0), conditional(condition, 5), writeWord(results[0], 31), writeWord(results[1], 32)})); + check(condition == 0 && results[0] == 11 && results[1] == 32, "COND_EXEC read its condition before an earlier packet of its queue stored it"); + run(joinPackets({writeWord(condition, 9), conditional(condition, 5), writeWord(results[0], 41)})); + check(results[0] == 41, "COND_EXEC skipped a range whose condition an earlier packet of its queue set"); + condition = 0; + run(joinPackets({conditional(condition, 5, 3u << 25u), writeWord(results[3], 42), writeWord(condition, 1), conditional(condition, 5, 1u << 25u), writeWord(results[4], 43)}), 0x20); + check(results[3] == 0 && results[4] == 43, "compute queue COND_EXEC with a cache policy evaluated wrong"); + + run(joinPackets({conditional(one, 10), conditional(zero, 10), writeWord(results[5], 51), writeWord(results[6], 52), writeWord(results[7], 53)})); + check(results[5] == 0 && results[6] == 0 && results[7] == 53, "a nested COND_EXEC range reaching past the outer range was not skipped exactly"); + run(joinPackets({conditional(zero, 10), conditional(one, 10), writeWord(results[5], 54), writeWord(results[6], 55), writeWord(results[7], 56)})); + check(results[5] == 0 && results[6] == 55 && results[7] == 56, "a skipped COND_EXEC range did not skip the COND_EXEC inside it"); + + static std::vector target; + target = joinPackets({writeWord(results[8], 61), conditional(zero, 5), writeWord(results[9], 62), writeWord(results[10], 63)}); + run(joinPackets({conditional(zero, 4), indirectBuffer(target), writeWord(results[11], 64)})); + check(results[8] == 0 && results[10] == 0 && results[11] == 64, "COND_EXEC over an INDIRECT_BUFFER did not skip the whole buffer"); + run(joinPackets({conditional(one, 4), indirectBuffer(target), writeWord(results[11], 65)})); + check(results[8] == 61 && results[9] == 0 && results[10] == 63 && results[11] == 65, "COND_EXEC over an INDIRECT_BUFFER or inside one evaluated wrong"); + + const auto rejected = [](std::vector words, const char* text) { + results[12] = 0; + expectFailure([&] { submitWords(words); }, text); + AgcDriverWaitIdle_nid_postfix(); + check(results[12] == 0, "a rejected conditional submission executed a prefix"); + }; + const auto sentinel = writeWord(results[12], 1); + rejected(joinPackets({sentinel, conditional(one, 3), writeWord(results[13], 1)}), "ends inside a packet"); + rejected(joinPackets({sentinel, conditional(one, 6), writeWord(results[13], 1)}), "exceeds its command buffer"); + rejected(joinPackets({sentinel, conditional(one, 6), {0xc004105cu, 7, 0, 1, 0, 0}}), "a flip inside a conditional execution range"); + rejected(joinPackets({sentinel, conditional(one, 2), makePacket(0x59, {0x80000000u})}), "REWIND inside a conditional execution range"); + rejected(joinPackets({sentinel, conditional(one, 4), indirectBuffer(target, true)}), "chained INDIRECT_BUFFER"); + rejected(joinPackets({sentinel, conditional(one, 2), {0x40000000u, 0}}), "whole packets"); + rejected(joinPackets({sentinel, makePacket(0x22, {low(&one), high(&one), 0, 0x4005}), writeWord(results[13], 1)}), "reserved count bits"); + static std::vector unaligned, overlong; + unaligned = joinPackets({conditional(one, 3), writeWord(results[13], 1)}); + overlong = joinPackets({conditional(one, 10), writeWord(results[13], 1)}); + rejected(joinPackets({sentinel, indirectBuffer(unaligned)}), "ends inside a packet"); + rejected(joinPackets({sentinel, indirectBuffer(overlong), writeWord(results[13], 1)}), "exceeds its command buffer"); + check(results[13] == 0, "a rejected conditional submission executed a guarded packet"); +} + void testAsyncMemoryFailure() { auto commands = makePacket(0x37, {0x100, 0x1000, 0, 1}); Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; @@ -572,11 +721,14 @@ int main(int argc, char** argv) { testCopies(); testMemoryCopyDecode(); testMemorySynchronization(); + testConditionalValidation(); + testConditionReadSynchronization(); testEventWrite(); testAcquireMem(); testDriverSubmission(); + testConditionalSubmission(); LibcRunShutdown_nid_postfix(); - std::puts("PM4 catalog, registers, state, memory and submission tests passed"); + std::puts("PM4 catalog, registers, state, memory, conditional execution and submission tests passed"); return 0; } catch (const std::exception& error) { std::fprintf(stderr, "%s\n", error.what()); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..63840037c 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -85,6 +85,9 @@ Throughout the project, every function at every stage either **does exactly what - The shader recompiler [ignores `s_setreg_b32`](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp): writes to MODE (float rounding and denormal controls) and the other hardware registers have no effect, and `s_getreg_b32` reads of the MODE round fields report round to nearest even even after `s_setreg_b32` wrote another mode. The initial round mode from the shader's program registers is not read either. - [Shader recompilation](../../core/shader/recompiler/Recompiler.cpp) currently occurs right before it was transferred to Vulkan with caching, but should be moved to the [relinker](../../core/relinker/main.cpp) stage. For this purpose, [shader/recompiler](../../core/shader/recompiler) was written completely independently from [libs/prx](../../core/libs/prx). - [v_fma_f64](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp) is fused exactly only for normal inputs: with a subnormal input it is a separate multiply and add, which rounds twice. +- [COND_EXEC](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp) reads its condition after the earlier packets of its queue complete. Without PFP_SYNC_ME the console's graphics PFP reads it ahead of the ME, so a title that races the read sees a later value here. +- A [flip inside a COND_EXEC range](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp) throws: the flip is reserved with the video output when the buffer is submitted, and how the console withdraws a skipped one is unknown. +- A [COND_EXEC range](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) must hold whole packets and end inside its command buffer. A chained INDIRECT_BUFFER or a REWIND inside a range throws, as what the CP does with the skipped dwords then is unknown. - [v_mullit_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follows the hardware measured with the MODE register at IEEE=1 and denormals kept (a signaling NaN src0 is quieted, a positive denormal src2 counts as positive): the recompiler does not read MODE, so other IEEE/denormal settings are not modelled. - The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability. - [libSceJpegEnc](../../core/libs/prx/libSceJpegEnc/Export.cpp) encodes 4:2:2 sampling requests with 4:2:0 chroma subsampling, and grayscale requests as a 3-component JPEG with neutral chroma instead of a single-component one: the [JPEG encoder](../../core/Decoder/Jpeg/src/Jpeg.cpp) (stb) only produces 3-component 4:2:0 and 4:4:4 images. From 61d2fb0a5363ce3a26ba74f2718dc727165a757e Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 11:57:06 +0200 Subject: [PATCH 132/164] feat(agc): accept the WRITE_DATA cache policy WRITE_DATA's control dword carries a two-bit cache policy in bits 26:25 (LRU, stream, no-allocate, bypass). Validate refused it, so a submission with a WRITE_DATA built with a non-zero policy was rejected as a whole. The policy only steers how the GPU L2 keeps the written lines; the stored words are the same, and Execute already writes them to guest memory in queue order. The mask now accepts bits 26:25, as was done for DMA_DATA's cache policy hints. The CE engine and the remaining reserved bits still throw, and the message no longer names the cache. agc_driver_pm4 stores both dwords of a WRITE_DATA with control 0x04100200 and checks that reserved bit 27 is still refused. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp | 3 ++- core/libs/prx/libSceAgcDriver/tests/Pm4.cpp | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index 9dfdfad7b..4b72ec3c5 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -196,6 +196,7 @@ std::string_view UnsupportedReason(std::uint32_t header) { constexpr std::uint32_t DmaSourceCachePolicy = 3u << 13u; constexpr std::uint32_t DmaDestinationCachePolicy = 3u << 25u; +constexpr std::uint32_t WriteDataCachePolicy = 3u << 25u; void Validate(std::span packet, std::uint32_t queue) { require(!packet.empty(), "truncated PM4 header"); @@ -380,7 +381,7 @@ void Validate(std::span packet, std::uint32_t queue) { break; case 0x37: { require(packet.size() >= 5, "WRITE_DATA has no data"); - require((packet[1] & ~0x40110f00u) == 0, "WRITE_DATA engine, cache or reserved fields are not implemented"); + require((packet[1] & ~(0x40110f00u | WriteDataCachePolicy)) == 0, "WRITE_DATA engine or reserved fields are not implemented"); const auto destination = (packet[1] >> 8u) & 0xfu; require(destination == 1 || destination == 2 || (queue != 0 && destination == 5), "WRITE_DATA register or GDS destination is not implemented"); require((packet[2] & 3u) == 0, "misaligned WRITE_DATA destination"); diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index 4c559fc5f..9195aa78c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -290,6 +290,9 @@ void testMemory() { check(data[0] == 22 && data[1] == 12, "WRITE_DATA fixed destination failed"); execute(state, makePacket(0x37, {0x40000100, low(data.data()), high(data.data()), 41, 42})); check(data[0] == 41 && data[1] == 42, "WRITE_DATA from the PFP failed"); + execute(state, makePacket(0x37, {0x04100200, low(data.data()), high(data.data()), 61, 62})); + check(data[0] == 61 && data[1] == 62, "WRITE_DATA with a cache policy failed"); + expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x37, {0x08000100, low(data.data()), high(data.data()), 71}), 0); }, "reserved"); expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x37, {0x80000100, low(data.data()), high(data.data()), 51}), 0); }, "engine"); execute(state, makePacket(0x81, {4, 31, 32})); execute(state, makePacket(0x83, {4, 2, low(data.data()), high(data.data())})); From 040475211107d508eaa3beeed8c4f3c83a3be9b2 Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:31:24 +0200 Subject: [PATCH 133/164] feat(audioout): implement sceAudioOut2Set3DLatency and accept sceAudioOut2MasteringInit(0) PPSA26344 calls sceAudioOut2Set3DLatency(0xFF, 2) right after sceAudioOut2Initialize and fails its audio initialization on a negative return; the export was missing, so the import did not resolve. Its audio subsystem setup also calls sceAudioOut2MasteringInit(0), which threw. sceAudioOut2Set3DLatency accepts a latency of 2 for the system user (0xFF) and throws for any other user or latency, as the unit and range are unknown. sceAudioOut2MasteringInit accepts flags 0 and throws for other flags. There is no 3D rendering or mastering stage, so neither call changes the output; TechnicalDebt lists this under Functional and the assumed signatures under Unknown function info. The new audio_out2_latency test covers the accepted and thrown values. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAudioOut/CMakeLists.txt | 10 ++++ .../libSceAudioOut/src/AudioOut2Mastering.cpp | 3 +- .../libSceAudioOut/src/AudioOut2System.cpp | 9 +++ .../libSceAudioOut/tests/AudioOut2Latency.cpp | 55 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 3 + 5 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp diff --git a/core/libs/prx/libSceAudioOut/CMakeLists.txt b/core/libs/prx/libSceAudioOut/CMakeLists.txt index 024083dad..6251abea9 100644 --- a/core/libs/prx/libSceAudioOut/CMakeLists.txt +++ b/core/libs/prx/libSceAudioOut/CMakeLists.txt @@ -52,4 +52,14 @@ if(BUILD_TESTING) set_tests_properties(audio_out_last_output_time PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(audio_out2_latency_tests tests/AudioOut2Latency.cpp) + target_include_directories(audio_out2_latency_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(audio_out2_latency_tests PRIVATE libSceAudioOut libc) + configure_windows_unwind(audio_out2_latency_tests) + add_test(NAME audio_out2_latency COMMAND audio_out2_latency_tests) + if(WIN32) + set_tests_properties(audio_out2_latency PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp index d8b0455c2..ac3f33976 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp @@ -5,8 +5,7 @@ extern "C" { int APS5_VABI sceAudioOut2MasteringInit(uint32_t flags) { - (void)flags; - NotImplemented_nid_no_patch(__func__); + if (flags != 0) NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp index b690814de..f658ddf10 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp @@ -1,12 +1,21 @@ +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +static constexpr int USER_ID_SYSTEM = 0xFF; +static constexpr std::uint32_t SUPPORTED_3D_LATENCY = 2; + extern "C" { int APS5_VABI sceAudioOut2Initialize(void) { return 0; } +int APS5_VABI sceAudioOut2Set3DLatency(int userId, std::uint32_t latency) { + if (userId != USER_ID_SYSTEM || latency != SUPPORTED_3D_LATENCY) NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceAudioOut2GetSystemState(AudioOut2SystemState* state) { if (!state) return static_cast(0x80260502); state->loudness = 0.0f; diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp new file mode 100644 index 000000000..7f3404f2a --- /dev/null +++ b/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp @@ -0,0 +1,55 @@ +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceAudioOut2Initialize(); +int APS5_VABI sceAudioOut2Set3DLatency(int, std::uint32_t); +int APS5_VABI sceAudioOut2MasteringInit(std::uint32_t); +} + +static void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +template +static bool ThrowsRuntimeError(TFunction function) { + try { + function(); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +namespace { + +constexpr int systemUser = 0xFF; +constexpr int user = 0x10000000; + +void TestSet3DLatency() { + Require(sceAudioOut2Set3DLatency(systemUser, 2) == 0, "latency 2 for the system user must be accepted"); + Require(sceAudioOut2Set3DLatency(systemUser, 2) == 0, "latency 2 must be accepted again"); + Require(ThrowsRuntimeError([] { sceAudioOut2Set3DLatency(systemUser, 1); }), "latency 1 must throw"); + Require(ThrowsRuntimeError([] { sceAudioOut2Set3DLatency(systemUser, 3); }), "latency 3 must throw"); + Require(ThrowsRuntimeError([] { sceAudioOut2Set3DLatency(user, 2); }), "a user other than the system user must throw"); +} + +void TestMasteringInit() { + Require(sceAudioOut2MasteringInit(0) == 0, "flags 0 must be accepted"); + Require(ThrowsRuntimeError([] { sceAudioOut2MasteringInit(1); }), "flags 1 must throw"); +} + +} + +int main() { + Require(sceAudioOut2Initialize() == 0, "initialization must succeed"); + TestSet3DLatency(); + TestMasteringInit(); + return 0; +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..de501d159 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -49,6 +49,8 @@ Throughout the project, every function at every stage either **does exactly what - [sceSslClose](../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - unknown signature - [sceSslGetSerialNumber](../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - unknown signature - [X+4jdIS75P0](../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown name, signature +- [sceAudioOut2Set3DLatency](../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) (libSceAudioOut) - assumed to take `(user_id, latency)`: KytyPS5 reads `(user_id, output, latency_us)`, but the only known call sets just edi and esi; only `(0xFF, 2)` is accepted +- [sceAudioOut2MasteringInit](../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) - flag values unknown; only 0 is accepted - [sceAudioOutSetMixLevelPadSpk](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - effect of a negative mix level unknown; only 0 to 32768 (0 dB) is accepted - [sceAudioOutSetMixLevelPadSpk](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - no source applies the level (shadPS4 only stores it); it is assumed to be a linear gain on the port's own output, and the -9 dB default (11626) is applied to every pad speaker port, including those of titles that never call the function - [sceAudioOutGetLastOutputTime](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - signature and behaviour taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [fpPS4](https://github.com/red-prig/fpPS4) reimplementations of the PS4 library, not confirmed on a PS5 title: the time is the process time at which `sceAudioOutOutput` or `sceAudioOutOutputs` accepted the port's last block, not the time the block reached the device @@ -96,6 +98,7 @@ Throughout the project, every function at every stage either **does exactly what - `--to-intel` guest module trampolines are covered only by a synthetic relinker test; no game title has been verified with them on Linux or Windows. - `--to-intel` replaces the approximate VEX.128 register forms of VRSQRTPS and VRCPPS with [correctly rounded](../../core/relinker/codegen/src/x86/ReciprocalLowering.cpp) 1/sqrt(x) and 1/x, not with the AMD approximation: its tables are not public. Intel's approximation of 1/sqrt(1.0) is 0x3F7FF000, and two Newton-Raphson steps on it settle on 0.99999994 instead of 1.0; a renormalised identity quaternion then turns into a NaN axis in PPSA21564, which the console does not show. Refined results now reach the exact value; unrefined ones can still differ from the console in the low bits. Each site costs an out-of-line stub (two jumps, a spill below the red zone, SQRTPS/DIVPS). The 256-bit, scalar, legacy and memory forms keep the native instruction, as does a 4-byte site with no movable instruction after it for the jump (5 sites in PPSA21564). - [sceKeyboardGetKey2Char](../../core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp) (libSceKeyboard) translates only the 101-key (US) arrangement and throws for the 106-key (Japanese) one; Ctrl and Alt do not change the character. +- [sceAudioOut2Set3DLatency](../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) and [sceAudioOut2MasteringInit](../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) have no effect: there is no 3D rendering or mastering stage. - [libSceAudiodec](../../core/libs/prx/libSceAudiodec/Export.cpp) throws for the 24-bit PCM word size (`iBwPcm` 0): its sample layout is unknown. ATRAC9 decoding is covered only by configuration and error tests, as no ATRAC9 encoder is available for a fixture. - [sceAjmDecMp3ParseFrame](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) throws when the original file length is requested (`parse_ofl` non-zero): reading it from the frame's Xing/Info, VBRI or Fraunhofer data is not implemented. - The [output modifier](../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) (`mul:2`, `mul:4`, `div:2`) follows the hardware with the IEEE mode off and f32 denormals flushed, the LLVM graphics default; the shader's MODE register is not read, and the hardware ignores the modifier in the other modes. On f16 results it is ignored, as on the hardware while f16 denormals are enabled; with them flushed the hardware applies it. From 5085746fce2bd8215347850c9bbd387f2f20c99d Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:00:19 +0200 Subject: [PATCH 134/164] fix(libc): reach the application map area end with the guest arena On Windows the guest arena ended at 0x72_0000_0000: #231 kept its 448 GiB size after #153 had moved its start down to 0x2_0000_0000. Titles map fixed addresses up to the application map area end, 0xFC_0000_0000 (SCE_KERNEL_APP_MAP_AREA_END_ADDR on the PS4), and a direct memory mapping above the old end threw "shared mapping outside the guest arena". The arena is now reserved up to 0xFC_0000_0000. The out-of-arena errors name the address and length, and a fixed mapping over a used arena range says whether it hit a host region. guest_memory maps direct memory at the last two pages below the end, reads it through an alias and checks that a mapping at the end is refused. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/src/GuestArena.cpp | 27 ++++++++++++++++------ core/libs/tests/GuestMemory.cpp | 33 +++++++++++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libc/src/GuestArena.cpp b/core/libs/prx/libc/src/GuestArena.cpp index b53450599..92f4877dc 100644 --- a/core/libs/prx/libc/src/GuestArena.cpp +++ b/core/libs/prx/libc/src/GuestArena.cpp @@ -21,7 +21,7 @@ namespace { constexpr std::uintptr_t ArenaStart = 0x0000000200000000ull; constexpr std::uintptr_t SystemReservedStart = 0x00000007FFFFC000ull; constexpr std::uintptr_t SystemReservedEnd = 0x0000001000000000ull; -constexpr std::size_t ArenaSize = 0x0000007000000000ull; +constexpr std::uintptr_t ApplicationAreaEnd = 0x000000FC00000000ull; std::uintptr_t alignUp(std::uintptr_t value, std::size_t alignment) { return (value + alignment - 1) & ~(static_cast(alignment) - 1); @@ -69,8 +69,11 @@ public: std::lock_guard lock(_lock); const auto start = reinterpret_cast(pointer); const auto next = _used.lower_bound(start); - if ((next != _used.end() && next->first < start + bytes) || (next != _used.begin() && std::prev(next)->second > start)) - throw std::runtime_error("fixed guest mapping overlaps a guest arena range"); + if ((next != _used.end() && next->first < start + bytes) || (next != _used.begin() && std::prev(next)->second > start)) { + char message[160]; + std::snprintf(message, sizeof(message), "fixed guest mapping 0x%llx+0x%zx overlaps %s", static_cast(start), bytes, OverlapsHostRegion(start, bytes) ? "a host region in the guest arena" : "a guest arena range"); + throw std::runtime_error(message); + } _used.emplace(start, start + bytes); } @@ -103,7 +106,7 @@ private: SYSTEM_INFO system{}; GetSystemInfo(&system); const std::uintptr_t granularity = system.dwAllocationGranularity; - const std::uintptr_t end = ArenaStart + ArenaSize; + const std::uintptr_t end = ApplicationAreaEnd; for (std::uintptr_t cursor = ArenaStart; cursor < end;) { MEMORY_BASIC_INFORMATION info{}; if (VirtualQuery(reinterpret_cast(cursor), &info, sizeof(info)) == 0) throw std::system_error(static_cast(GetLastError()), std::system_category(), "query the guest arena range"); @@ -197,18 +200,28 @@ bool GuestArenaHostRegionOverlaps_nid_postfix(std::uintptr_t address, std::size_ return Arena::Get().OverlapsHostRegion(address, bytes); } +namespace { + +std::invalid_argument OutsideArena(const char* operation, const void* pointer, std::size_t bytes) { + char message[128]; + std::snprintf(message, sizeof(message), "%s 0x%llx+0x%zx outside the guest arena", operation, static_cast(reinterpret_cast(pointer)), bytes); + return std::invalid_argument(message); +} + +} + void GuestArenaCommit_nid_postfix(void* pointer, std::size_t bytes, std::uint32_t protection, std::size_t granule) { - if (!Arena::Get().Contains(pointer, bytes)) throw std::invalid_argument("commit outside the guest arena"); + if (!Arena::Get().Contains(pointer, bytes)) throw OutsideArena("commit", pointer, bytes); WindowsMappings::Get().Commit(pointer, bytes, protection, granule, Arena::Get().WriteWatched()); } void GuestArenaReset_nid_postfix(void* pointer, std::size_t bytes) { - if (!Arena::Get().Contains(pointer, bytes)) throw std::invalid_argument("reset outside the guest arena"); + if (!Arena::Get().Contains(pointer, bytes)) throw OutsideArena("reset", pointer, bytes); WindowsMappings::Get().Reset(pointer, bytes); } void GuestArenaMap_nid_postfix(void* pointer, std::size_t bytes, void* section, std::uint64_t offset, std::uint32_t protection) { - if (!Arena::Get().Contains(pointer, bytes)) throw std::invalid_argument("shared mapping outside the guest arena"); + if (!Arena::Get().Contains(pointer, bytes)) throw OutsideArena("shared mapping", pointer, bytes); WindowsMappings::Get().Map(pointer, bytes, section, offset, protection); } diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp index 4b6c3cbcd..f7ba8fa38 100644 --- a/core/libs/tests/GuestMemory.cpp +++ b/core/libs/tests/GuestMemory.cpp @@ -236,6 +236,38 @@ static void CheckNoOverwriteRejectsHostOccupiedMapping() { GuestArena::GuestArenaReset_nid_postfix(target, page); Require(sceKernelReleaseDirectMemory(phys, page) == 0); } + +static void CheckFixedMappingsReachTheApplicationAreaEnd() { + constexpr std::size_t page = 0x4000; + constexpr std::uintptr_t applicationAreaEnd = 0xFC00000000ull; + std::uintptr_t base = 0; + std::size_t size = 0; + GuestArena::GuestArenaRange_nid_postfix(&base, &size); + Require(base == 0x200000000ull && base + size == applicationAreaEnd); + std::int64_t phys = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0); + void* const requested = reinterpret_cast(applicationAreaEnd - page * 2); + void* mapped = requested; + Require(sceKernelMapDirectMemory(&mapped, page * 2, 3, 0x90, phys, 0) == 0); + Require(mapped == requested); + static_cast(mapped)[page * 2 - 1] = 7; + void* alias = nullptr; + Require(sceKernelMapDirectMemory(&alias, page, 3, 0, phys + page, 0) == 0); + Require(static_cast(alias)[page - 1] == 7); + VirtualQueryInfo info{}; + Require(sceKernelVirtualQuery(mapped, 0, &info, sizeof(info)) == 0 && info.is_direct && info.end == applicationAreaEnd); + void* beyond = reinterpret_cast(applicationAreaEnd); + bool refused = false; + try { + refused = sceKernelMapDirectMemory(&beyond, page, 3, 0x10, phys, 0) != 0; + } catch (const std::exception&) { + refused = true; + } + Require(refused && beyond == reinterpret_cast(applicationAreaEnd)); + Require(sceKernelMunmap(alias, page) == 0); + Require(sceKernelMunmap(mapped, page * 2) == 0); + Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); +} #endif #if defined(__linux__) @@ -674,6 +706,7 @@ int main() { CheckHeapAfterMappingReuse(); #ifdef _WIN32 CheckNoOverwriteRejectsHostOccupiedMapping(); + CheckFixedMappingsReachTheApplicationAreaEnd(); #endif CheckSharedWriteTracking(); CheckReadsIntoSharedWriteTracking(); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..8a1728a15 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -42,6 +42,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceKernelInternalMemoryGetModuleSegmentInfo](../../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) - unknown signature - [sceKernelSyncOnAddressWait](../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - the only known caller passes a null timeout and a name string as the fourth argument; the timeout is assumed to point to microseconds like the other kernel waits, and the name is ignored - [sceKernelMapNamedFlexibleMemoryInternal](../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - flag 0x8000 unknown; only the sceKernelMapNamedFlexibleMemory flags are accepted +- [Guest arena](../../core/libs/prx/libc/src/GuestArena.cpp) (libc) - the application map area is assumed to end at 0xFC_0000_0000, as `SCE_KERNEL_APP_MAP_AREA_END_ADDR` does on the PS4. On Windows, host allocations made in the arena before libc loads stay, and a fixed guest mapping over one throws - [sceLibcInternalBacktraceForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature - [sceLibcInternalHeapErrorReportForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature - [__progname](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - unknown data export From d926e7879c570886fc66219e3065e1e3463b244c Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:28:25 +0200 Subject: [PATCH 135/164] feat(http): implement sceHttpParseStatusLine Titles import sceHttpParseStatusLine from libSceHttp to read the status line of a response; without the export they fail at startup. Parse the HTTP/x.y version, the three-digit code and the reason phrase up to the line feed, and return the library's PARSE_HTTP error codes. The seven-argument signature is the one SharpProspero declares; the behaviour follows the shadPS4 reimplementation of the PS4 library, except that no byte past lineLen is read. A consumed count above INT32_MAX cannot be returned and throws. guest_http covers valid lines and each error path. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceHttp/CMakeLists.txt | 2 +- core/libs/prx/libSceHttp/src/HttpErrors.hpp | 2 + core/libs/prx/libSceHttp/src/Parse.cpp | 68 +++++++++++++++++++++ core/libs/tests/GuestHttp.cpp | 48 +++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 5 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 core/libs/prx/libSceHttp/src/Parse.cpp diff --git a/core/libs/prx/libSceHttp/CMakeLists.txt b/core/libs/prx/libSceHttp/CMakeLists.txt index 0093dd1c0..473651532 100644 --- a/core/libs/prx/libSceHttp/CMakeLists.txt +++ b/core/libs/prx/libSceHttp/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceHttp SHARED EXCLUDE_FROM_ALL Export.cpp src/Uri.cpp) +add_library(libSceHttp SHARED EXCLUDE_FROM_ALL Export.cpp src/Parse.cpp src/Uri.cpp) target_include_directories(libSceHttp PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libSceHttp) diff --git a/core/libs/prx/libSceHttp/src/HttpErrors.hpp b/core/libs/prx/libSceHttp/src/HttpErrors.hpp index 66d70c7e4..b95f8d25b 100644 --- a/core/libs/prx/libSceHttp/src/HttpErrors.hpp +++ b/core/libs/prx/libSceHttp/src/HttpErrors.hpp @@ -5,5 +5,7 @@ inline constexpr int ERROR_OUT_OF_MEMORY = static_cast(0x80431022); inline constexpr int ERROR_NETWORK = static_cast(0x80431063); inline constexpr int ERROR_INVALID_VALUE = static_cast(0x804311FE); inline constexpr int ERROR_INVALID_URL = static_cast(0x80433060); +inline constexpr int ERROR_PARSE_HTTP_INVALID_RESPONSE = static_cast(0x80432060); +inline constexpr int ERROR_PARSE_HTTP_INVALID_VALUE = static_cast(0x804321FE); #endif diff --git a/core/libs/prx/libSceHttp/src/Parse.cpp b/core/libs/prx/libSceHttp/src/Parse.cpp new file mode 100644 index 000000000..93b9d109b --- /dev/null +++ b/core/libs/prx/libSceHttp/src/Parse.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include "prx/libc/include/General.hpp" +#include "prx/libSceHttp/src/HttpErrors.hpp" + +namespace { + +bool isAsciiDigit(char c) { + return c >= '0' && c <= '9'; +} + +bool parseVersionNumber(const char* line, size_t lineLen, size_t& index, int32_t* value) { + if (index >= lineLen || !isAsciiDigit(line[index])) return false; + uint32_t number = 0; + while (index < lineLen && isAsciiDigit(line[index])) { + number = number * 10 + static_cast(line[index] - '0'); + ++index; + } + *value = static_cast(number); + return true; +} + +} + +extern "C" { + +int APS5_VABI sceHttpParseStatusLine(const char* statusLine, size_t lineLen, int32_t* httpMajorVer, int32_t* httpMinorVer, + int32_t* responseCode, const char** reasonPhrase, size_t* phraseLen) { + if (!statusLine) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (!httpMajorVer || !httpMinorVer || !responseCode || !reasonPhrase || !phraseLen) return ERROR_PARSE_HTTP_INVALID_VALUE; + + *httpMajorVer = 0; + *httpMinorVer = 0; + if (lineLen < 8 || std::memcmp(statusLine, "HTTP/", 5) != 0) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + + size_t index = 5; + if (!parseVersionNumber(statusLine, lineLen, index, httpMajorVer)) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (index >= lineLen || statusLine[index] != '.') return ERROR_PARSE_HTTP_INVALID_RESPONSE; + ++index; + if (!parseVersionNumber(statusLine, lineLen, index, httpMinorVer)) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (index >= lineLen || statusLine[index] != ' ') return ERROR_PARSE_HTTP_INVALID_RESPONSE; + ++index; + + if (lineLen - index < 3) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (!isAsciiDigit(statusLine[index]) || !isAsciiDigit(statusLine[index + 1]) || !isAsciiDigit(statusLine[index + 2])) { + return ERROR_PARSE_HTTP_INVALID_RESPONSE; + } + *responseCode = (statusLine[index] - '0') * 100 + (statusLine[index + 1] - '0') * 10 + (statusLine[index + 2] - '0'); + index += 3; + + const char* phrase = statusLine + index; + const void* lineFeed = std::memchr(phrase, '\n', lineLen - index); + if (!lineFeed) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + + size_t length = static_cast(static_cast(lineFeed) - phrase); + const size_t consumed = index + length + 1; + if (consumed > static_cast(INT32_MAX)) { + NotImplemented_nid_no_patch("sceHttpParseStatusLine: status line longer than INT32_MAX"); + } + if (length > 0 && phrase[length - 1] == '\r') --length; + + *reasonPhrase = phrase; + *phraseLen = length; + return static_cast(consumed); +} + +} diff --git a/core/libs/tests/GuestHttp.cpp b/core/libs/tests/GuestHttp.cpp index fa1ae4a63..951d90532 100644 --- a/core/libs/tests/GuestHttp.cpp +++ b/core/libs/tests/GuestHttp.cpp @@ -16,6 +16,7 @@ int APS5_VABI sceHttpCreateRequest2(int, const char*, const char*, std::uint64_t int APS5_VABI sceHttpsEnableOption(int, std::uint32_t); int APS5_VABI sceHttpsLoadCert(int, int, void*, void*, void*); int APS5_VABI sceHttpGetLastErrno(int, int*); +int APS5_VABI sceHttpParseStatusLine(const char*, std::size_t, std::int32_t*, std::int32_t*, std::int32_t*, const char**, std::size_t*); } static void Require(bool value) { if (!value) std::abort(); } @@ -103,4 +104,51 @@ int main() { Require(sceHttpGetLastErrno(1, &httpErrno) == 0); Require(httpErrno == 0); Require(sceHttpGetLastErrno(1, nullptr) == invalidValue); + + constexpr int parseInvalidResponse = static_cast(0x80432060); + constexpr int parseInvalidValue = static_cast(0x804321FE); + std::int32_t major = -1; + std::int32_t minor = -1; + std::int32_t code = -1; + const char* phrase = nullptr; + std::size_t phraseLength = 0; + auto parse = [&](const char* line, std::size_t length) { + return sceHttpParseStatusLine(line, length, &major, &minor, &code, &phrase, &phraseLength); + }; + + const char* response = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"; + Require(parse(response, std::strlen(response)) == 17); + Require(major == 1 && minor == 1 && code == 200); + Require(phrase == response + 12 && phraseLength == 3 && std::strncmp(phrase, " OK", 3) == 0); + + const char* lineFeedOnly = "HTTP/10.25 404 Not Found\n"; + Require(parse(lineFeedOnly, std::strlen(lineFeedOnly)) == 25); + Require(major == 10 && minor == 25 && code == 404); + Require(phrase == lineFeedOnly + 14 && phraseLength == 10); + + const char* noPhrase = "HTTP/2.0 204\r\n"; + Require(parse(noPhrase, std::strlen(noPhrase)) == 14); + Require(major == 2 && minor == 0 && code == 204); + Require(phrase == noPhrase + 12 && phraseLength == 0); + + phrase = nullptr; + phraseLength = 0; + Require(parse(nullptr, 17) == parseInvalidResponse); + Require(sceHttpParseStatusLine(response, 17, nullptr, &minor, &code, &phrase, &phraseLength) == parseInvalidValue); + Require(sceHttpParseStatusLine(response, 17, &major, &minor, &code, &phrase, nullptr) == parseInvalidValue); + major = -1; + minor = -1; + Require(parse("HTTX/1.1 200 OK\n", 16) == parseInvalidResponse); + Require(major == 0 && minor == 0); + Require(parse("HTTP/1.1", 7) == parseInvalidResponse); + Require(parse("HTTP/123", 8) == parseInvalidResponse); + Require(parse("HTTP/x.1 200 OK\n", 16) == parseInvalidResponse); + Require(parse("HTTP/1 200 OK\n", 14) == parseInvalidResponse); + Require(parse("HTTP/1. 200 OK\n", 15) == parseInvalidResponse); + Require(parse("HTTP/1.1/200 OK\n", 16) == parseInvalidResponse); + Require(parse("HTTP/1.1 2x0 OK\n", 16) == parseInvalidResponse); + Require(parse("HTTP/1.1 20", 11) == parseInvalidResponse); + Require(parse(response, 15) == parseInvalidResponse); + Require(parse(response, 16) == parseInvalidResponse); + Require(phrase == nullptr && phraseLength == 0); } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..1a8f64327 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -65,6 +65,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceAjmBatchJobGetCodecInfo, sceAjmBatchJobGetGaplessDecode](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the `RUN_GET_CODEC_INFO` flag (0x800), the ATRAC9 codec info layout and its next frame size are taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [RPCSX](https://github.com/RPCSX/rpcsx) reimplementations, not confirmed on a PS5 title; a run job without buffers returns result 0, as in shadPS4. The gapless sideband reports the total and skip counts the title set and does not count them down: RPCSX does the same, shadPS4 counts them down and BryKytyPS5 returns its current counts - [AJM Opus decoder](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native, codec 24) - the initialize parameter layout (`u32 channels, u32 sample rate, u32`, seen as `2, 48000, 0`) and the little-endian `u16` byte count before each packet come from one title (The Smurfs Dreams); only 48000 Hz and a zero third word are accepted - [sceHttpParseResponseHeader](../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature +- [sceHttpParseStatusLine](../../core/libs/prx/libSceHttp/src/Parse.cpp) (libSceHttp) - behaviour and error codes follow the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation of the PS4 library, not confirmed on a PS5 title; unlike it, no byte past `lineLen` is read - [sceHttp2GetMemoryPoolStats](../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - unknown signature - [sceNetGetMemoryPoolStats](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature - [sce::Np::CppWebApi::Common::IntrusivePtr](../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x18-byte layout (object, deleter, `LibContext*`) and the offset of the 32-bit reference count in the pointee (0x00, 0x30 for `Vector`) come from the module bundled with one title (PPSA21564); other SDK versions are not checked. The destructor throws on the last reference of a pointer without a deleter: destroying the object needs its destructor and the `LibContext` allocator, which are not implemented From 28b2a08fa71aa8800cecd78160eeea0d7c3c6b9a Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:27:49 +0200 Subject: [PATCH 136/164] feat(libkernel): implement sceKernelSyncOnAddressWait32 and sceKernelSyncOnAddressWait64 libc's __std_atomic_wait_direct_32 and __std_atomic_wait_direct_64 import these functions, so a libc.prx that contains the C++20 atomic wait wrappers does not load without them. Wait32 compares a 4-byte value and Wait64 the full 8-byte value. Both share the waiter queue of sceKernelSyncOnAddressWait, so sceKernelSyncOnAddressWake releases waiters of every size on the address. A null address, or one not aligned to the value size, throws. The by-value comparand, the return values and the 8-byte alignment of Wait64 are assumed and recorded in TechnicalDebt. Co-Authored-By: Claude Opus 5.5 --- .../SyncOnAddress/src/SyncOnAddress.cpp | 51 ++++++++---- core/libs/tests/GuestSyncOnAddress.cpp | 78 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 3 files changed, 115 insertions(+), 15 deletions(-) diff --git a/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp b/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp index 87944ae04..733ae7c41 100644 --- a/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp +++ b/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp @@ -25,23 +25,16 @@ struct AddressWaiter { std::mutex g_waitersLock; std::unordered_map> g_waiters; -bool IsWordAddress(std::uintptr_t address) { - return address != 0 && address % alignof(std::uint32_t) == 0; +template +bool IsAlignedAddress(std::uintptr_t address) { + return address != 0 && address % alignof(TValue) == 0; } -} // namespace - -extern "C" { - -int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name) { - (void)name; +template +int WaitOnAddress(TValue* address, TValue expected, const KernelUseconds* timeout, const void* caller) { const auto key = reinterpret_cast(address); - if (!IsWordAddress(key)) { - APS5_INVALID_ARG_EX; - } - std::unique_lock lock(g_waitersLock); - if (std::atomic_ref(*address).load() != expected) { + if (std::atomic_ref(*address).load() != expected) { return SYNC_ON_ADDRESS_OK; } @@ -65,13 +58,41 @@ int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t e } } lock.unlock(); - KernelTraceWait_nid_postfix("addr", __builtin_return_address(0), static_cast(waited.count()), !woken); + KernelTraceWait_nid_postfix("addr", caller, static_cast(waited.count()), !woken); return woken ? SYNC_ON_ADDRESS_OK : SCE_KERNEL_ERROR_ETIMEDOUT; } +} // namespace + +extern "C" { + +int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name) { + (void)name; + if (!IsAlignedAddress(reinterpret_cast(address))) { + APS5_INVALID_ARG_EX; + } + return WaitOnAddress(address, expected, timeout, __builtin_return_address(0)); +} + +int APS5_VABI sceKernelSyncOnAddressWait32(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name) { + (void)name; + if (!IsAlignedAddress(reinterpret_cast(address))) { + APS5_INVALID_ARG_EX; + } + return WaitOnAddress(address, expected, timeout, __builtin_return_address(0)); +} + +int APS5_VABI sceKernelSyncOnAddressWait64(std::uint64_t* address, std::uint64_t expected, const KernelUseconds* timeout, const char* name) { + (void)name; + if (!IsAlignedAddress(reinterpret_cast(address))) { + APS5_INVALID_ARG_EX; + } + return WaitOnAddress(address, expected, timeout, __builtin_return_address(0)); +} + int APS5_VABI sceKernelSyncOnAddressWake(void* address, std::int32_t count) { const auto key = reinterpret_cast(address); - if (!IsWordAddress(key) || count < 0) { + if (!IsAlignedAddress(key) || count < 0) { APS5_INVALID_ARG_EX; } diff --git a/core/libs/tests/GuestSyncOnAddress.cpp b/core/libs/tests/GuestSyncOnAddress.cpp index 363c35d9a..e5954051f 100644 --- a/core/libs/tests/GuestSyncOnAddress.cpp +++ b/core/libs/tests/GuestSyncOnAddress.cpp @@ -10,6 +10,8 @@ extern "C" { int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name); +int APS5_VABI sceKernelSyncOnAddressWait32(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name); +int APS5_VABI sceKernelSyncOnAddressWait64(std::uint64_t* address, std::uint64_t expected, const KernelUseconds* timeout, const char* name); int APS5_VABI sceKernelSyncOnAddressWake(void* address, std::int32_t count); } @@ -130,14 +132,87 @@ static void WaitsOnTheLowHalfOfAnOnceState() { waiter.join(); } +static void SizedWaitsTimeOutWhileTheValueMatches() { + std::uint32_t word = 5; + std::uint64_t value = 0x500000005ULL; + const KernelUseconds none = 0; + Require(sceKernelSyncOnAddressWait32(&word, 4, &none, "differs") == SCE_OK); + Require(sceKernelSyncOnAddressWait32(&word, 5, &none, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(sceKernelSyncOnAddressWait64(&value, 0x500000005ULL, &none, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT); + + const KernelUseconds timeout = 20000; + auto start = std::chrono::steady_clock::now(); + Require(sceKernelSyncOnAddressWait32(&word, 5, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15)); + start = std::chrono::steady_clock::now(); + Require(sceKernelSyncOnAddressWait64(&value, 0x500000005ULL, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15)); +} + +static void Wait64ComparesTheHighHalf() { + std::uint64_t value = 0x100000001ULL; + const KernelUseconds none = 0; + Require(sceKernelSyncOnAddressWait64(&value, 0x1ULL, &none, "high") == SCE_OK); + Require(sceKernelSyncOnAddressWait64(&value, 0x200000001ULL, &none, "high") == SCE_OK); + Require(sceKernelSyncOnAddressWait64(&value, 0x100000001ULL, &none, "high") == SCE_KERNEL_ERROR_ETIMEDOUT); +} + +static void WakeReleasesWaitersOfEverySize() { + std::uint64_t value = 1; + auto* word = reinterpret_cast(&value); + std::atomic started{0}; + std::atomic finished{0}; + std::vector results(3, -1); + + std::vector waiters; + waiters.emplace_back([&] { + ++started; + results[0] = sceKernelSyncOnAddressWait(word, 1, &FAILSAFE_TIMEOUT, "sized"); + ++finished; + }); + waiters.emplace_back([&] { + ++started; + results[1] = sceKernelSyncOnAddressWait32(word, 1, &FAILSAFE_TIMEOUT, "sized"); + ++finished; + }); + waiters.emplace_back([&] { + ++started; + results[2] = sceKernelSyncOnAddressWait64(&value, 1, &FAILSAFE_TIMEOUT, "sized"); + ++finished; + }); + AwaitCount(started, 3); + std::this_thread::sleep_for(SETTLE); + + Require(sceKernelSyncOnAddressWake(&value, 1) == SCE_OK); + AwaitCount(finished, 1); + std::this_thread::sleep_for(SETTLE); + Require(finished.load() == 1); + + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (finished.load() != 3) { + Require(std::chrono::steady_clock::now() < deadline); + Require(sceKernelSyncOnAddressWake(&value, INT_MAX) == SCE_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + for (auto& waiter : waiters) waiter.join(); + for (const int result : results) Require(result == SCE_OK); +} + static void RejectsInvalidArguments() { std::uint32_t words[2] = {1, 1}; auto* misaligned = reinterpret_cast(reinterpret_cast(words) + 1); Require(Rejects([&] { sceKernelSyncOnAddressWait(nullptr, 1, nullptr, nullptr); })); Require(Rejects([&] { sceKernelSyncOnAddressWait(misaligned, 1, nullptr, nullptr); })); + Require(Rejects([&] { sceKernelSyncOnAddressWait32(nullptr, 1, nullptr, nullptr); })); + Require(Rejects([&] { sceKernelSyncOnAddressWait32(misaligned, 1, nullptr, nullptr); })); Require(Rejects([&] { sceKernelSyncOnAddressWake(nullptr, 1); })); Require(Rejects([&] { sceKernelSyncOnAddressWake(misaligned, 1); })); Require(Rejects([&] { sceKernelSyncOnAddressWake(words, -1); })); + + std::uint64_t values[2] = {1, 1}; + auto* wordAligned = reinterpret_cast(reinterpret_cast(values) + sizeof(std::uint32_t)); + Require(Rejects([&] { sceKernelSyncOnAddressWait64(nullptr, 1, nullptr, nullptr); })); + Require(Rejects([&] { sceKernelSyncOnAddressWait64(wordAligned, 1, nullptr, nullptr); })); } int main() { @@ -146,5 +221,8 @@ int main() { WakeReleasesAWaiterWithoutAValueChange(); WakeHonoursTheCountAndTheAddress(); WaitsOnTheLowHalfOfAnOnceState(); + SizedWaitsTimeOutWhileTheValueMatches(); + Wait64ComparesTheHighHalf(); + WakeReleasesWaitersOfEverySize(); RejectsInvalidArguments(); } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..eb1c2b629 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -41,6 +41,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceAgcDcbPrimeUtcl2](../../core/libs/prx/libSceAgc/DcbState/src/Display.cpp) (libSceAgc) - assumed to write the packet `sceAgcAcbPrimeUtcl2` writes: PRIME_UTCL2 has the same 5 dwords on the graphics and compute rings. Not confirmed on a title. Both write a NOP of that size, as there is no TLB to prime - [sceKernelInternalMemoryGetModuleSegmentInfo](../../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) - unknown signature - [sceKernelSyncOnAddressWait](../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - the only known caller passes a null timeout and a name string as the fourth argument; the timeout is assumed to point to microseconds like the other kernel waits, and the name is ignored +- [sceKernelSyncOnAddressWait32 / sceKernelSyncOnAddressWait64](../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - libc's `__std_atomic_wait_direct_32/64` pass a microsecond timeout or null and ignore the result; the by-value comparand, return values and Wait64's 8-byte alignment are assumed - [sceKernelMapNamedFlexibleMemoryInternal](../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - flag 0x8000 unknown; only the sceKernelMapNamedFlexibleMemory flags are accepted - [sceLibcInternalBacktraceForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature - [sceLibcInternalHeapErrorReportForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature From 2dea7164e386fc5b9a145fa898cc843695fb82d5 Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:34:43 +0200 Subject: [PATCH 137/164] feat(libcinternal): run thread_local destructors at thread exit A title's own libc.prx implements __cxa_thread_atexit and the destructor pass of exit() through _sceLibcInternalThreadAtexit and _sceLibcInternalThreadDtors. Its __cxa_finalize calls _sceLibcInternalForceTlsDestructor. Destructors are kept per thread and run newest first, including those registered while they run, as FreeBSD's __cxa_thread_call_dtors does. A null destructor or one outside every loaded image throws instead of being skipped. The first registration installs the pass as the libkernel thread exit hook, so it runs on every guest thread before the thread can be joined. _sceLibcInternalForceTlsDestructor throws. Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceLibcInternal/CMakeLists.txt | 23 +++++ core/libs/prx/libSceLibcInternal/Export.cpp | 80 +++++++++++++++++ core/libs/tests/GuestThreadAtexit.cpp | 87 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 3 + 4 files changed, 193 insertions(+) create mode 100644 core/libs/prx/libSceLibcInternal/CMakeLists.txt create mode 100644 core/libs/tests/GuestThreadAtexit.cpp diff --git a/core/libs/prx/libSceLibcInternal/CMakeLists.txt b/core/libs/prx/libSceLibcInternal/CMakeLists.txt new file mode 100644 index 000000000..3aae2c755 --- /dev/null +++ b/core/libs/prx/libSceLibcInternal/CMakeLists.txt @@ -0,0 +1,23 @@ +add_library(libSceLibcInternal SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceLibcInternal PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(libSceLibcInternal PRIVATE ${CMAKE_DL_LIBS}) +configure_windows_unwind(libSceLibcInternal) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_thread_atexit_tests ${LIBS_INCLUDE_DIR}/tests/GuestThreadAtexit.cpp) + target_include_directories(guest_thread_atexit_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_thread_atexit_tests PRIVATE libSceLibcInternal libkernel libc) + configure_windows_unwind(guest_thread_atexit_tests) + add_test(NAME guest_thread_atexit COMMAND guest_thread_atexit_tests) + set_tests_properties(guest_thread_atexit PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_thread_atexit PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceLibcInternal/Export.cpp b/core/libs/prx/libSceLibcInternal/Export.cpp index 49699b6ca..c84ee09a1 100644 --- a/core/libs/prx/libSceLibcInternal/Export.cpp +++ b/core/libs/prx/libSceLibcInternal/Export.cpp @@ -1,5 +1,69 @@ +#include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/HeapDiagnostics.hpp" +#include "prx/libc/include/HostThreadLocal.hpp" +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +extern "C" void APS5_VABI sceKernelSetThreadDtors(thread_dtors_func_t dtors); + +namespace { + +using ThreadDestructorFunction = void (APS5_VABI*)(void*); + +struct ThreadDestructor { + ThreadDestructorFunction function; + void* object; + void* dsoSymbol; +}; + +struct ThreadDestructorsTag {}; + +std::vector& ThreadDestructors() { + return HostThreadLocal, ThreadDestructorsTag>(); +} + +bool IsInLoadedImage(const void* address) { + if (address == nullptr) + return false; +#ifdef _WIN32 + HMODULE module = nullptr; + return GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, static_cast(address), &module) != 0; +#else + Dl_info info{}; + return dladdr(address, &info) != 0; +#endif +} + +void APS5_VABI RunThreadDestructors_nid_no_patch() { + auto& destructors = ThreadDestructors(); + while (!destructors.empty()) { + const ThreadDestructor destructor = destructors.back(); + destructors.pop_back(); + const auto* function = reinterpret_cast(destructor.function); + if (!IsInLoadedImage(function)) { + std::ostringstream message; + message << "thread_local destructor " << function << " of dso " << destructor.dsoSymbol << " is not in a loaded image"; + throw std::runtime_error(message.str()); + } + destructor.function(destructor.object); + } +} + +void RegisterThreadExitHook() { + [[maybe_unused]] static const bool registered = [] { + sceKernelSetThreadDtors(RunThreadDestructors_nid_no_patch); + return true; + }(); +} + +} extern "C" { @@ -13,4 +77,20 @@ void APS5_VABI sceLibcHeapGetTraceInfo_nid_postfix(Info* info) { LibcHeapTraceInfo_nid_no_patch(info); } +int APS5_VABI _sceLibcInternalThreadAtexit_nid_postfix(ThreadDestructorFunction destructor, void* object, void* dsoSymbol) { + RegisterThreadExitHook(); + ThreadDestructors().push_back({destructor, object, dsoSymbol}); + return 0; +} + +void APS5_VABI _sceLibcInternalThreadDtors_nid_postfix() { + RunThreadDestructors_nid_no_patch(); +} + +int APS5_VABI _sceLibcInternalForceTlsDestructor_nid_postfix(KernelModule handle) { + (void)handle; + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/core/libs/tests/GuestThreadAtexit.cpp b/core/libs/tests/GuestThreadAtexit.cpp new file mode 100644 index 000000000..f37fd5d52 --- /dev/null +++ b/core/libs/tests/GuestThreadAtexit.cpp @@ -0,0 +1,87 @@ +#include "SceTypes.hpp" +#include +#include +#include +#include + +extern "C" { +int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); +int APS5_VABI scePthreadJoin(Pthread thread, void** retval); +Pthread APS5_VABI scePthreadSelf(); +int APS5_VABI _sceLibcInternalThreadAtexit_nid_postfix(void (APS5_VABI* destructor)(void*), void* object, void* dsoSymbol); +void APS5_VABI _sceLibcInternalThreadDtors_nid_postfix(); +} + +namespace { + +using Destructor = void (APS5_VABI*)(void*); + +void Require(bool value) { if (!value) std::abort(); } + +int dsoHandle = 0; +std::vector calls; +std::vector callers; + +void* Object(std::intptr_t value) { return reinterpret_cast(value); } + +int Register(Destructor destructor, std::intptr_t object) { + return _sceLibcInternalThreadAtexit_nid_postfix(destructor, Object(object), &dsoHandle); +} + +void APS5_VABI Record(void* object) { + calls.push_back(reinterpret_cast(object)); + callers.push_back(scePthreadSelf()); +} + +void APS5_VABI RecordAndRegister(void* object) { + Record(object); + Require(Register(Record, 9) == 0); +} + +void* APS5_VABI Worker(void*) { + Require(Register(Record, 4) == 0); + Require(Register(Record, 5) == 0); + return nullptr; +} + +bool DestructorsThrow() { + try { + _sceLibcInternalThreadDtors_nid_postfix(); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +} + +int main() { + const Pthread mainThread = scePthreadSelf(); + Require(Register(Record, 1) == 0); + Require(Register(RecordAndRegister, 2) == 0); + Require(Register(Record, 3) == 0); + _sceLibcInternalThreadDtors_nid_postfix(); + Require(calls == std::vector{3, 2, 9, 1}); + Require(callers == std::vector(4, mainThread)); + _sceLibcInternalThreadDtors_nid_postfix(); + Require(calls.size() == 4); + + calls.clear(); + callers.clear(); + Pthread worker = nullptr; + Require(scePthreadCreate(&worker, nullptr, Worker, nullptr, "ThreadAtexit") == 0); + Require(scePthreadJoin(worker, nullptr) == 0); + Require(calls == std::vector{5, 4}); + Require(callers == std::vector(2, worker)); + + calls.clear(); + std::vector heap(64); + Require(Register(Record, 6) == 0); + Require(Register(nullptr, 7) == 0); + Require(Register(reinterpret_cast(heap.data()), 8) == 0); + Require(DestructorsThrow()); + Require(DestructorsThrow()); + Require(calls.empty()); + _sceLibcInternalThreadDtors_nid_postfix(); + Require(calls == std::vector{6}); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..a490dce87 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -44,6 +44,8 @@ Throughout the project, every function at every stage either **does exactly what - [sceKernelMapNamedFlexibleMemoryInternal](../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - flag 0x8000 unknown; only the sceKernelMapNamedFlexibleMemory flags are accepted - [sceLibcInternalBacktraceForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature - [sceLibcInternalHeapErrorReportForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature +- [_sceLibcInternalThreadAtexit, _sceLibcInternalThreadDtors](../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - FreeBSD `__cxa_thread_atexit` semantics assumed; a null or non-image destructor and an allocation failure throw +- [_sceLibcInternalForceTlsDestructor](../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - unknown return type and behaviour; a title's `libc.prx` calls it from `__cxa_finalize` with the module handle of a non-null dso - [__progname](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - unknown data export - [pthread_barrierattr_setpshared](../../core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp) (libkernel) - PTHREAD_PROCESS_SHARED throws: FreeBSD 9.0 libthr rejects it with EINVAL and 11.0 accepts it, and which one the console follows is unknown - [sceSslClose](../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - unknown signature @@ -101,3 +103,4 @@ Throughout the project, every function at every stage either **does exactly what - The [output modifier](../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) (`mul:2`, `mul:4`, `div:2`) follows the hardware with the IEEE mode off and f32 denormals flushed, the LLVM graphics default; the shader's MODE register is not read, and the hardware ignores the modifier in the other modes. On f16 results it is ignored, as on the hardware while f16 denormals are enabled; with them flushed the hardware applies it. - [libScePlayerInvitationDialog](../../core/libs/prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.cpp) simulates dialog completion without displaying UI or sending invitations; its parameter ABI remains unverified. - [64-bit LDS atomics](../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) (`ds_*_u64`, `ds_*_b64`, `ds_*_f64`) read and write the two dwords under a workgroup lock stored after the guest LDS, as LDS is a 32-bit array without 64-bit atomics. They are atomic with respect to each other only: 32-bit accesses to the same dwords issued concurrently are not ordered by the lock. 64-bit GDS atomics throw. `ds_min/max(_rtn)_f64` and `ds_cmpst(_rtn)_f64` follow the NaN and signed-zero rules measured for the f32 forms; the f64 forms were not measured on hardware. +- [Thread exit destructors](../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) run when a libkernel thread finishes; on the main or a host thread only `_sceLibcInternalThreadDtors` runs them, as `exit` does, else they are dropped From 09218ee68a1db92e4dd8e1cd94191a3dae30729e Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:24:55 +0200 Subject: [PATCH 138/164] feat(msgdialog): implement sceMsgDialogClose in libSceMsgDialog.native A title that loads libSceMsgDialog.native without libSceMsgDialog imports sceMsgDialogClose from it, so without the export it stops at startup on an unresolved import. Close turns a running dialog into a finished one and returns NOT_RUNNING in every other state, as libSceMsgDialog's Close does. The module keeps its own dialog status, so it cannot reuse that export. Open in this module finishes the dialog at once, so Close after Open returns NOT_RUNNING and leaves the result unchanged. The module is now listed under silent stubs in TechnicalDebt, and a CMakeLists.txt registers the guest_msg_dialog_native test. Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceMsgDialog.native/CMakeLists.txt | 20 ++++++++ .../prx/libSceMsgDialog.native/Export.cpp | 8 +++ core/libs/tests/GuestMsgDialogNative.cpp | 49 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 78 insertions(+) create mode 100644 core/libs/prx/libSceMsgDialog.native/CMakeLists.txt create mode 100644 core/libs/tests/GuestMsgDialogNative.cpp diff --git a/core/libs/prx/libSceMsgDialog.native/CMakeLists.txt b/core/libs/prx/libSceMsgDialog.native/CMakeLists.txt new file mode 100644 index 000000000..111746d44 --- /dev/null +++ b/core/libs/prx/libSceMsgDialog.native/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(libSceMsgDialog.native SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceMsgDialog.native PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceMsgDialog.native) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_msg_dialog_native_tests ${LIBS_INCLUDE_DIR}/tests/GuestMsgDialogNative.cpp) + target_include_directories(guest_msg_dialog_native_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_msg_dialog_native_tests PRIVATE libSceMsgDialog.native) + add_test(NAME guest_msg_dialog_native COMMAND guest_msg_dialog_native_tests) + if(WIN32) + set_tests_properties(guest_msg_dialog_native PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceMsgDialog.native/Export.cpp b/core/libs/prx/libSceMsgDialog.native/Export.cpp index 1423e8fed..027eff38d 100644 --- a/core/libs/prx/libSceMsgDialog.native/Export.cpp +++ b/core/libs/prx/libSceMsgDialog.native/Export.cpp @@ -8,10 +8,12 @@ namespace { std::atomic g_status{0}; +constexpr int COMMON_DIALOG_STATUS_RUNNING = 2; constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; constexpr int COMMON_DIALOG_RESULT_OK = 0; constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); +constexpr int COMMON_DIALOG_ERROR_NOT_RUNNING = static_cast(0x80B8000Bu); constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); constexpr int BUTTON_ID_OK = 1; } @@ -20,6 +22,12 @@ extern "C" { int SceMsgDialogNativeModuleLoaded_nid_no_patch = 1; +int APS5_VABI sceMsgDialogClose(void) { + int expected = COMMON_DIALOG_STATUS_RUNNING; + if (!g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_FINISHED)) return COMMON_DIALOG_ERROR_NOT_RUNNING; + return 0; +} + int APS5_VABI sceMsgDialogGetResult(MsgDialogResult* result) { const int status = g_status.load(); if (status == 0) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; diff --git a/core/libs/tests/GuestMsgDialogNative.cpp b/core/libs/tests/GuestMsgDialogNative.cpp new file mode 100644 index 000000000..6e3b757e2 --- /dev/null +++ b/core/libs/tests/GuestMsgDialogNative.cpp @@ -0,0 +1,49 @@ +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +extern "C" { +int APS5_VABI sceMsgDialogInitialize(void); +int APS5_VABI sceMsgDialogOpen(const void* param); +int APS5_VABI sceMsgDialogUpdateStatus(void); +int APS5_VABI sceMsgDialogGetResult(MsgDialogResult* result); +int APS5_VABI sceMsgDialogClose(void); +int APS5_VABI sceMsgDialogTerminate(void); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr int kStatusInitialized = 1; +constexpr int kStatusFinished = 3; +constexpr int kErrNotRunning = static_cast(0x80B8000Bu); +constexpr int kButtonIdOk = 1; + +} + +int main() { + std::uint8_t param[0x60]{}; + + Require(sceMsgDialogClose() == kErrNotRunning); + Require(sceMsgDialogUpdateStatus() == 0); + + Require(sceMsgDialogInitialize() == 0); + Require(sceMsgDialogClose() == kErrNotRunning); + Require(sceMsgDialogUpdateStatus() == kStatusInitialized); + + Require(sceMsgDialogOpen(param) == 0); + Require(sceMsgDialogUpdateStatus() == kStatusFinished); + Require(sceMsgDialogClose() == kErrNotRunning); + Require(sceMsgDialogUpdateStatus() == kStatusFinished); + + MsgDialogResult result{}; + result.button_id = -1; + Require(sceMsgDialogGetResult(&result) == 0); + Require(result.result == 0); + Require(result.button_id == kButtonIdOk); + + Require(sceMsgDialogTerminate() == 0); + Require(sceMsgDialogClose() == kErrNotRunning); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..7f30b5d62 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -12,6 +12,7 @@ Throughout the project, every function at every stage either **does exactly what it's supposed to or throws an exception**. Everywhere... except: - [libSceSaveDataDialog.native](../../core/libs/prx/libSceSaveDataDialog.native/Export.cpp) +- [libSceMsgDialog.native](../../core/libs/prx/libSceMsgDialog.native/Export.cpp) - dialogs finish at `sceMsgDialogOpen` without UI and answer with button 1, so `sceMsgDialogClose` returns `NOT_RUNNING`; its result before `sceMsgDialogInitialize` follows libSceMsgDialog - [libSceCommonDialog](../../core/libs/prx/libSceCommonDialog/Export.cpp) - [libSceErrorDialog](../../core/libs/prx/libSceErrorDialog/Export.cpp) - the error dialog runs the state machine but shows nothing; the error code passed to `sceErrorDialogOpen` is only logged - [libSceHmd](../../core/libs/prx/libSceHmd/Export.cpp) implements only the disconnected-headset path: initialization succeeds, device queries report `NotDetected`, and opening a device returns `DeviceDisconnected`. Headset support, tracking, rendering and additional HMD exports are not implemented; SDK-level ABI compatibility and in-game behaviour remain unverified. From 05dc3e898208e94e44e07c2439ad6d14724710ce Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:26:53 +0200 Subject: [PATCH 139/164] feat(npcommerce): implement sceNpCommerceSetPsStoreIconLayout Titles set the PS Store icon layout before they show the icon, and a title that imports the call stops at startup on the unresolved import. No PS Store icon is drawn, so like the show and hide calls it accepts every layout and returns 0 without effect. The signature follows shadPS4 (a PS4 reference), which also returns OK for every layout. The libSceNpCommerce entry under "Silent stubs" in TechnicalDebt now names the function. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceNpCommerce/Export.cpp | 5 +++++ docs/dev/TechnicalDebt.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceNpCommerce/Export.cpp b/core/libs/prx/libSceNpCommerce/Export.cpp index 6d8f3f581..6b2bd01d9 100644 --- a/core/libs/prx/libSceNpCommerce/Export.cpp +++ b/core/libs/prx/libSceNpCommerce/Export.cpp @@ -74,4 +74,9 @@ int APS5_VABI sceNpCommerceShowPsStoreIcon(int pos) { return 0; } +int APS5_VABI sceNpCommerceSetPsStoreIconLayout(int layout) { + (void)layout; + return 0; +} + } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..6ff4ac3dd 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -16,7 +16,7 @@ Throughout the project, every function at every stage either **does exactly what - [libSceErrorDialog](../../core/libs/prx/libSceErrorDialog/Export.cpp) - the error dialog runs the state machine but shows nothing; the error code passed to `sceErrorDialogOpen` is only logged - [libSceHmd](../../core/libs/prx/libSceHmd/Export.cpp) implements only the disconnected-headset path: initialization succeeds, device queries report `NotDetected`, and opening a device returns `DeviceDisconnected`. Headset support, tracking, rendering and additional HMD exports are not implemented; SDK-level ABI compatibility and in-game behaviour remain unverified. - [libSceAudioPropagation](../../core/libs/prx/libSceAudioPropagation/Export.cpp) implements only the no-acoustic-propagation path: the system keeps a 16-byte header in the title's CPU memory (no GPU memory), rooms, portals, sources and materials are tracked handles, attributes are validated and have no effect, no rays are requested and sources report no audio paths. Ray results, path calculation and audio paths are accepted only when empty and throw otherwise; `sceAudioPropagationSourceRender` writes silence (zeroes) to each source's output buffer for the given size, without a dry signal. Objects still alive when their system is destroyed become unusable, and their later destroy or unregister is a no-op: PPSA21567 releases its reference-counted room, portal and source owners just before `sceAudioPropagationSystemDestroy`, so the order for every object is not proven. Occlusion, reflections and reverb from level geometry are absent; in-game behaviour remains unverified. -- [libSceNpCommerce](../../core/libs/prx/libSceNpCommerce/Export.cpp) - the PS Store icon show/hide calls do nothing +- [libSceNpCommerce](../../core/libs/prx/libSceNpCommerce/Export.cpp) - the PS Store icon show/hide calls and `sceNpCommerceSetPsStoreIconLayout` do nothing - [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) validates the priority and CPU affinity that the open param requests for the VideoOut service thread but does not apply them: the port's present and vblank threads are host threads, and guest priorities and affinities do not reach host scheduling - The shader recompiler [skips baryctric coordinates](../../core/shader/recompiler/Recompiler.cpp) (is not even passed to SpirvTargetOptions at row 212). - [libSceAvPlayer](../../core/libs/prx/libSceAvPlayer/Export.cpp): `sceAvPlayerSetLogCallback` accepts a callback that is never called, as the player produces no log messages, and `sceAvPlayerSetAvailableBandwidth` has no effect, as it governs HLS sources, which `sceAvPlayerAddSource` does not implement. From 1a88fe66a8fd89c13198ab88727b8044e11bab9b Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:35:33 +0200 Subject: [PATCH 140/164] feat(npsessionsignaling): implement offline RequestPrepare and GetLocalNetInfo Without network a signaling context is never prepared. Like ActivateSession, ActivateUser and GetConnectionInfo, sceNpSessionSignalingRequestPrepare returns 0x80552D06 (UNAVAILABLE) and writes no request id. sceNpSessionSignalingGetLocalNetInfo no longer throws: it returns 0x80552D06 and leaves the info unchanged, as libSceNetCtl reports neither an address nor NAT info offline. A null request id or info returns 0x80552D02, as a null context id does in CreateContext2. GetConnectionStatus still throws. The new guest_np_session_signaling test checks the error codes and that failed calls leave the request id and info unchanged; it is registered in a new libSceNpSessionSignaling CMakeLists.txt. Co-Authored-By: Claude Opus 5.5 --- .../libSceNpSessionSignaling/CMakeLists.txt | 20 ++++++++ .../prx/libSceNpSessionSignaling/Export.cpp | 11 ++-- core/libs/tests/GuestNpSessionSignaling.cpp | 51 +++++++++++++++++++ 3 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 core/libs/prx/libSceNpSessionSignaling/CMakeLists.txt create mode 100644 core/libs/tests/GuestNpSessionSignaling.cpp diff --git a/core/libs/prx/libSceNpSessionSignaling/CMakeLists.txt b/core/libs/prx/libSceNpSessionSignaling/CMakeLists.txt new file mode 100644 index 000000000..6446469df --- /dev/null +++ b/core/libs/prx/libSceNpSessionSignaling/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(libSceNpSessionSignaling SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceNpSessionSignaling PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceNpSessionSignaling) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_np_session_signaling_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpSessionSignaling.cpp) + target_include_directories(guest_np_session_signaling_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_session_signaling_tests PRIVATE libSceNpSessionSignaling) + add_test(NAME guest_np_session_signaling COMMAND guest_np_session_signaling_tests) + if(WIN32) + set_tests_properties(guest_np_session_signaling PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpSessionSignaling/Export.cpp b/core/libs/prx/libSceNpSessionSignaling/Export.cpp index d140181b6..eb3ee27e0 100644 --- a/core/libs/prx/libSceNpSessionSignaling/Export.cpp +++ b/core/libs/prx/libSceNpSessionSignaling/Export.cpp @@ -69,9 +69,14 @@ int32_t APS5_VABI sceNpSessionSignalingGetConnectionStatus(int32_t context_id, i int32_t APS5_VABI sceNpSessionSignalingGetLocalNetInfo(int32_t context_id, void* info) { (void)context_id; - (void)info; - NotImplemented_nid_no_patch(__func__); - return 0; + if (!info) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + return SCE_NP_SESSION_SIGNALING_ERROR_UNAVAILABLE; +} + +int APS5_VABI sceNpSessionSignalingRequestPrepare(uint32_t contextId, uint32_t* requestId) { + (void)contextId; + if (!requestId) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + return SCE_NP_SESSION_SIGNALING_ERROR_UNAVAILABLE; } int APS5_VABI sceNpSessionSignalingGetMemoryInfo(void) { diff --git a/core/libs/tests/GuestNpSessionSignaling.cpp b/core/libs/tests/GuestNpSessionSignaling.cpp new file mode 100644 index 000000000..10ec8b222 --- /dev/null +++ b/core/libs/tests/GuestNpSessionSignaling.cpp @@ -0,0 +1,51 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceNpSessionSignalingInitialize(void* param); +int APS5_VABI sceNpSessionSignalingCreateContext2(const void* param, std::uint32_t* contextId); +std::int32_t APS5_VABI sceNpSessionSignalingGetLocalNetInfo(std::int32_t contextId, void* info); +int APS5_VABI sceNpSessionSignalingRequestPrepare(std::uint32_t contextId, std::uint32_t* requestId); +int APS5_VABI sceNpSessionSignalingTerminate(void); +} + +namespace { + +constexpr int InvalidArgument = static_cast(0x80552D02u); +constexpr int Unavailable = static_cast(0x80552D06u); + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "NpSessionSignaling: %s\n", message); + std::abort(); + } +} + +} + +int main() { + unsigned char initializeParam[32] = {}; + Require(sceNpSessionSignalingInitialize(initializeParam) == 0, "initialize failed"); + unsigned char contextParam[64] = {}; + std::uint32_t contextId = 0; + Require(sceNpSessionSignalingCreateContext2(contextParam, &contextId) == 0, "context creation failed"); + + std::uint32_t requestId = 0xa5a5a5a5u; + Require(sceNpSessionSignalingRequestPrepare(contextId, nullptr) == InvalidArgument, "null request id"); + Require(sceNpSessionSignalingRequestPrepare(contextId, &requestId) == Unavailable, "prepare without network"); + Require(requestId == 0xa5a5a5a5u, "failed prepare wrote a request id"); + + const auto netContextId = static_cast(contextId); + unsigned char info[16]; + std::memset(info, 0xa5, sizeof(info)); + unsigned char untouched[sizeof(info)]; + std::memcpy(untouched, info, sizeof(info)); + Require(sceNpSessionSignalingGetLocalNetInfo(netContextId, nullptr) == InvalidArgument, "null info"); + Require(sceNpSessionSignalingGetLocalNetInfo(netContextId, info) == Unavailable, "local net info without network"); + Require(std::memcmp(info, untouched, sizeof(info)) == 0, "failed query modified the info"); + + Require(sceNpSessionSignalingTerminate() == 0, "terminate failed"); +} From 776ecd3b3581f843a145de5307bbeee033d4518a Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:25:33 +0200 Subject: [PATCH 141/164] feat(psml): implement the MFSR 1100 calls for hardware without PSSR scePsmlMfsrGetContextBufferRequirement1100, scePsmlMfsrCreateContext1100 and scePsmlMfsrGetDispatchMfsrPacket1100 were not exported, so a title importing them failed to load. In Sony's libScePsml all three return 0x8A810001 (not initialized) without writing their outputs until scePsmlMfsrInit has succeeded. AnyPS5's scePsmlMfsrInit throws, so that is the only state a title can reach, and the three calls now return that error. The existing stubs for scePsmlMfsrInit, scePsmlMfsrReleaseContext and scePsmlMfsrGetDispatchMfsrPacketSizeInDwords get their real names and still throw. libScePsml_debug gets a CMakeLists.txt for the new guest_psml test. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libScePsml_debug/CMakeLists.txt | 20 +++++++ core/libs/prx/libScePsml_debug/Export.cpp | 34 +++++++++--- core/libs/tests/GuestPsml.cpp | 53 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 core/libs/prx/libScePsml_debug/CMakeLists.txt create mode 100644 core/libs/tests/GuestPsml.cpp diff --git a/core/libs/prx/libScePsml_debug/CMakeLists.txt b/core/libs/prx/libScePsml_debug/CMakeLists.txt new file mode 100644 index 000000000..ee07e525e --- /dev/null +++ b/core/libs/prx/libScePsml_debug/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(libScePsml_debug SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libScePsml_debug PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libScePsml_debug) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_psml_tests ${LIBS_INCLUDE_DIR}/tests/GuestPsml.cpp) + target_include_directories(guest_psml_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_psml_tests PRIVATE libScePsml_debug) + add_test(NAME guest_psml COMMAND guest_psml_tests) + if(WIN32) + set_tests_properties(guest_psml PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libScePsml_debug/Export.cpp b/core/libs/prx/libScePsml_debug/Export.cpp index 67d7cf76b..2e8ee6e64 100644 --- a/core/libs/prx/libScePsml_debug/Export.cpp +++ b/core/libs/prx/libScePsml_debug/Export.cpp @@ -3,22 +3,45 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr std::int32_t SCE_PSML_ERROR_NOT_INITIALIZED = static_cast(0x8A810001); + +} + extern "C" { +std::int32_t APS5_VABI scePsmlMfsrGetContextBufferRequirement1100(void* requirement, const void* param) { + (void)requirement; + (void)param; + return SCE_PSML_ERROR_NOT_INITIALIZED; +} + +std::int32_t APS5_VABI scePsmlMfsrCreateContext1100(void** context, const void* param) { + (void)context; + (void)param; + return SCE_PSML_ERROR_NOT_INITIALIZED; +} + +std::int32_t APS5_VABI scePsmlMfsrGetDispatchMfsrPacket1100(void* context, void* commandBuffer, const void* param) { + (void)context; + (void)commandBuffer; + (void)param; + return SCE_PSML_ERROR_NOT_INITIALIZED; +} + APS5_EXPORT("+2KpvixvL6E", scePsmlUnknown__P2KpvixvL6E); int APS5_VABI scePsmlUnknown__P2KpvixvL6E() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("3WVD91e12ZQ", scePsmlUnknown_3WVD91e12ZQ); -int APS5_VABI scePsmlUnknown_3WVD91e12ZQ() { +int APS5_VABI scePsmlMfsrInit() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("AHalTX9wFZY", scePsmlUnknown_AHalTX9wFZY); -int APS5_VABI scePsmlUnknown_AHalTX9wFZY() { +int APS5_VABI scePsmlMfsrGetDispatchMfsrPacketSizeInDwords() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -47,8 +70,7 @@ int APS5_VABI scePsmlUnknown_GJY0MvuTcs8() { return 0; } -APS5_EXPORT("JaLBe0P3jSU", scePsmlUnknown_JaLBe0P3jSU); -int APS5_VABI scePsmlUnknown_JaLBe0P3jSU() { +int APS5_VABI scePsmlMfsrReleaseContext() { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/tests/GuestPsml.cpp b/core/libs/tests/GuestPsml.cpp new file mode 100644 index 000000000..d0aac1d17 --- /dev/null +++ b/core/libs/tests/GuestPsml.cpp @@ -0,0 +1,53 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +extern "C" { +std::int32_t APS5_VABI scePsmlMfsrGetContextBufferRequirement1100(void* requirement, const void* param); +std::int32_t APS5_VABI scePsmlMfsrCreateContext1100(void** context, const void* param); +std::int32_t APS5_VABI scePsmlMfsrGetDispatchMfsrPacket1100(void* context, void* commandBuffer, const void* param); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr std::int32_t kErrNotInitialized = static_cast(0x8A810001); + +struct CommandBuffer { + std::uint32_t* cursor; + std::uint32_t sizeInDwords; +}; + +} + +int main() { + std::array param{}; + + std::array requirement; + requirement.fill(0xa5); + const auto untouchedRequirement = requirement; + Require(scePsmlMfsrGetContextBufferRequirement1100(requirement.data(), param.data()) == kErrNotInitialized); + Require(requirement == untouchedRequirement); + Require(scePsmlMfsrGetContextBufferRequirement1100(nullptr, nullptr) == kErrNotInitialized); + + std::uint64_t contextStorage = 0; + void* const sentinel = &contextStorage; + void* context = sentinel; + Require(scePsmlMfsrCreateContext1100(&context, param.data()) == kErrNotInitialized); + Require(context == sentinel); + Require(scePsmlMfsrCreateContext1100(nullptr, nullptr) == kErrNotInitialized); + + std::array dwords; + dwords.fill(0xa5a5a5a5u); + const auto untouchedDwords = dwords; + CommandBuffer commandBuffer{dwords.data(), static_cast(dwords.size())}; + Require(scePsmlMfsrGetDispatchMfsrPacket1100(&contextStorage, &commandBuffer, param.data()) == kErrNotInitialized); + Require(commandBuffer.cursor == dwords.data()); + Require(commandBuffer.sizeInDwords == dwords.size()); + Require(dwords == untouchedDwords); + Require(scePsmlMfsrGetDispatchMfsrPacket1100(nullptr, &commandBuffer, param.data()) == kErrNotInitialized); + Require(commandBuffer.cursor == dwords.data()); + Require(dwords == untouchedDwords); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..698d5c807 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -54,6 +54,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceAudioOutGetLastOutputTime](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - signature and behaviour taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [fpPS4](https://github.com/red-prig/fpPS4) reimplementations of the PS4 library, not confirmed on a PS5 title: the time is the process time at which `sceAudioOutOutput` or `sceAudioOutOutputs` accepted the port's last block, not the time the block reached the device - [AOWqIYsgVHs](../../core/libs/prx/libSceContentExport/Export.cpp) (libSceContentExport) - unknown name, signature - [GQTObcITIXI](../../core/libs/prx/libSceShare/Export.cpp) (libSceShare) - unknown name, signature +- [scePsmlMfsrGetContextBufferRequirement1100, scePsmlMfsrCreateContext1100, scePsmlMfsrGetDispatchMfsrPacket1100](../../core/libs/prx/libScePsml_debug/Export.cpp) (libScePsml_debug) - argument layouts unknown; only 0x8A810001 (not initialized) is returned - [BnMAMrsfVWo](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libc) - unknown name, signature - [scePngEncEncode](../../core/libs/prx/libScePngEnc/Export.cpp) (libScePngEnc) - whether rows may stay unfiltered when `filter_type` names a subset of the filters is unknown; as in shadPS4, they may only for 0 and for the all-filters mask - AudioIn, NpSessionSignaling and PlayerInvitationDialog exports added without an implementation have assumed signatures From 823c3caf4fdccd351992d803b26cc28c9673b374 Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 16:32:49 +0200 Subject: [PATCH 142/164] fix(relinker): keep Windows startup stacks below the guest arena The PE writer marked relocatable executables HIGH_ENTROPY_VA. Windows then places the main thread stack, TEB, PEB and the loader threads' stacks, which exist before libc reserves the guest arena from 0x2_0000_0000, anywhere in the first TiB. Nearly half the time they land inside the arena and stay there as holes that fixed guest mappings cannot use. DllCharacteristics is now 0x140 (DYNAMIC_BASE, NX_COMPAT). The image is still relocated and bottom-up allocations keep the 8-bit randomization, but they start near the bottom of the address space, far below the arena. DLL placement does not depend on this flag. windows_address_space checks the flags and, on Windows, runs a relinked guest that returns the high dword of its stack pointer. Co-Authored-By: Claude Opus 5.5 --- core/relinker/CMakeLists.txt | 2 + .../src/windows/WindowsPeWriter.cpp | 2 +- .../tests/test_windows_address_space.py | 45 +++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 core/relinker/relinker/tests/test_windows_address_space.py diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index a388ec7a3..c704aaf97 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -202,6 +202,8 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_entry_argv.py $) add_test(NAME windows_gui COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_gui.py $) + add_test(NAME windows_address_space COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_address_space.py $) add_test(NAME windows_icon COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_icon.py $) add_test(NAME guest_intel_trampolines COMMAND ${Python3_EXECUTABLE} diff --git a/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp b/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp index 61c0a567d..3c4e9ac34 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp @@ -30,7 +30,7 @@ std::vector WindowsPeWriter::Write(const std::vector& s Io::WriteU16(result, optionalOffset + 50, 2); Io::WriteU32(result, optionalOffset + 60, LoadRva); Io::WriteU16(result, optionalOffset + 68, windowsGui ? 2 : 3); - Io::WriteU16(result, optionalOffset + 70, directories[5].Size == 0 ? 0x100 : 0x160); + Io::WriteU16(result, optionalOffset + 70, directories[5].Size == 0 ? 0x100 : 0x140); Io::WriteU64(result, optionalOffset + 72, 0x210000); Io::WriteU64(result, optionalOffset + 80, 0x200000); Io::WriteU64(result, optionalOffset + 88, 0x100000); diff --git a/core/relinker/relinker/tests/test_windows_address_space.py b/core/relinker/relinker/tests/test_windows_address_space.py new file mode 100644 index 000000000..d2720daf6 --- /dev/null +++ b/core/relinker/relinker/tests/test_windows_address_space.py @@ -0,0 +1,45 @@ +"""Keep the main thread stack that Windows creates before libc reserves the guest arena below it.""" + +import os +from pathlib import Path +import struct +import subprocess +import sys +import tempfile + +from test_optional_plt import fixture + +GUEST_ARENA_START = 0x200000000 +DYNAMIC_BASE = 0x40 +NX_COMPAT = 0x100 + + +def dll_characteristics(pe): + pe_offset = struct.unpack_from("> 32, \ + f"stack not below {GUEST_ARENA_START:#x}: exit code {executed.returncode:#x}" + print("Windows address space integration tests passed") + + +if __name__ == "__main__": + main() From 15b191ef6a1c444b48bbeb9d14de3cc476adb1ff Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:27:44 +0200 Subject: [PATCH 143/164] feat(systemservice): implement sceSystemServiceParamGetString The export threw, and titles query the system name during startup. Parameter 6 (the system name) now writes the fixed name "PS5" into a buffer of at least SYSTEM_SERVICE_MAX_SYSTEM_NAME_LENGTH (65) bytes. A null buffer or a zero size returns SYSTEM_SERVICE_ERROR_PARAMETER, as in KytyPS5. Other parameter ids and buffers of 1 to 64 bytes still throw, because no PS5 source gives their result. guest_system_service covers the error rows, both throws and the returned name. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceSystemService/Export.cpp | 19 ++++++++++----- .../prx/libSceSystemService/SystemService.hpp | 4 ++++ core/libs/tests/GuestSystemService.cpp | 23 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libSceSystemService/Export.cpp b/core/libs/prx/libSceSystemService/Export.cpp index 01a6365a5..205897fe2 100644 --- a/core/libs/prx/libSceSystemService/Export.cpp +++ b/core/libs/prx/libSceSystemService/Export.cpp @@ -80,12 +80,19 @@ int APS5_VABI sceSystemServiceParamGetInt(int paramId, int* value) { return SYSTEM_SERVICE_OK; } -int APS5_VABI sceSystemServiceParamGetString(int param_id, char* buf, size_t buf_size) { - (void)param_id; - (void)buf; - (void)buf_size; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSystemServiceParamGetString(int paramId, char* buf, size_t bufSize) { + if (buf == nullptr || bufSize == 0) { + return SYSTEM_SERVICE_ERROR_PARAMETER; + } + if (paramId != SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME) { + NotImplemented_nid_no_patch("sceSystemServiceParamGetString: parameter other than the system name"); + } + if (bufSize < SYSTEM_SERVICE_MAX_SYSTEM_NAME_LENGTH) { + NotImplemented_nid_no_patch("sceSystemServiceParamGetString: buffer shorter than 65 bytes"); + } + constexpr char SystemName[] = "PS5"; + std::memcpy(buf, SystemName, sizeof(SystemName)); + return SYSTEM_SERVICE_OK; } int APS5_VABI sceSystemServicePowerTick(void) { diff --git a/core/libs/prx/libSceSystemService/SystemService.hpp b/core/libs/prx/libSceSystemService/SystemService.hpp index 7a2c84a12..e86820da4 100644 --- a/core/libs/prx/libSceSystemService/SystemService.hpp +++ b/core/libs/prx/libSceSystemService/SystemService.hpp @@ -1,6 +1,7 @@ #ifndef CORE_LIBS_PRX_LIBSCESYSTEMSERVICE_SYSTEMSERVICE_HPP #define CORE_LIBS_PRX_LIBSCESYSTEMSERVICE_SYSTEMSERVICE_HPP +#include #include constexpr int SYSTEM_SERVICE_OK = 0; @@ -12,9 +13,12 @@ constexpr int SYSTEM_SERVICE_PARAM_ID_DATE_FORMAT = 2; constexpr int SYSTEM_SERVICE_PARAM_ID_TIME_FORMAT = 3; constexpr int SYSTEM_SERVICE_PARAM_ID_TIME_ZONE = 4; constexpr int SYSTEM_SERVICE_PARAM_ID_SUMMERTIME = 5; +constexpr int SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME = 6; constexpr int SYSTEM_SERVICE_PARAM_ID_GAME_PARENTAL_LEVEL = 7; constexpr int SYSTEM_SERVICE_PARAM_ID_ENTER_BUTTON_ASSIGN = 1000; +constexpr std::size_t SYSTEM_SERVICE_MAX_SYSTEM_NAME_LENGTH = 65; + constexpr int SYSTEM_SERVICE_PARAM_LANG_ENGLISH_US = 1; constexpr int SYSTEM_SERVICE_PARAM_DATE_FORMAT_DDMMYYYY = 1; constexpr int SYSTEM_SERVICE_PARAM_TIME_FORMAT_24HOUR = 1; diff --git a/core/libs/tests/GuestSystemService.cpp b/core/libs/tests/GuestSystemService.cpp index 88193689e..fe937fc57 100644 --- a/core/libs/tests/GuestSystemService.cpp +++ b/core/libs/tests/GuestSystemService.cpp @@ -1,13 +1,26 @@ #include "SceTypes.hpp" #include "prx/libSceSystemService/SystemService.hpp" +#include #include +#include +#include extern "C" int APS5_VABI sceSystemServiceGetHdrToneMapLuminance(SystemServiceHdrToneMapLuminance* luminance); +extern "C" int APS5_VABI sceSystemServiceParamGetString(int paramId, char* buf, std::size_t bufSize); namespace { void Require(bool value) { if (!value) std::abort(); } +bool ParamGetStringThrows(int paramId, char* buf, std::size_t bufSize) { + try { + static_cast(sceSystemServiceParamGetString(paramId, buf, bufSize)); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + } int main() { @@ -17,4 +30,14 @@ int main() { Require(luminance.max_full_frame_tone_map_luminance == 100.0f); Require(luminance.max_tone_map_luminance == 100.0f); Require(luminance.min_tone_map_luminance == 0.0f); + + char name[SYSTEM_SERVICE_MAX_SYSTEM_NAME_LENGTH]; + std::memset(name, 'x', sizeof(name)); + Require(sceSystemServiceParamGetString(SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME, nullptr, sizeof(name)) == SYSTEM_SERVICE_ERROR_PARAMETER); + Require(sceSystemServiceParamGetString(SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME, name, 0) == SYSTEM_SERVICE_ERROR_PARAMETER); + Require(ParamGetStringThrows(SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME, name, sizeof(name) - 1)); + Require(ParamGetStringThrows(SYSTEM_SERVICE_PARAM_ID_LANG, name, sizeof(name))); + Require(name[0] == 'x'); + Require(sceSystemServiceParamGetString(SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME, name, sizeof(name)) == SYSTEM_SERVICE_OK); + Require(std::strcmp(name, "PS5") == 0); } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a84ead929..1762bbdc5 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -74,6 +74,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceNpTrophy2UnregisterUnlockCallback](../../core/libs/prx/libSceNpTrophy2/src/Context.cpp) (libSceNpTrophy2) - unknown signature - [sceSystemServiceDisableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature - [sceSystemServiceReenableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature +- [sceSystemServiceParamGetString](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - the system name (parameter 6) is the fixed `PS5`, not the console's name; other parameters and buffers of 1 to 64 bytes throw - [sceVoiceSetMuteFlag](../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature - [snwprintf_s](../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follows C11 K.3.9.1.3 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`. A runtime-constraint violation only returns a negative value: `set_constraint_handler_s` is not exported and the console's default handler is unknown. An invalid multibyte `%s` argument is copied byte by byte instead of being reported as an encoding error, as in `vswprintf` - [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) - the added `sceUltQueuePop`, `sceUltQueueTryPush`, `sceUltQueueDestroy` and `sceUltQueueDataResourcePoolDestroy` signatures are inferred from the existing push/pop and object APIs. Queue destruction is assumed to reject blocked callers with `ULT_ERROR_BUSY` and otherwise discard queued data; pool exhaustion returns `ULT_ERROR_AGAIN`. These rules, alignment checks and shared slot limits have not been confirmed on PS5 hardware or a title. Non-null queue and data-pool option parameters throw because their layouts are unknown. From 62c70fb1d5336b885f8c632d4271ea60225b2ab4 Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:25:42 +0200 Subject: [PATCH 144/164] fix(libkernel): state the size of a direct memory backing Windows cannot create On Windows each direct memory allocation is backed by a page-file section that CreateFileMappingW commits in full. When the system commit limit cannot cover it, the title stopped with "create direct memory backing: Insufficient system resources exist to complete the requested service", which did not say how much was requested. The error now states the requested size in bytes and MiB, and the runtime layout notes in USAGE.md describe the commit requirement and the page file. Allocation behaviour is unchanged: committing at allocation matches the console, where allocated direct memory is always backed by physical memory. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp | 7 ++++++- docs/user/USAGE.md | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index 25147d08a..ef114beba 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -226,7 +226,12 @@ public: #ifdef _WIN32 const auto size = static_cast(bytes); section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE, static_cast(size >> 32), static_cast(size), nullptr); - if (!section) throw std::system_error(static_cast(GetLastError()), std::system_category(), "create direct memory backing"); + if (!section) { + const auto error = static_cast(GetLastError()); + char message[96]; + std::snprintf(message, sizeof(message), "create direct memory backing of 0x%llx bytes (%llu MiB)", static_cast(size), static_cast((size + 0xFFFFF) >> 20)); + throw std::system_error(error, std::system_category(), message); + } #else file = memfd_create("direct memory", MFD_CLOEXEC); if (file < 0) throw std::system_error(errno, std::generic_category(), "create direct memory backing"); diff --git a/docs/user/USAGE.md b/docs/user/USAGE.md index 3b8a408be..7cb429298 100644 --- a/docs/user/USAGE.md +++ b/docs/user/USAGE.md @@ -70,6 +70,8 @@ app0/ Use `sce_modules/` or `prx/` instead of `sce_module/` if that is the input directory name. Relinker preserves each module's directory under `app0/` and prints its exact path. Place app resources in `app0/` separately. Copy the built system libraries from `build/core/libs/libs/*.prx` into `libs/`; use libraries built for the target OS. A custom `--rpath` changes the system library location. +On Windows, direct memory (`sceKernelAllocateDirectMemory`, up to 13824 MiB per title) is committed in full when the title allocates it, not when its pages are first used. The system commit limit (installed memory plus page file size, the second value of Committed in Task Manager) must cover it together with all other committed memory. Otherwise the allocation throws `create direct memory backing of 0x bytes ( MiB)` with the Windows error; enlarge the page file or close other applications. + Linux: ```sh From e30336c8154b55d1cc32a6a5c34e09963981b2a6 Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:23:20 +0200 Subject: [PATCH 145/164] feat(razor): implement sceRazorCpuPushMarkerStatic and sceRazorCpuPopMarker Ghost of Yotei (PPSA26344) imports both functions, so its import resolution failed at startup. Markers only feed an attached Razor capture. AnyPS5 hosts none and sceRazorCpuIsCapturing reports 0, so, like the other libSceRazorCpu exports, both do nothing and return 0. The push signature comes from the title's calls (a string literal, 0x80ffffff, 2). TechnicalDebt lists both functions with the assumed signatures. guest_razor_cpu covers nested and unbalanced markers. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceRazorCpu/Export.cpp | 11 +++++++++++ core/libs/tests/GuestRazorCpu.cpp | 8 ++++++++ docs/dev/TechnicalDebt.md | 1 + 3 files changed, 20 insertions(+) diff --git a/core/libs/prx/libSceRazorCpu/Export.cpp b/core/libs/prx/libSceRazorCpu/Export.cpp index 83a295d21..0a5ec3953 100644 --- a/core/libs/prx/libSceRazorCpu/Export.cpp +++ b/core/libs/prx/libSceRazorCpu/Export.cpp @@ -24,4 +24,15 @@ int APS5_VABI sceRazorCpuJobManagerSequence(const void* args) { return 0; } +int APS5_VABI sceRazorCpuPushMarkerStatic(const char* name, uint32_t color, uint32_t flags) { + (void)name; + (void)color; + (void)flags; + return 0; +} + +int APS5_VABI sceRazorCpuPopMarker(void) { + return 0; +} + } diff --git a/core/libs/tests/GuestRazorCpu.cpp b/core/libs/tests/GuestRazorCpu.cpp index 2b966d8a6..0f1469a82 100644 --- a/core/libs/tests/GuestRazorCpu.cpp +++ b/core/libs/tests/GuestRazorCpu.cpp @@ -7,6 +7,8 @@ std::uint32_t APS5_VABI sceRazorCpuIsCapturing(void); int APS5_VABI sceRazorCpuJobManagerDispatch(const void* args); int APS5_VABI sceRazorCpuJobManagerJob(const void* args); int APS5_VABI sceRazorCpuJobManagerSequence(const void* args); +int APS5_VABI sceRazorCpuPushMarkerStatic(const char* name, std::uint32_t color, std::uint32_t flags); +int APS5_VABI sceRazorCpuPopMarker(void); } namespace { @@ -20,4 +22,10 @@ int main() { Require(sceRazorCpuJobManagerDispatch(nullptr) == 0); Require(sceRazorCpuJobManagerJob(nullptr) == 0); Require(sceRazorCpuJobManagerSequence(nullptr) == 0); + Require(sceRazorCpuPushMarkerStatic("outer", 0x80ffffffu, 2) == 0); + Require(sceRazorCpuPushMarkerStatic("inner", 0x80ffffffu, 2) == 0); + Require(sceRazorCpuPopMarker() == 0); + Require(sceRazorCpuPopMarker() == 0); + Require(sceRazorCpuPushMarkerStatic("unbalanced", 0x80ffffffu, 2) == 0); + Require(sceRazorCpuIsCapturing() == 0); } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 02ea84a84..1477e6bb8 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -64,6 +64,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceAvPlayerStartEx](../../core/libs/prx/libSceAvPlayer/Export.cpp) (libSceAvPlayer) - start info layout unknown; it is ignored and playback starts as with `sceAvPlayerStart` - [sceAvPlayerInit / sceAvPlayerInitEx](../../core/libs/prx/libSceAvPlayer/src/Player.cpp) (libSceAvPlayer) - behaviour without a memory replacement unknown; frame and sample buffers then come from the guest heap - [SceAvPlayerVideoEx](../../core/libs/SceTypes.hpp) (libSceAvPlayer) - frame rate field and encoding unknown; it is left zero in frame and stream info +- [sceRazorCpuPushMarkerStatic, sceRazorCpuPopMarker](../../core/libs/prx/libSceRazorCpu/Export.cpp) (libSceRazorCpu) - signatures from PPSA26344's calls; (name, color, flags) assumed for the push; both do nothing without a capture - [sceAjmDecMp3ParseFrame](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - signature, result layout and rate tables taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation of the PS4 library, not confirmed on a PS5 title: the layer field is not checked and MPEG-2.5 stops at 64 kbps - [sceAjmBatchJobGetCodecInfo, sceAjmBatchJobGetGaplessDecode](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the `RUN_GET_CODEC_INFO` flag (0x800), the ATRAC9 codec info layout and its next frame size are taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [RPCSX](https://github.com/RPCSX/rpcsx) reimplementations, not confirmed on a PS5 title; a run job without buffers returns result 0, as in shadPS4. The gapless sideband reports the total and skip counts the title set and does not count them down: RPCSX does the same, shadPS4 counts them down and BryKytyPS5 returns its current counts - [AJM Opus decoder](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native, codec 24) - the initialize parameter layout (`u32 channels, u32 sample rate, u32`, seen as `2, 48000, 0`) and the little-endian `u16` byte count before each packet come from one title (The Smurfs Dreams); only 48000 Hz and a zero third word are accepted From 4450965721b2b2081e01deaffd18b66072f425c8 Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 11:18:33 +0200 Subject: [PATCH 146/164] feat(voiceqos): declare the missing libSceVoiceQoS exports libSceVoiceQoS exported only sceVoiceQoSInit, so a title that imports the rest of the library failed at load on unresolved imports. Declare sceVoiceQoSEnd, the local and remote endpoint create and delete functions, sceVoiceQoSConnect, sceVoiceQoSDisconnect, both endpoint attribute setters, sceVoiceQoSReadPacket and sceVoiceQoSWritePacket. Each calls NotImplemented_nid_no_patch and reads no arguments, so the imports resolve and a call still stops with the function's name. sceVoiceQoSInit is unchanged. Their behaviour follows in a later change, once a title is seen to reach them. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceVoiceQoS/Export.cpp | 55 +++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/core/libs/prx/libSceVoiceQoS/Export.cpp b/core/libs/prx/libSceVoiceQoS/Export.cpp index a0af71ff3..4cb9ab16f 100644 --- a/core/libs/prx/libSceVoiceQoS/Export.cpp +++ b/core/libs/prx/libSceVoiceQoS/Export.cpp @@ -13,4 +13,59 @@ int APS5_VABI sceVoiceQoSInit(void* mem_block, uint32_t mem_size, int32_t app_ty return 0; } +int APS5_VABI sceVoiceQoSEnd() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSCreateLocalEndpoint() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSDeleteLocalEndpoint() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSCreateRemoteEndpoint() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSDeleteRemoteEndpoint() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSConnect() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSDisconnect() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSSetLocalEndpointAttribute() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSSetRemoteEndpointAttribute() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSReadPacket() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSWritePacket() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + } From d3e2e53a7cef0c1b4ea0152156e9398ae7dab44e Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 11:23:54 +0200 Subject: [PATCH 147/164] feat(videoout): accept the STRICT_COLORIMETRY buffer option sceVideoOutRegisterBuffers2 and sceVideoOutSubmitChangeBufferAttribute2 threw for every buffer attribute option other than 0. PPSA26344 builds its display buffers with option 8 through the libSceAgc helper linked into it, so its first registration failed. Option 8, VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY (name and value from KytyPS5), is now accepted and presented as option 0; every other option still throws. What the console changes for it is unknown and recorded in TechnicalDebt. The rejection now names the tiling mode, pitch, aspect ratio and option it was given. video_out_flip_tests decode checks that option 8 describes the same buffer as option 0 and that options 1, 32 and 40 are rejected. Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceVideoOut/include/VideoOutDriver.hpp | 2 ++ core/libs/prx/libSceVideoOut/src/Buffer.cpp | 3 ++- core/libs/prx/libSceVideoOut/tests/Flip.cpp | 12 ++++++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp index ea75f8d80..adf59924a 100644 --- a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp +++ b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp @@ -62,6 +62,8 @@ static constexpr std::size_t VIDEO_OUT_FLIP_QUEUE_CAPACITY = 16; static constexpr int VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_UNCOMPRESSED = 0; static constexpr int VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_COMPRESSED = 1; +static constexpr std::uint64_t VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE = 0; +static constexpr std::uint64_t VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY = 8; static constexpr int VIDEO_OUT_EVENT_FLIP = 0; static constexpr int VIDEO_OUT_EVENT_VBLANK = 1; diff --git a/core/libs/prx/libSceVideoOut/src/Buffer.cpp b/core/libs/prx/libSceVideoOut/src/Buffer.cpp index aa40461e1..25fb6a241 100644 --- a/core/libs/prx/libSceVideoOut/src/Buffer.cpp +++ b/core/libs/prx/libSceVideoOut/src/Buffer.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -10,7 +11,7 @@ AgcDriver::DisplayBuffer DescribeVideoOutBuffer(const VideoOutBuffer& buffer, const BufferAttributeGroup& group) { const auto& attribute = group.attribute; if (attribute.reserved0 != 0 || attribute.pad0 != 0 || attribute.reserved1[0] != 0 || attribute.reserved1[1] != 0 || attribute.reserved1[2] != 0) throw std::runtime_error("VideoOut: reserved buffer attribute bits are set"); - if (attribute.tiling_mode > 1 || (attribute.tiling_mode == 0 && attribute.pitch_in_pixel != 0) || attribute.aspect_ratio != 0 || attribute.option != 0) throw std::runtime_error("VideoOut: unsupported tiling, pitch, aspect ratio or buffer option"); + if (attribute.tiling_mode > 1 || (attribute.tiling_mode == 0 && attribute.pitch_in_pixel != 0) || attribute.aspect_ratio != 0 || (attribute.option != VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE && attribute.option != VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY)) throw std::runtime_error("VideoOut: unsupported tiling " + std::to_string(attribute.tiling_mode) + ", pitch " + std::to_string(attribute.pitch_in_pixel) + ", aspect ratio " + std::to_string(attribute.aspect_ratio) + " or buffer option " + std::to_string(attribute.option)); if (group.category != VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_UNCOMPRESSED || buffer.metadataAddress != 0 || attribute.dcc_control != 0 || attribute.dcc_cb_register_clear_color != 0) throw std::runtime_error("VideoOut: DCC presentation is not implemented"); const AgcDriver::DisplayBuffer result{buffer.dataAddress, attribute.pixel_format, attribute.width, attribute.height, attribute.tiling_mode, attribute.pitch_in_pixel}; static_cast(AgcDriverDisplayBufferSize_nid_postfix(result)); diff --git a/core/libs/prx/libSceVideoOut/tests/Flip.cpp b/core/libs/prx/libSceVideoOut/tests/Flip.cpp index ad78a9798..9dd1463b0 100644 --- a/core/libs/prx/libSceVideoOut/tests/Flip.cpp +++ b/core/libs/prx/libSceVideoOut/tests/Flip.cpp @@ -162,6 +162,18 @@ void testDecode() { expectFailure([&] { AgcDriver::ReadDisplayBuffer(buffer); }); buffer.address = std::numeric_limits::max() & ~uint64_t{65535}; expectFailure([&] { AgcDriver::DisplayBufferSize(buffer); }); + const VideoOutBuffer registered{0, reinterpret_cast(tiled.data()), 0}; + BufferAttributeGroup group{}; + sceVideoOutSetBufferAttribute2(&group.attribute, 0x8000000000000000ull, 0, 259, 137, VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE, 0, 0); + group.occupied = true; + const auto plain = DescribeVideoOutBuffer(registered, group); + group.attribute.option = VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY; + const auto strict = DescribeVideoOutBuffer(registered, group); + check(strict.address == plain.address && strict.pixelFormat == plain.pixelFormat && strict.width == plain.width && strict.height == plain.height && strict.tilingMode == plain.tilingMode && strict.pitchInPixel == plain.pitchInPixel, "the STRICT_COLORIMETRY option changed the described buffer"); + for (const uint64_t option : {1ull, 32ull, 40ull}) { + group.attribute.option = option; + check(expectFailure([&] { DescribeVideoOutBuffer(registered, group); }).find("buffer option " + std::to_string(option)) != std::string::npos, "an unsupported buffer option was accepted"); + } } void testOpenParam() { diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 02ea84a84..b27c51fb1 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -25,6 +25,7 @@ Throughout the project, every function at every stage either **does exactly what ### Unknown function info - [sceAvPlayerAddSource](../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) - requests up to 4 additional decode-ahead video framebuffers from the memory replacement beyond `num_output_video_framebuffers` when memory is available; the console's internal buffering behaviour is unknown +- [sceVideoOutRegisterBuffers2](../../core/libs/prx/libSceVideoOut/src/Buffer.cpp) (libSceVideoOut) - buffer option 8 (STRICT_COLORIMETRY) is presented as option 0, with the stored values unchanged; what the console changes for it is unknown. Only options 0 and 8 are accepted - [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - the open param's first word is unknown (PPSA21564 passes 16; it is not the byte size, since the affinity mask is at offset 16); only 16 is accepted. Whether the param continues past offset 24 is also unknown - [sceRtcParseDateTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - accepted format assumed to be the ISO 8601 subset `YYYY-MM-DDTHH:MM:SS[.ffffff]` (T/t/space separator, optional fractional seconds) with the sibling RFC 3339 zone suffix (`Z` or `±HH:MM`); a string without a zone is taken as UTC, and anything outside this shape throws instead of returning a parse error, unverified against hardware - [AMPR WriteAddressFromCounterPairOnCompletion](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - the pair of an even counter n is assumed to give counter n in the low 32 bits and n + 1 in the high 32 bits. Wait compare values, wait flush and counter index ranges follow the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation; the flush is accepted and has no effect, as commands run in order From fa996bbccb8f7302cd8270b6aa8961725c8589a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 13:03:00 +0200 Subject: [PATCH 148/164] fix(shader): saturate VOP3 integer mad/sad results on clamp Co-Authored-By: Claude --- .../tests/execution/Vop3IntegerClamp.cpp | 188 ++++++++++++++++++ .../tests/execution/Vop3IntegerClampWide.cpp | 187 +++++++++++++++++ .../tests/execution/Vop3Sad.cpp | 14 -- .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 30 ++- .../Translation/TranslationContext.hpp | 2 + .../Translation/src/IntegerInstructions.cpp | 50 ++++- 6 files changed, 442 insertions(+), 29 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClamp.cpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClampWide.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClamp.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClamp.cpp new file mode 100644 index 000000000..4bc1207d3 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClamp.cpp @@ -0,0 +1,188 @@ +#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 +#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, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd543800a, 0x041a0b04, 0xd542800b, + 0x041a0b04, 0xd740800c, 0x041a0b04, 0xd75e800d, 0x041a0b04, 0xd773800e, 0x041a0b04, 0xd775800f, + 0x041a0b04, 0xd55a8010, 0x041a0b04, 0xd55b8011, 0x041a0b04, 0xd55c8012, 0x041a0b04, 0xd55d8013, + 0x041a0b04, 0xd5718014, 0x041a0b04, 0x7e2a0307, 0xd740d815, 0x041a0b04, 0xd75eb016, 0x041a0b04, + 0xd7758817, 0x041a0b04, 0xd7739018, 0x041a0b04, 0x7e320307, 0xd75ef819, 0x041a0b04, 0xe0781000, + 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000000u, 0x00000001u, 0x0000ffffu}, + {0x00000000u, 0x00007fffu, 0xffffffffu, 0x0000fff0u}, + {0x01010101u, 0x01010101u, 0xffffffffu, 0xff00ff00u}, + {0x00000001u, 0xff00ff00u, 0x80000000u, 0x7fffffffu}, + {0x00000000u, 0x00000000u, 0x506377feu, 0x09418c53u}, + {0x00000000u, 0x00000000u, 0xad078efau, 0x00000002u}, + {0x00000000u, 0x00000001u, 0x00000002u, 0x293b6032u}, + {0x00000000u, 0x00000001u, 0x0000fff0u, 0x00ff00ffu}, + {0x00000000u, 0x00000001u, 0x00ff00ffu, 0xdbd16e44u}, + {0x00000000u, 0x00000001u, 0x671b91d7u, 0xb1836b5au}, + {0x00000000u, 0x00000001u, 0xb51b0523u, 0x54b074b8u}, + {0x00000000u, 0x00000001u, 0xff0ec345u, 0x00007fffu}, + {0x00000000u, 0x00000002u, 0x00000000u, 0xfefefefeu}, + {0x00000000u, 0x0000007fu, 0x00000002u, 0xcee69ed0u}, + {0x00000000u, 0x0000007fu, 0x12345678u, 0x40778188u}, + {0x00000000u, 0x0000007fu, 0x1bf35776u, 0x12345678u}, + {0x00000000u, 0x0000007fu, 0x29c0ba3bu, 0x48e98a0au}, + {0x00000000u, 0x0000007fu, 0x9d299c7bu, 0x39cdd549u}, + {0x00000000u, 0x0000007fu, 0x9ff9949eu, 0x00ffffffu}, + {0x00000000u, 0x00000080u, 0x000000ffu, 0x12345678u}, + {0x00000000u, 0x00000080u, 0x00008000u, 0x80000001u}, + {0x00000000u, 0x00000080u, 0x0000fff0u, 0xae15989du}, + {0x00000000u, 0x00000080u, 0x0000ffffu, 0x00000080u}, + {0x00000000u, 0x00000080u, 0x80000001u, 0x0000007fu}, + {0x00000000u, 0x00000080u, 0xaf4a9e38u, 0xd42344f4u}, + {0x00000000u, 0x00000080u, 0xea4d8c45u, 0x00625618u}, + {0x00000000u, 0x00000080u, 0xfd4fb563u, 0xff00ff00u}, + {0x00000000u, 0x000000ffu, 0x00000001u, 0x310733d9u}, + {0x00000000u, 0x000000ffu, 0x2f3bacc0u, 0xffff0000u}, + {0x00000000u, 0x000000ffu, 0x85f55b11u, 0xffff0000u}, + {0x00000000u, 0x000000ffu, 0xff00ff00u, 0x9fc408e0u}, + {0x00000000u, 0x000000ffu, 0xffff0000u, 0xffff0000u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x0001ffffu, 0x00000000u, 0x00000001u, 0x00000001u, 0x0000ffffu}, + {0xffffffffu, 0xffffffffu, 0x0000ffffu, 0x0000ffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xfffffff0u, 0x0000ffffu, 0xffffffffu, 0xffffffffu, 0xfffffff0u}, + {0xffffffffu, 0x7fffffffu, 0x0000ffffu, 0x00007fffu, 0xffffffffu, 0x00010200u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffff00u, 0x00007fffu, 0x00010200u, 0xffffffffu, 0x7fffff00u}, + {0x8000ff00u, 0x8000ff00u, 0x0000ff00u, 0x0000ff00u, 0x8000ff00u, 0x80000000u, 0x800001ffu, 0x81ff0000u, 0x8001fdffu, 0xffffffffu, 0x800001feu, 0x0000ffffu, 0x00008000u, 0x80000000u, 0x8000ff00u, 0x8000ffffu}, + {0x506377feu, 0x506377feu, 0x000077feu, 0x000077feu, 0x506377feu, 0x506377feu, 0x506377feu, 0x506377feu, 0x506377feu, 0x506377feu, 0x506377feu, 0x77fe8c53u, 0x00005063u, 0x506377feu, 0x506377feu, 0x50638c53u}, + {0xad078efau, 0xad078efau, 0x00008efau, 0x00008efau, 0xad078efau, 0xad078efau, 0xad078efau, 0xad078efau, 0xad078efau, 0xad078efau, 0xad078efau, 0x8efa0002u, 0x0000ad07u, 0xad078efau, 0xad078efau, 0xad070002u}, + {0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000003u, 0x00010002u, 0x00000003u, 0x00000003u, 0x00000003u, 0x00026032u, 0x00000000u, 0x00000002u, 0x00000002u, 0x00006032u}, + {0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff1u, 0x0001fff0u, 0x0000fff1u, 0x0000fff1u, 0x0000fff1u, 0xfff000ffu, 0x00000000u, 0x0000fff0u, 0x0000fff0u, 0x000000ffu}, + {0x00ff00ffu, 0x00ff00ffu, 0x000000ffu, 0x000000ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff0100u, 0x010000ffu, 0x00ff0100u, 0x00ff0100u, 0x00ff0100u, 0x00ff6e44u, 0x000000ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff6e44u}, + {0x671b91d7u, 0x671b91d7u, 0x000091d7u, 0x000091d7u, 0x671b91d7u, 0x671b91d7u, 0x671b91d8u, 0x671c91d7u, 0x671b91d8u, 0x671b91d8u, 0x671b91d8u, 0x91d76b5au, 0x0000671bu, 0x671b91d7u, 0x671b91d7u, 0x671b6b5au}, + {0xb51b0523u, 0xb51b0523u, 0x00000523u, 0x00000523u, 0xb51b0523u, 0xb51b0523u, 0xb51b0524u, 0xb51c0523u, 0xb51b0524u, 0xb51b0524u, 0xb51b0524u, 0x052374b8u, 0x0000b51bu, 0xb51b0523u, 0xb51b0523u, 0xb51b74b8u}, + {0xff0ec345u, 0xff0ec345u, 0x0000c345u, 0x0000c345u, 0xff0ec345u, 0xff0ec345u, 0xff0ec346u, 0xff0fc345u, 0xff0ec346u, 0xff0ec346u, 0xff0ec346u, 0xc3457fffu, 0x0000ff0eu, 0xff0ec345u, 0xff0ec345u, 0xff0e7fffu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000002u, 0x00020000u, 0x00000002u, 0x00000002u, 0x00000002u, 0x0000fefeu, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000fefeu}, + {0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000081u, 0x007f0002u, 0x00000081u, 0x00000081u, 0x00000081u, 0x00029ed0u, 0x00000000u, 0x00000002u, 0x00000002u, 0x00009ed0u}, + {0x12345678u, 0x12345678u, 0x00005678u, 0x00005678u, 0x12345678u, 0x12345678u, 0x123456f7u, 0x12b35678u, 0x123456f7u, 0x123456f7u, 0x123456f7u, 0x56788188u, 0x00001234u, 0x12345678u, 0x12345678u, 0x12348188u}, + {0x1bf35776u, 0x1bf35776u, 0x00005776u, 0x00005776u, 0x1bf35776u, 0x1bf35776u, 0x1bf357f5u, 0x1c725776u, 0x1bf357f5u, 0x1bf357f5u, 0x1bf357f5u, 0x57765678u, 0x00001bf3u, 0x1bf35776u, 0x1bf35776u, 0x1bf35678u}, + {0x29c0ba3bu, 0x29c0ba3bu, 0x0000ba3bu, 0x0000ba3bu, 0x29c0ba3bu, 0x29c0ba3bu, 0x29c0babau, 0x2a3fba3bu, 0x29c0babau, 0x29c0babau, 0x29c0babau, 0xba3b8a0au, 0x000029c0u, 0x29c0ba3bu, 0x29c0ba3bu, 0x29c08a0au}, + {0x9d299c7bu, 0x9d299c7bu, 0x00009c7bu, 0x00009c7bu, 0x9d299c7bu, 0x9d299c7bu, 0x9d299cfau, 0x9da89c7bu, 0x9d299cfau, 0x9d299cfau, 0x9d299cfau, 0x9c7bd549u, 0x00009d29u, 0x9d299c7bu, 0x9d299c7bu, 0x9d29d549u}, + {0x9ff9949eu, 0x9ff9949eu, 0x0000949eu, 0x0000949eu, 0x9ff9949eu, 0x9ff9949eu, 0x9ff9951du, 0xa078949eu, 0x9ff9951du, 0x9ff9951du, 0x9ff9951du, 0x949effffu, 0x00009ff9u, 0x9ff9949eu, 0x9ff9949eu, 0x9ff9ffffu}, + {0x000000ffu, 0x000000ffu, 0x000000ffu, 0x000000ffu, 0x000000ffu, 0x000000ffu, 0x0000017fu, 0x008000ffu, 0x0000017fu, 0x0000017fu, 0x0000017fu, 0x00ff5678u, 0x00000000u, 0x000000ffu, 0x000000ffu, 0x00005678u}, + {0x00008000u, 0x00008000u, 0x00008000u, 0x00008000u, 0x00008000u, 0x00008000u, 0x00008080u, 0x00808000u, 0x00008080u, 0x00008080u, 0x00008080u, 0x80000001u, 0x00000000u, 0x00008000u, 0x00008000u, 0x00000001u}, + {0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x00010070u, 0x0080fff0u, 0x00010070u, 0x00010070u, 0x00010070u, 0xfff0989du, 0x00000000u, 0x0000fff0u, 0x0000fff0u, 0x0000989du}, + {0x0000ffffu, 0x0000ffffu, 0x0000ffffu, 0x0000ffffu, 0x0000ffffu, 0x0000ffffu, 0x0001007fu, 0x0080ffffu, 0x0001007fu, 0x0001007fu, 0x0001007fu, 0xffff0080u, 0x00000000u, 0x0000ffffu, 0x0000ffffu, 0x00000080u}, + {0x80000001u, 0x80000001u, 0x00000001u, 0x00000001u, 0x80000001u, 0x80000001u, 0x80000081u, 0x80800001u, 0x80000081u, 0x80000081u, 0x80000081u, 0x0001007fu, 0x00008000u, 0x80000001u, 0x80000001u, 0x8000007fu}, + {0xaf4a9e38u, 0xaf4a9e38u, 0x00009e38u, 0x00009e38u, 0xaf4a9e38u, 0xaf4a9e38u, 0xaf4a9eb8u, 0xafca9e38u, 0xaf4a9eb8u, 0xaf4a9eb8u, 0xaf4a9eb8u, 0x9e3844f4u, 0x0000af4au, 0xaf4a9e38u, 0xaf4a9e38u, 0xaf4a44f4u}, + {0xea4d8c45u, 0xea4d8c45u, 0x00008c45u, 0x00008c45u, 0xea4d8c45u, 0xea4d8c45u, 0xea4d8cc5u, 0xeacd8c45u, 0xea4d8cc5u, 0xea4d8cc5u, 0xea4d8cc5u, 0x8c455618u, 0x0000ea4du, 0xea4d8c45u, 0xea4d8c45u, 0xea4d5618u}, + {0xfd4fb563u, 0xfd4fb563u, 0x0000b563u, 0x0000b563u, 0xfd4fb563u, 0xfd4fb563u, 0xfd4fb5e3u, 0xfdcfb563u, 0xfd4fb5e3u, 0xfd4fb5e3u, 0xfd4fb5e3u, 0xb563ff00u, 0x0000fd4fu, 0xfd4fb563u, 0xfd4fb563u, 0xfd4fff00u}, + {0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000100u, 0x00ff0001u, 0x00000100u, 0x00000100u, 0x00000100u, 0x000133d9u, 0x00000000u, 0x00000001u, 0x00000001u, 0x000033d9u}, + {0x2f3bacc0u, 0x2f3bacc0u, 0x0000acc0u, 0x0000acc0u, 0x2f3bacc0u, 0x2f3bacc0u, 0x2f3badbfu, 0x303aacc0u, 0x2f3badbfu, 0x2f3badbfu, 0x2f3badbfu, 0xacc00000u, 0x00002f3bu, 0x2f3bacc0u, 0x2f3bacc0u, 0x2f3b0000u}, + {0x85f55b11u, 0x85f55b11u, 0x00005b11u, 0x00005b11u, 0x85f55b11u, 0x85f55b11u, 0x85f55c10u, 0x86f45b11u, 0x85f55c10u, 0x85f55c10u, 0x85f55c10u, 0x5b110000u, 0x000085f5u, 0x85f55b11u, 0x85f55b11u, 0x85f50000u}, + {0xff00ff00u, 0xff00ff00u, 0x0000ff00u, 0x0000ff00u, 0xff00ff00u, 0xff00ff00u, 0xff00ffffu, 0xffffff00u, 0xff00ffffu, 0xff00ffffu, 0xff00ffffu, 0xff0008e0u, 0x0000ff00u, 0xff00ff00u, 0xff00ff00u, 0xff0008e0u}, + {0xffff0000u, 0xffff0000u, 0x00000000u, 0x00000000u, 0xffff0000u, 0xffff0000u, 0xffff00ffu, 0xffffffffu, 0xffff00ffu, 0xffff00ffu, 0xffff00ffu, 0x00000000u, 0x0000ffffu, 0xffff0000u, 0xffff0000u, 0xffff0000u} +}; +constexpr const char* Names[16] = { + "mad_u32_u24", + "mad_i32_i24", + "mad_u16", + "mad_i16", + "mad_u32_u16", + "mad_i32_i16", + "sad_u8", + "sad_hi_u8", + "sad_u16", + "sad_u32", + "msad_u8", + "mad_u16 op_sel", + "mad_i16 op_sel", + "mad_i32_i16 op_sel", + "mad_u32_u16 op_sel", + "mad_i16 op_sel", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop3 integer clamp: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop3 integer clamp tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClampWide.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClampWide.cpp new file mode 100644 index 000000000..adc82c757 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClampWide.cpp @@ -0,0 +1,187 @@ +#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 +#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, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd572800a, 0x041a0d04, 0xd573800c, + 0x041a0d04, 0xd575800e, 0x04120d04, 0xd7558012, 0x041a0b04, 0xd7568013, 0x041a0b04, 0xd7528014, + 0x041a0b04, 0xd7538015, 0x041a0b04, 0xd7588016, 0x041a0b04, 0xd7598017, 0x041a0b04, 0xd54e8018, + 0x041a0b04, 0xd54f8019, 0x041a0b04, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, + 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000000u, 0x00800000u, 0x0000ffffu}, + {0x0000007fu, 0xe3718038u, 0xfffffffeu, 0xfd7c84feu}, + {0x0000007fu, 0xfffffffeu, 0x0000007fu, 0xfffffffeu}, + {0xfffffffeu, 0x00000002u, 0xb93201bfu, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3ff51543u, 0x8ca197b1u}, + {0x00000000u, 0x00000001u, 0x007fffffu, 0x29813b74u}, + {0x00000000u, 0x00000001u, 0x00ff00ffu, 0x00007fffu}, + {0x00000000u, 0x00000001u, 0x01010101u, 0xa48d7f36u}, + {0x00000000u, 0x00000001u, 0x1ef5acc3u, 0x5d780492u}, + {0x00000000u, 0x00000001u, 0x41f8bfb7u, 0x3f57a9d3u}, + {0x00000000u, 0x00000001u, 0xfefefefeu, 0x0000007fu}, + {0x00000000u, 0x00000002u, 0x00ffffffu, 0x80000000u}, + {0x00000000u, 0x00000002u, 0x7351a059u, 0x00007fffu}, + {0x00000000u, 0x00000002u, 0xcb50b50fu, 0x7fffffffu}, + {0x00000000u, 0x0000007fu, 0x0000ffffu, 0x01010101u}, + {0x00000000u, 0x0000007fu, 0x0ac13011u, 0xffff0000u}, + {0x00000000u, 0x0000007fu, 0x4b88f735u, 0x000000ffu}, + {0x00000000u, 0x0000007fu, 0xffffffffu, 0x0000fff0u}, + {0x00000000u, 0x00000080u, 0x00000000u, 0x00ffffffu}, + {0x00000000u, 0x00000080u, 0xffffffffu, 0xde945808u}, + {0x00000000u, 0x000000ffu, 0x000000ffu, 0x00000080u}, + {0x00000000u, 0x000000ffu, 0x7fffffffu, 0xca9c52c9u}, + {0x00000000u, 0x000000ffu, 0x80000001u, 0x0000007fu}, + {0x00000000u, 0x000000ffu, 0xffff0000u, 0x00007fffu}, + {0x00000000u, 0x000000ffu, 0xffffffffu, 0xffffffffu}, + {0x00000000u, 0x00007fffu, 0x01010101u, 0xcff6ab04u}, + {0x00000000u, 0x00007fffu, 0x44e9da39u, 0x63548e6fu}, + {0x00000000u, 0x00007fffu, 0x4944fa2au, 0xf5524d50u}, + {0x00000000u, 0x00007fffu, 0xa1dfdb1eu, 0xa9e05a10u}, + {0x00000000u, 0x00007fffu, 0xffff0000u, 0x01010101u}, + {0x00000000u, 0x00007fffu, 0xffffffffu, 0x0000007fu}, + {0x00000000u, 0x0000fff0u, 0x00000080u, 0x01010101u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x01000080u, 0x0080ffffu, 0x01000080u, 0x0080ffffu, 0x00000080u, 0x00000080u, 0x00800080u, 0x0001007fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xffffffffu, 0xffff8841u, 0xffffffffu, 0xffff8841u, 0x000003fbu, 0xe37183fbu, 0xffffffffu, 0xfd7c87d0u, 0x0000007fu, 0x0000fffeu, 0x00008038u, 0x0000007fu, 0x0000fffeu, 0x00008038u, 0x000001ffu, 0x007fe371u}, + {0x017d007fu, 0xffffffffu, 0x007f007fu, 0xffffffffu, 0x0000007fu, 0xffffffffu, 0x000000feu, 0xffffffffu, 0x0000007fu, 0x0000fffeu, 0x0000fffeu, 0x0000007fu, 0x0000007fu, 0x0000007fu, 0x000000ffu, 0x00007fffu}, + {0xbbf4040fu, 0x012c0227u, 0xbbf4040fu, 0x012c0227u, 0xffffffffu, 0x000002c4u, 0xb93203e6u, 0x0000012cu, 0x000001bfu, 0x0000fffeu, 0x0000fffeu, 0x00000002u, 0x00000002u, 0x000001bfu, 0xfffffffcu, 0xfffffe00u}, + {0x418116cfu, 0x8e2d993du, 0x418116cfu, 0x8e2d993du, 0x0000018cu, 0x0000018cu, 0x3ff516cfu, 0x8ca1993du, 0x00001543u, 0x00001543u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x02fdffffu, 0x2bfd3df0u, 0x02fcffffu, 0x2bfd3df0u, 0x0000027du, 0x0000027eu, 0x0080027bu, 0x29813df0u, 0x00000001u, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x02fe02fdu, 0x01ff81fcu, 0x02fd02fdu, 0x01fe81fcu, 0x000001feu, 0x000001ffu, 0x00ff02fcu, 0x000081fdu, 0x000000ffu, 0x000000ffu, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x01040105u, 0xa4907f39u, 0x01040105u, 0xa4907f39u, 0x00000004u, 0x00000004u, 0x01010104u, 0xa48d7f39u, 0x00000101u, 0x00000101u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x2176af45u, 0x5ff90713u, 0x2176af45u, 0x5ff90713u, 0x00000282u, 0x00000282u, 0x1ef5af44u, 0x5d780713u, 0x00000001u, 0x0000acc3u, 0x0000acc3u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x44a6c266u, 0x4205ac81u, 0x44a6c266u, 0x4205ac81u, 0x000002afu, 0x000002afu, 0x41f8c265u, 0x3f57ac81u, 0x00000001u, 0x0000bfb7u, 0x0000bfb7u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0xffffffffu, 0x03f70476u, 0xffffffffu, 0x03f70476u, 0x000003f8u, 0x000003f8u, 0xfeff02f5u, 0x00000476u, 0x00000001u, 0x0000fefeu, 0x0000fefeu, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x03feffffu, 0x82fb02fbu, 0x03fcffffu, 0x82fb02fbu, 0x000002fdu, 0x000002ffu, 0x010002fau, 0x800002fbu, 0x00000002u, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x00000002u, 0x00000000u, 0x00000000u}, + {0x750ca216u, 0x01bb81bau, 0x750ca216u, 0x01bb81bau, 0x000001bdu, 0x000001bdu, 0x7351a214u, 0x000081bau, 0x00000002u, 0x0000a059u, 0x0000a059u, 0x00000000u, 0x00000000u, 0x00000002u, 0x00000000u, 0x00000000u}, + {0xcd2db6eeu, 0x81dcffffu, 0xcd2db6eeu, 0x81dcffffu, 0x000001dfu, 0x000001dfu, 0xcb50b6ecu, 0x800001dcu, 0x00000002u, 0x0000b50fu, 0x0000b50fu, 0x00000000u, 0x00000000u, 0x00000002u, 0x00000000u, 0x00000000u}, + {0x027dffffu, 0x0280037eu, 0x01feffffu, 0x028002ffu, 0x000001feu, 0x0000027du, 0x000101fdu, 0x01010280u, 0x0000007fu, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x00000000u}, + {0x0c38311du, 0xffff008du, 0x0c38311du, 0xffff008du, 0x0000010cu, 0x000001f6u, 0x0ac1309eu, 0xffff012bu, 0x00003011u, 0x00003011u, 0x00000000u, 0x00000000u, 0x0000007fu, 0x0000007fu, 0x00000000u, 0x00000000u}, + {0x4d70f934u, 0x0180027fu, 0x4d70f934u, 0x0180027fu, 0x000001ffu, 0x00000267u, 0x4b88f8b5u, 0x0000027fu, 0x0000007fu, 0x0000f735u, 0x0000f735u, 0x00000000u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x00000000u}, + {0xffffffffu, 0x037dffffu, 0xffffffffu, 0x037dffffu, 0x000003fcu, 0x000003fcu, 0xffffffffu, 0x0001036du, 0x0000007fu, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x00000000u}, + {0x00800000u, 0x017fffffu, 0x00000000u, 0x00ffffffu, 0x00000000u, 0x00000080u, 0x00000000u, 0x00ffffffu, 0x00000080u, 0x00000080u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000080u, 0x00000080u}, + {0xffffffffu, 0xe2105b84u, 0xffffffffu, 0xe2105b84u, 0x000003fcu, 0x000003fcu, 0xffffffffu, 0xde945b84u, 0x00000080u, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x00000080u, 0x00000000u, 0x00000000u}, + {0x01fe01feu, 0x01fe027eu, 0x00ff01feu, 0x00ff017fu, 0x000000ffu, 0x000001feu, 0x000001feu, 0x0000017fu, 0x000000ffu, 0x000000ffu, 0x00000000u, 0x00000000u, 0x000000ffu, 0x000000ffu, 0x00000000u, 0x00000000u}, + {0x837cffffu, 0xcd195546u, 0x837cffffu, 0xcd195546u, 0x0000037cu, 0x0000047cu, 0x8000027cu, 0xca9c5546u, 0x000000ffu, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00000000u, 0x00000000u}, + {0x80800082u, 0x018001ffu, 0x80800082u, 0x00810100u, 0x00000081u, 0x0000017fu, 0x80000082u, 0x00000100u, 0x000000ffu, 0x000000ffu, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000001u, 0x0000007fu, 0x00000000u}, + {0xffff01feu, 0x02fd80feu, 0xffff01feu, 0x01fe80feu, 0x000001feu, 0x000001feu, 0xffff00ffu, 0x000081fdu, 0x000000ffu, 0x000000ffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x000000ffu}, + {0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x000003fcu, 0x000003fcu, 0xffffffffu, 0xffffffffu, 0x000000ffu, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00000000u, 0x00000000u}, + {0x02020105u, 0xd174ac82u, 0x02020105u, 0xd174ac82u, 0x00000004u, 0x00008100u, 0x0101027fu, 0xcff6ac82u, 0x00007fffu, 0x00007fffu, 0x00000000u, 0x00000000u, 0x00000101u, 0x00000101u, 0x00003fffu, 0x0000007fu}, + {0x47a0dc79u, 0x64608fd3u, 0x47a0dc79u, 0x64608fd3u, 0x00000240u, 0x000082b6u, 0x44e9db9du, 0x63548f7bu, 0x00007fffu, 0x0000da39u, 0x0000da39u, 0x00000000u, 0x00000000u, 0x00007fffu, 0x00000000u, 0x0000007fu}, + {0x4b62fbdbu, 0xf6054f65u, 0x4b62fbdbu, 0xf6054f65u, 0x000001b1u, 0x0000821du, 0x4944fc3fu, 0xf5524e03u, 0x00007fffu, 0x0000fa2au, 0x0000fa2au, 0x00000000u, 0x00000000u, 0x00007fffu, 0x0000001fu, 0x00000000u}, + {0xa415dd97u, 0xab235b4bu, 0xa415dd97u, 0xab235b4bu, 0x00000279u, 0x00008235u, 0xa1dfdc59u, 0xa9e05b53u, 0x00007fffu, 0x0000db1eu, 0x0000db1eu, 0x00000000u, 0x00000000u, 0x00007fffu, 0x00000000u, 0x00000000u}, + {0xffff01feu, 0x037f0181u, 0xffff01feu, 0x02800181u, 0x000001feu, 0x000080feu, 0xffff0080u, 0x01010280u, 0x00007fffu, 0x00007fffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00007fffu, 0x00007fffu}, + {0xffffffffu, 0x027e02fdu, 0xffffffffu, 0x027e02fdu, 0x000003fcu, 0x000082fcu, 0xffffffffu, 0x000002fdu, 0x00007fffu, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x00007fffu, 0x00000000u, 0x00000000u}, + {0x01700100u, 0x03700370u, 0x00800100u, 0x01810181u, 0x00000080u, 0x00010070u, 0x00000100u, 0x01010181u, 0x00000080u, 0x0000fff0u, 0x0000fff0u, 0x00000000u, 0x00000000u, 0x00000080u, 0x0000fff0u, 0x0000fff0u} +}; +constexpr const char* Names[16] = { + "qsad_pk_u16_u8 0", + "qsad_pk_u16_u8 1", + "mqsad_pk_u16_u8 0", + "mqsad_pk_u16_u8 1", + "mqsad_u32_u8 0", + "mqsad_u32_u8 1", + "mqsad_u32_u8 2", + "mqsad_u32_u8 3", + "max3_i16", + "max3_u16", + "min3_i16", + "min3_u16", + "med3_i16", + "med3_u16", + "alignbit_b32", + "alignbyte_b32", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop3 integer clamp wide: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + 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::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop3 integer clamp wide tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3Sad.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3Sad.cpp index 00055340c..8d4c14553 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3Sad.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3Sad.cpp @@ -90,19 +90,6 @@ void Run(AgcDriver::VulkanDevice& device) { device.WaitIdle(); } -void CheckClampRefused(AgcDriver::VulkanDevice& device) { - for (const std::uint32_t opcode : {0x15au, 0x15bu, 0x15cu, 0x171u}) { - alignas(256) const std::array code{0xd400800au | (opcode << 16u), 0x041a0b04u, 0xbf810000u}; - std::string refusal; - try { - static_cast(Compile(device, code)); - } catch (const std::exception& error) { - refusal = error.what(); - } - Require(refusal.find("VOP3 source modifiers are not implemented") != std::string::npos, "VOP3 opcode " + Hex(opcode) + " with clamp was not refused"); - } -} - std::uint32_t Sad(std::uint32_t lhs, std::uint32_t rhs, std::uint32_t fieldBits, bool masked) { const std::uint32_t fieldMask = (1u << fieldBits) - 1u; std::uint32_t sum = 0u; @@ -154,7 +141,6 @@ int main() { FillInput(); Run(*device); Check(); - CheckClampRefused(*device); std::puts("vop3 sad tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index ba44a9edd..756a18296 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -753,8 +753,26 @@ bool isNativeVop3AlignOpcode(RdnaOpcode opcode) { return opcode == RdnaOpcode::VAlignbitB32 || opcode == RdnaOpcode::VAlignbyteB32; } -bool usesSignedSaturateClamp(RdnaOpcode opcode) { - return opcode == RdnaOpcode::VAddNcI32 || opcode == RdnaOpcode::VSubNcI32; +bool usesIntegerSaturateClamp(RdnaOpcode opcode) { + switch (opcode) { + case RdnaOpcode::VAddNcI32: + case RdnaOpcode::VSubNcI32: + case RdnaOpcode::VMadU32U24: + case RdnaOpcode::VMadI32I24: + case RdnaOpcode::VMadU16: + case RdnaOpcode::VMadI16: + case RdnaOpcode::VMadU32U16: + case RdnaOpcode::VMadI32I16: + case RdnaOpcode::VSadU8: + case RdnaOpcode::VSadHiU8: + case RdnaOpcode::VSadU16: + case RdnaOpcode::VSadU32: + case RdnaOpcode::VMsadU8: + case RdnaOpcode::VQsadPkU16U8: + case RdnaOpcode::VMqsadPkU16U8: + case RdnaOpcode::VMqsadU32U8: return true; + default: return false; + } } bool isCubeOpcode(RdnaOpcode opcode) { @@ -1938,7 +1956,7 @@ bool isF32DivisionStepOpcode(RdnaOpcode opcode) { } bool supportsNativeVop3Clamp(RdnaOpcode opcode) { - return supportsNativeVop3ResultModifiers(opcode) || usesInexactClampControl(opcode) || usesSignedSaturateClamp(opcode) || + return supportsNativeVop3ResultModifiers(opcode) || usesInexactClampControl(opcode) || usesIntegerSaturateClamp(opcode) || isF32DivisionStepOpcode(opcode) || isCubeOpcode(opcode); } @@ -1956,13 +1974,13 @@ void checkNativeVop3Modifiers(RdnaOpcode opcode, bool permlane, bool carryInOut, return; } if (isNativeVop3I16TernaryOpcode(opcode)) { - if (abs != 0u || clamp != 0u || omod != 0u || neg != 0u) { + if (abs != 0u || omod != 0u || neg != 0u) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); } return; } if (isNativeVop3AlignOpcode(opcode)) { - if (abs != 0u || clamp != 0u || omod != 0u || neg != 0u) { + if (abs != 0u || omod != 0u || neg != 0u) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); } return; @@ -1980,7 +1998,7 @@ void checkNativeVop3Modifiers(RdnaOpcode opcode, bool permlane, bool carryInOut, } return; } - if (usesSignedSaturateClamp(opcode)) { + if (usesIntegerSaturateClamp(opcode)) { if (abs != 0u || opSel != 0u || omod != 0u || neg != 0u) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); } diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index b2e93e342..3bf343733 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -193,6 +193,8 @@ private: bool integer16Shift(const RdnaInstruction& inst, IrOpcode opcode, bool arithmetic); bool integer16Binary(const RdnaInstruction& inst, IrOpcode opcode, bool sign); bool integer16Mad(const RdnaInstruction& inst, bool sign, bool wide); + IrU32 saturateU16Result(const RdnaInstruction& inst, const IrU32& value, bool sign); + IrU32 wideAdd(const RdnaInstruction& inst, const IrU32& low, const IrU32& high, const IrU32& addend, bool sign); bool vAddSubNcI32(const RdnaInstruction& inst, bool subtract); bool integer16Ternary(const RdnaInstruction& inst, IrOpcode opcode, bool sign); bool vMed3I16(const RdnaInstruction& inst); diff --git a/core/shader/recompiler/Translation/src/IntegerInstructions.cpp b/core/shader/recompiler/Translation/src/IntegerInstructions.cpp index 9e9949ba3..ba6b25f67 100644 --- a/core/shader/recompiler/Translation/src/IntegerInstructions.cpp +++ b/core/shader/recompiler/Translation/src/IntegerInstructions.cpp @@ -25,6 +25,33 @@ bool TranslationContext::integer16Binary(const RdnaInstruction& inst, IrOpcode o return true; } +IrU32 TranslationContext::saturateU16Result(const RdnaInstruction& inst, const IrU32& value, bool sign) { + if (!inst.destination.clamp) { + return value; + } + if (!sign) { + return IrU32(ir.Emit(IrOpcode::UMin32, IrType::U32, {&value.Value(), &ir.Constant(0xffffu)})); + } + const IrU32 upper(ir.Emit(IrOpcode::SMin32, IrType::U32, {&value.Value(), &ir.Constant(0x7fffu)})); + return IrU32(ir.Emit(IrOpcode::SMax32, IrType::U32, {&upper.Value(), &ir.Constant(0xffff8000u)})); +} + +IrU32 TranslationContext::wideAdd(const RdnaInstruction& inst, const IrU32& low, const IrU32& high, const IrU32& addend, bool sign) { + const IrU32 sum(ir.IAdd(low.Value(), addend.Value())); + if (!inst.destination.clamp) { + return sum; + } + const IrU32 carry(ir.Select(ir.ULessThan(sum.Value(), low.Value()), ir.Constant(1u), ir.Constant(0u))); + IrU32 top(ir.IAdd(high.Value(), carry.Value())); + if (!sign) { + return IrU32(ir.Select(ir.INotEqual(top.Value(), ir.Constant(0u)), ir.Constant(0xffffffffu), sum.Value())); + } + top = IrU32(ir.IAdd(top.Value(), ir.ShiftRightArithmetic(addend.Value(), ir.Constant(31u)))); + const IrU1 overflow(ir.INotEqual(top.Value(), ir.ShiftRightArithmetic(sum.Value(), ir.Constant(31u)))); + IrValue& saturated = ir.BitwiseXor(ir.ShiftRightArithmetic(top.Value(), ir.Constant(31u)), ir.Constant(0x7fffffffu)); + return IrU32(ir.Select(overflow.Value(), saturated, sum.Value())); +} + bool TranslationContext::vMed3I16(const RdnaInstruction& inst) { const IrU32 first = readU16AsU32(sourceAt(inst, 0u), true); const IrU32 second = readU16AsU32(sourceAt(inst, 1u), true); @@ -46,11 +73,14 @@ bool TranslationContext::integer16Ternary(const RdnaInstruction& inst, IrOpcode bool TranslationContext::integer16Mad(const RdnaInstruction& inst, bool sign, bool wide) { const IrU32 lhs = readU16AsU32(sourceAt(inst, 0u), sign); const IrU32 rhs = readU16AsU32(sourceAt(inst, 1u), sign); - const IrU32 addend = wide ? readU32(sourceAt(inst, 2u)) : readU16AsU32(sourceAt(inst, 2u), sign); - const IrU32 result(ir.IAdd(ir.IMul(lhs.Value(), rhs.Value()), addend.Value())); + const IrU32 product(ir.IMul(lhs.Value(), rhs.Value())); if (wide) { + const IrU32 high(sign ? ir.ShiftRightArithmetic(product.Value(), ir.Constant(31u)) : ir.Constant(0u)); + const IrU32 result = wideAdd(inst, product, high, readU32(sourceAt(inst, 2u)), sign); writeOperand(inst.destination, &result.Value()); } else { + const IrU32 addend = readU16AsU32(sourceAt(inst, 2u), sign); + const IrU32 result = saturateU16Result(inst, IrU32(ir.IAdd(product.Value(), addend.Value())), sign); write16Bits(inst.destination, IrU32(ir.BitwiseAnd(result.Value(), ir.Constant(0xffffu)))); } return true; @@ -323,7 +353,8 @@ bool TranslationContext::integer24(const RdnaInstruction& inst, bool sign, bool result = IrU32(ir.Select(ir.INotEqual(top.Value(), expected), saturated, result.Value())); } if (addend) { - result = IrU32(ir.IAdd(result.Value(), readU32(sourceAt(inst, 2u)).Value())); + const IrU32 productHigh(inst.destination.clamp ? ir.Emit(sign ? IrOpcode::SMulHi : IrOpcode::UMulHi, IrType::U32, {&lhs.Value(), &rhs.Value()}) : ir.Constant(0u)); + result = wideAdd(inst, result, productHigh, readU32(sourceAt(inst, 2u)), sign); } writeOperand(inst.destination, &result.Value()); return true; @@ -377,7 +408,7 @@ bool TranslationContext::vSadU32(const RdnaInstruction& inst) { const IrU32 lo(ir.Emit(IrOpcode::UMin32, IrType::U32, {&lhs.Value(), &rhs.Value()})); const IrU32 hi(ir.Emit(IrOpcode::UMax32, IrType::U32, {&lhs.Value(), &rhs.Value()})); const IrU32 addend = readU32(sourceAt(inst, 2u)); - const IrU32 result(ir.IAdd(ir.ISub(hi.Value(), lo.Value()), addend.Value())); + const IrU32 result = wideAdd(inst, IrU32(ir.ISub(hi.Value(), lo.Value())), IrU32(ir.Constant(0u)), addend, false); writeOperand(inst.destination, &result.Value()); return true; } @@ -405,7 +436,7 @@ bool TranslationContext::subwordSad(const RdnaInstruction& inst, std::uint32_t f sum = IrU32(ir.ShiftLeftLogical(sum.Value(), ir.Constant(sumShift))); } const IrU32 addend = readU32(sourceAt(inst, 2u)); - const IrU32 result(ir.IAdd(sum.Value(), addend.Value())); + const IrU32 result = wideAdd(inst, sum, IrU32(ir.Constant(0u)), addend, false); writeOperand(inst.destination, &result.Value()); return true; } @@ -423,14 +454,15 @@ bool TranslationContext::vQsadU8(const RdnaInstruction& inst, bool masked, bool const std::array accumulator = readU32Pair(sourceAt(inst, 2u)); if (wide) { const std::array accumulatorHigh = readU32Pair(offsetOperand(sourceAt(inst, 2u), 2u)); - writeU32Pair(inst.destination, {IrU32(ir.IAdd(accumulator[0].Value(), sad(0u).Value())), IrU32(ir.IAdd(accumulator[1].Value(), sad(8u).Value()))}); + const IrU32 zero(ir.Constant(0u)); + writeU32Pair(inst.destination, {wideAdd(inst, sad(0u), zero, accumulator[0], false), wideAdd(inst, sad(8u), zero, accumulator[1], false)}); writeU32Pair(offsetOperand(inst.destination, 2u), - {IrU32(ir.IAdd(accumulatorHigh[0].Value(), sad(16u).Value())), IrU32(ir.IAdd(accumulatorHigh[1].Value(), sad(24u).Value()))}); + {wideAdd(inst, sad(16u), zero, accumulatorHigh[0], false), wideAdd(inst, sad(24u), zero, accumulatorHigh[1], false)}); return true; } const auto packed = [&](const IrU32& addend, std::uint32_t shift) { - const IrU32 low(ir.IAdd(addend.Value(), sad(shift).Value())); - const IrU32 high(ir.IAdd(ir.ShiftRightLogical(addend.Value(), ir.Constant(16u)), sad(shift + 8u).Value())); + const IrU32 low = saturateU16Result(inst, IrU32(ir.IAdd(ir.BitwiseAnd(addend.Value(), ir.Constant(0xffffu)), sad(shift).Value())), false); + const IrU32 high = saturateU16Result(inst, IrU32(ir.IAdd(ir.ShiftRightLogical(addend.Value(), ir.Constant(16u)), sad(shift + 8u).Value())), false); return IrU32(ir.BitwiseOr(ir.BitwiseAnd(low.Value(), ir.Constant(0xffffu)), ir.ShiftLeftLogical(high.Value(), ir.Constant(16u)))); }; writeU32Pair(inst.destination, {packed(accumulator[0], 0u), packed(accumulator[1], 16u)}); From 3299d56707f616e395dc904ac5c9e1f8709cd3f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:09:52 +0200 Subject: [PATCH 149/164] fix(tools): count function-try-block exports in progress.py Co-Authored-By: Claude --- tools/progress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/progress.py b/tools/progress.py index b78a73e89..81ceeea05 100644 --- a/tools/progress.py +++ b/tools/progress.py @@ -10,7 +10,7 @@ PRX = ROOT / "core" / "libs" / "prx" OPCODES = ROOT / "core" / "shader" / "recompiler" / "RdnaDecoder" / "include" / "RdnaDecoder" / "RdnaOpcode.hpp" ISA = Path(__file__).resolve().parent / "rdna_isa.txt" SOURCE = f'https://github.com/{os.environ.get("GITHUB_REPOSITORY", "boykopovar/AnyPS5")}/blob/main' -DEFINITION = re.compile(r"\bAPS5_VABI\s+(\w+)\s*\([^;{]*\)\s*(?:noexcept\s*)?\{") +DEFINITION = re.compile(r"\bAPS5_VABI\s+(\w+)\s*\([^;{]*\)\s*(?:noexcept\s*)?(?:try\s*)?\{") STUB = "NotImplemented_nid_no_patch" STUB_WRAPPER = re.compile(r"\bstatic\s+(?:\[\[noreturn\]\]\s+)?void\s+(\w+)\s*\([^;{]*\)\s*\{") FLAT_SEGMENTS = ("GLOBAL_", "SCRATCH_") From f3e783b6cb1971fbda219580338e85bd8da73f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:14:49 +0200 Subject: [PATCH 150/164] feat(libs): implement sceNpTrophy2UnregisterUnlockCallback Co-Authored-By: Claude --- core/libs/prx/libSceNpTrophy2/CMakeLists.txt | 18 +++++++++++++++++ core/libs/prx/libSceNpTrophy2/src/Context.cpp | 3 +-- core/libs/tests/GuestNpTrophy2.cpp | 20 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 2 +- 4 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 core/libs/tests/GuestNpTrophy2.cpp diff --git a/core/libs/prx/libSceNpTrophy2/CMakeLists.txt b/core/libs/prx/libSceNpTrophy2/CMakeLists.txt index c86b204c3..06ccd898a 100644 --- a/core/libs/prx/libSceNpTrophy2/CMakeLists.txt +++ b/core/libs/prx/libSceNpTrophy2/CMakeLists.txt @@ -11,3 +11,21 @@ target_include_directories(libSceNpTrophy2 PRIVATE ) target_link_libraries(libSceNpTrophy2 PRIVATE libc) configure_windows_unwind(libSceNpTrophy2) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_nptrophy2_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpTrophy2.cpp) + target_include_directories(guest_nptrophy2_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_nptrophy2_tests PRIVATE libSceNpTrophy2 libc) + configure_windows_unwind(guest_nptrophy2_tests) + add_test(NAME guest_nptrophy2 COMMAND guest_nptrophy2_tests) + if(WIN32) + set_tests_properties(guest_nptrophy2 PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpTrophy2/src/Context.cpp b/core/libs/prx/libSceNpTrophy2/src/Context.cpp index 097a8fe0d..e259afb73 100644 --- a/core/libs/prx/libSceNpTrophy2/src/Context.cpp +++ b/core/libs/prx/libSceNpTrophy2/src/Context.cpp @@ -31,8 +31,7 @@ int APS5_VABI sceNpTrophy2RegisterContext(int context, int handle, uint64_t opti } int APS5_VABI sceNpTrophy2UnregisterUnlockCallback() { - NotImplemented_nid_no_patch(__func__); - return 0; + return SCE_NP_TROPHY2_OK; } } diff --git a/core/libs/tests/GuestNpTrophy2.cpp b/core/libs/tests/GuestNpTrophy2.cpp new file mode 100644 index 000000000..5a286ab35 --- /dev/null +++ b/core/libs/tests/GuestNpTrophy2.cpp @@ -0,0 +1,20 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include + +extern "C" { +int APS5_VABI sceNpTrophy2RegisterUnlockCallback(void*, void*); +int APS5_VABI sceNpTrophy2UnregisterUnlockCallback(); +} + +static void Require(bool value) { if (!value) std::abort(); } + +static void APS5_VABI OnUnlock(int, int, void*) {} + +int main() { + int userdata = 0; + Require(sceNpTrophy2UnregisterUnlockCallback() == 0); + Require(sceNpTrophy2RegisterUnlockCallback(reinterpret_cast(&OnUnlock), &userdata) == 0); + Require(sceNpTrophy2UnregisterUnlockCallback() == 0); + Require(sceNpTrophy2RegisterUnlockCallback(reinterpret_cast(&OnUnlock), &userdata) == 0); + Require(sceNpTrophy2UnregisterUnlockCallback() == 0); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 02ea84a84..d01404406 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -20,6 +20,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) validates the priority and CPU affinity that the open param requests for the VideoOut service thread but does not apply them: the port's present and vblank threads are host threads, and guest priorities and affinities do not reach host scheduling - The shader recompiler [skips baryctric coordinates](../../core/shader/recompiler/Recompiler.cpp) (is not even passed to SpirvTargetOptions at row 212). - [libSceAvPlayer](../../core/libs/prx/libSceAvPlayer/Export.cpp): `sceAvPlayerSetLogCallback` accepts a callback that is never called, as the player produces no log messages, and `sceAvPlayerSetAvailableBandwidth` has no effect, as it governs HLS sources, which `sceAvPlayerAddSource` does not implement. +- [libSceNpTrophy2](../../core/libs/prx/libSceNpTrophy2/src/GameInfo.cpp): `sceNpTrophy2RegisterUnlockCallback` accepts a callback that is never called, as no trophy is ever unlocked, and `sceNpTrophy2UnregisterUnlockCallback` only returns success. - [sceAvPlayerSetTrickSpeed](../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) with a negative speed runs the clock backwards but delivers no frames; when a forward speed is set again, playback resumes from the rewound time. ### Unknown function info @@ -74,7 +75,6 @@ Throughout the project, every function at every stage either **does exactly what - [sceNpSessionSignalingGetMemoryInfo](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature - [sceNpSessionSignalingGetConnectionStatistics](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature - [sce::Np::CppWebApi::Common::String](../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x20-byte layout (32-bit reference count, buffer at 0x08, buffer size at 0x10, `LibContext*` at 0x18) comes from the module bundled with one title (PPSA21564); other SDK versions are not checked. The destructor throws for a non-null buffer: releasing it needs the `LibContext` allocator, which is not implemented -- [sceNpTrophy2UnregisterUnlockCallback](../../core/libs/prx/libSceNpTrophy2/src/Context.cpp) (libSceNpTrophy2) - unknown signature - [sceSystemServiceDisableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature - [sceSystemServiceReenableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature - [sceVoiceSetMuteFlag](../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature From ce4bdbf2eb8e2fc45b2c2099b78b45807418df5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:22:12 +0200 Subject: [PATCH 151/164] feat(libc): strtoimax and logbf Co-Authored-By: Claude --- core/libs/prx/libc/src/Math.cpp | 1 + core/libs/prx/libc/src/RuntimeSupport.cpp | 4 ++++ core/libs/tests/GuestMath.cpp | 13 +++++++++++++ 3 files changed, 18 insertions(+) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 40eda10b4..ee249e03b 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -13,6 +13,7 @@ float APS5_VABI acosf_nid_postfix(float x) { return std::acos(x); } float APS5_VABI atan2f_nid_postfix(float y, float x) { return std::atan2(y, x); } float APS5_VABI tanf_nid_postfix(float x) { return std::tan(x); } float APS5_VABI log10f_nid_postfix(float x) { return std::log10(x); } +float APS5_VABI logbf_nid_postfix(float x) { return std::logb(x); } double APS5_VABI exp2_nid_postfix(double x) { return std::exp2(x); } double APS5_VABI ldexp_nid_postfix(double x, int exponent) { return std::ldexp(x, exponent); } double APS5_VABI scalbn_nid_postfix(double x, int exponent) { return std::scalbn(x, exponent); } diff --git a/core/libs/prx/libc/src/RuntimeSupport.cpp b/core/libs/prx/libc/src/RuntimeSupport.cpp index ef00dd455..e6bf99f61 100644 --- a/core/libs/prx/libc/src/RuntimeSupport.cpp +++ b/core/libs/prx/libc/src/RuntimeSupport.cpp @@ -93,6 +93,10 @@ unsigned int APS5_VABI _Atomic_load_4_nid_postfix(volatile unsigned int* target, return GccAtomicLoad(target); } +std::intmax_t APS5_VABI strtoimax_nid_postfix(const char* str, char** endptr, int base) { + return std::strtoimax(str, endptr, base); +} + std::uintmax_t APS5_VABI strtoumax_nid_postfix(const char* str, char** endptr, int base) { return std::strtoumax(str, endptr, base); } diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 7ef93b7ec..ab9241755 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -10,6 +10,7 @@ float APS5_VABI strtof_nid_postfix(const char*, char**); long double APS5_VABI strtold_nid_postfix(const char*, char**); std::int64_t APS5_VABI strtol_nid_postfix(const char*, char**, int); std::uint64_t APS5_VABI strtoul_nid_postfix(const char*, char**, int); +std::intmax_t APS5_VABI strtoimax_nid_postfix(const char*, char**, int); int* APS5_VABI __error_nid_postfix(); float APS5_VABI fmodf_nid_postfix(float, float); float APS5_VABI asinf_nid_postfix(float); @@ -17,6 +18,7 @@ float APS5_VABI acosf_nid_postfix(float); float APS5_VABI atan2f_nid_postfix(float, float); float APS5_VABI tanf_nid_postfix(float); float APS5_VABI log10f_nid_postfix(float); +float APS5_VABI logbf_nid_postfix(float); double APS5_VABI exp2_nid_postfix(double); double APS5_VABI ldexp_nid_postfix(double, int); double APS5_VABI scalbn_nid_postfix(double, int); @@ -69,6 +71,12 @@ static void CheckIntegerConversions() { std::fprintf(stderr, "Guest strtol failed for '%s' in base %d\n", test.text, test.base); std::abort(); } + *__error_nid_postfix() = 0; + const auto maxValue = strtoimax_nid_postfix(test.text, &end, test.base); + if (maxValue != test.value || end != test.text + test.consumed || *__error_nid_postfix() != test.error) { + std::fprintf(stderr, "Guest strtoimax failed for '%s' in base %d\n", test.text, test.base); + std::abort(); + } } struct UnsignedCase { const char* text; @@ -134,6 +142,11 @@ int main() { Require(std::abs(atan2f_nid_postfix(1.f, -1.f) - 2.3561945f) < 0.000001f); Require(tanf_nid_postfix(0.f) == 0.f); Require(log10f_nid_postfix(100.f) == 2.f); + Require(logbf_nid_postfix(8.f) == 3.f && logbf_nid_postfix(-0.75f) == -1.f); + Require(logbf_nid_postfix(std::numeric_limits::denorm_min()) == -149.f); + Require(logbf_nid_postfix(0.f) == -std::numeric_limits::infinity()); + Require(logbf_nid_postfix(-std::numeric_limits::infinity()) == std::numeric_limits::infinity()); + Require(std::isnan(logbf_nid_postfix(std::numeric_limits::quiet_NaN()))); Require(exp2_nid_postfix(-3.) == 0.125); Require(ldexp_nid_postfix(0.75, 4) == 12.); Require(scalbn_nid_postfix(0.75, -2) == 0.1875); From 4136a04ba7879fef358fb36bab01ed6ba91e5221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:25:46 +0200 Subject: [PATCH 152/164] feat(ulobjmgr): register and unregister object Co-Authored-By: Claude --- core/libs/prx/ulobjmgr/CMakeLists.txt | 20 +++++++++++++++++ core/libs/prx/ulobjmgr/Export.cpp | 18 ++++++++++----- core/libs/tests/GuestUlobjmgr.cpp | 32 +++++++++++++++++++++++++++ docs/dev/TechnicalDebt.md | 2 ++ 4 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 core/libs/prx/ulobjmgr/CMakeLists.txt create mode 100644 core/libs/tests/GuestUlobjmgr.cpp diff --git a/core/libs/prx/ulobjmgr/CMakeLists.txt b/core/libs/prx/ulobjmgr/CMakeLists.txt new file mode 100644 index 000000000..84d347d1a --- /dev/null +++ b/core/libs/prx/ulobjmgr/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(ulobjmgr SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(ulobjmgr PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(ulobjmgr) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_ulobjmgr_tests ${LIBS_INCLUDE_DIR}/tests/GuestUlobjmgr.cpp) + target_include_directories(guest_ulobjmgr_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_ulobjmgr_tests PRIVATE ulobjmgr) + add_test(NAME guest_ulobjmgr COMMAND guest_ulobjmgr_tests) + if(WIN32) + set_tests_properties(guest_ulobjmgr PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/ulobjmgr/Export.cpp b/core/libs/prx/ulobjmgr/Export.cpp index 528a0dfc2..25b7f547b 100644 --- a/core/libs/prx/ulobjmgr/Export.cpp +++ b/core/libs/prx/ulobjmgr/Export.cpp @@ -3,17 +3,23 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int ULOBJMGR_EINVAL = 22; +constexpr std::uint32_t ULOBJMGR_ID_LIMIT = 0x4000; + +} + extern "C" { -int APS5_VABI _sceUlobjmgrRegisterObject(void* object) { - (void)object; - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _sceUlobjmgrRegisterObject(std::uint64_t object, std::int32_t kind, std::uint32_t* id) { + if (object == 0 || kind == 0 || id == nullptr) return ULOBJMGR_EINVAL; + *id = 0; return 0; } -int APS5_VABI _sceUlobjmgrUnregisterObject(void* object) { - (void)object; - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _sceUlobjmgrUnregisterObject(std::uint32_t id) { + if (id >= ULOBJMGR_ID_LIMIT) return ULOBJMGR_EINVAL; return 0; } diff --git a/core/libs/tests/GuestUlobjmgr.cpp b/core/libs/tests/GuestUlobjmgr.cpp new file mode 100644 index 000000000..0fd79500f --- /dev/null +++ b/core/libs/tests/GuestUlobjmgr.cpp @@ -0,0 +1,32 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +extern "C" { +int APS5_VABI _sceUlobjmgrRegisterObject(std::uint64_t object, std::int32_t kind, std::uint32_t* id); +int APS5_VABI _sceUlobjmgrUnregisterObject(std::uint32_t id); +} + +namespace { + +constexpr int einval = 22; + +void Require(bool value) { if (!value) std::abort(); } + +} + +int main() { + std::uint32_t id = 0xffffffffu; + Require(_sceUlobjmgrRegisterObject(0, 1, &id) == einval); + Require(id == 0xffffffffu); + Require(_sceUlobjmgrRegisterObject(0x1000, 0, &id) == einval); + Require(id == 0xffffffffu); + Require(_sceUlobjmgrRegisterObject(0x1000, 1, nullptr) == einval); + Require(_sceUlobjmgrRegisterObject(0x1000, -1, &id) == 0); + Require(id == 0); + + Require(_sceUlobjmgrUnregisterObject(id) == 0); + Require(_sceUlobjmgrUnregisterObject(0x3fff) == 0); + Require(_sceUlobjmgrUnregisterObject(0x4000) == einval); + Require(_sceUlobjmgrUnregisterObject(0xffffffffu) == einval); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 02ea84a84..c3205955e 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -21,6 +21,7 @@ Throughout the project, every function at every stage either **does exactly what - The shader recompiler [skips baryctric coordinates](../../core/shader/recompiler/Recompiler.cpp) (is not even passed to SpirvTargetOptions at row 212). - [libSceAvPlayer](../../core/libs/prx/libSceAvPlayer/Export.cpp): `sceAvPlayerSetLogCallback` accepts a callback that is never called, as the player produces no log messages, and `sceAvPlayerSetAvailableBandwidth` has no effect, as it governs HLS sources, which `sceAvPlayerAddSource` does not implement. - [sceAvPlayerSetTrickSpeed](../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) with a negative speed runs the clock backwards but delivers no frames; when a forward speed is set again, playback resumes from the rewound time. +- [ulobjmgr](../../core/libs/prx/ulobjmgr/Export.cpp) registers no object: `_sceUlobjmgrRegisterObject` always hands out id 0 and `_sceUlobjmgrUnregisterObject` releases nothing, as shadPS4 does ### Unknown function info @@ -80,6 +81,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceVoiceSetMuteFlag](../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature - [snwprintf_s](../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follows C11 K.3.9.1.3 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`. A runtime-constraint violation only returns a negative value: `set_constraint_handler_s` is not exported and the console's default handler is unknown. An invalid multibyte `%s` argument is copied byte by byte instead of being reported as an encoding error, as in `vswprintf` - [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) - the added `sceUltQueuePop`, `sceUltQueueTryPush`, `sceUltQueueDestroy` and `sceUltQueueDataResourcePoolDestroy` signatures are inferred from the existing push/pop and object APIs. Queue destruction is assumed to reject blocked callers with `ULT_ERROR_BUSY` and otherwise discard queued data; pool exhaustion returns `ULT_ERROR_AGAIN`. These rules, alignment checks and shared slot limits have not been confirmed on PS5 hardware or a title. Non-null queue and data-pool option parameters throw because their layouts are unknown. +- [_sceUlobjmgrRegisterObject](../../core/libs/prx/ulobjmgr/Export.cpp) (ulobjmgr) - parameters taken from shadPS4 (PS4): a nonzero 64-bit object, a nonzero 32-bit kind and a 32-bit id output; the meaning of the first two and the PS5 signature are unverified. The id bound of `_sceUlobjmgrUnregisterObject` (below 0x4000) and the raw `EINVAL` (22) return also come from shadPS4 ### Functional From 7bcdc6f5780775c78bde7aa33526e7e1f0c2cd76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:28:55 +0200 Subject: [PATCH 153/164] feat(http): name sceHttpSetInflateGZIPEnabled and sceHttpSetRequestStatusCallback Co-Authored-By: Claude --- core/libs/prx/libSceHttp/Export.cpp | 11 +++++------ core/libs/tests/GuestHttp.cpp | 5 +++++ docs/dev/TechnicalDebt.md | 1 + 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libSceHttp/Export.cpp b/core/libs/prx/libSceHttp/Export.cpp index d84ed5a4c..0bbfa9e09 100644 --- a/core/libs/prx/libSceHttp/Export.cpp +++ b/core/libs/prx/libSceHttp/Export.cpp @@ -246,15 +246,14 @@ int APS5_VABI sceHttpSetChunkedTransferEnabled(int id, int enable) { } -APS5_EXPORT("i9mhafzkEi8", sceHttpUnknown00); -int APS5_VABI sceHttpUnknown00(void) { - NotImplemented_nid_no_patch("i9mhafzkEi8"); +int APS5_VABI sceHttpSetInflateGZIPEnabled(int id, int enable) { + (void)id; + if (static_cast(enable) > 1) return ERROR_INVALID_VALUE; return 0; } -APS5_EXPORT("vO4B-42ef-k", sceHttpUnknown01); -int APS5_VABI sceHttpUnknown01(void) { - NotImplemented_nid_no_patch("vO4B-42ef-k"); +int APS5_VABI sceHttpSetRequestStatusCallback(void) { + NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/tests/GuestHttp.cpp b/core/libs/tests/GuestHttp.cpp index fa1ae4a63..697279441 100644 --- a/core/libs/tests/GuestHttp.cpp +++ b/core/libs/tests/GuestHttp.cpp @@ -7,6 +7,7 @@ extern "C" { int APS5_VABI sceHttpUriParse(SceHttpUriElement*, const char*, void*, std::size_t*, std::size_t); +int APS5_VABI sceHttpSetInflateGZIPEnabled(int, int); int APS5_VABI sceHttpUriBuild(char*, std::size_t*, std::size_t, const SceHttpUriElement*, std::uint32_t); int APS5_VABI sceHttpUriEscape(char*, std::size_t*, std::size_t, const char*); int APS5_VABI sceHttpCreateEpoll(int, HttpEpollHandle*); @@ -97,6 +98,10 @@ int main() { Require(sceHttpReadData(1, data, sizeof(data)) == network); Require(sceHttpCreateRequest2(1, "GET", "/", 0) > 0); + Require(sceHttpSetInflateGZIPEnabled(1, 0) == 0); + Require(sceHttpSetInflateGZIPEnabled(1, 1) == 0); + Require(sceHttpSetInflateGZIPEnabled(1, 2) == invalidValue); + Require(sceHttpSetInflateGZIPEnabled(1, -1) == invalidValue); Require(sceHttpsEnableOption(1, 0) == 0); Require(sceHttpsLoadCert(1, 0, nullptr, nullptr, nullptr) == 0); int httpErrno = -1; diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 02ea84a84..2eae8a29a 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -70,6 +70,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceHttpParseResponseHeader](../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature - [sceHttp2GetMemoryPoolStats](../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - unknown signature - [sceNetGetMemoryPoolStats](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceHttpSetRequestStatusCallback](../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - no reference implements it (shadPS4 only logs and returns 0); when the callback runs and what status it gets is unknown - [sce::Np::CppWebApi::Common::IntrusivePtr](../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x18-byte layout (object, deleter, `LibContext*`) and the offset of the 32-bit reference count in the pointee (0x00, 0x30 for `Vector`) come from the module bundled with one title (PPSA21564); other SDK versions are not checked. The destructor throws on the last reference of a pointer without a deleter: destroying the object needs its destructor and the `LibContext` allocator, which are not implemented - [sceNpSessionSignalingGetMemoryInfo](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature - [sceNpSessionSignalingGetConnectionStatistics](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature From cf7c5990873e6ff31fce6ce5003ed7c874efa228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:32:16 +0200 Subject: [PATCH 154/164] feat(libs): implement scePlayGoDialogGetStatus Rename the NOAMxY2EGS0 stub and return the dialog's current status without advancing it. Co-Authored-By: Claude --- core/libs/prx/libScePlayGoDialog/Export.cpp | 6 ++---- core/libs/tests/GuestPlayGoDialog.cpp | 5 +++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libScePlayGoDialog/Export.cpp b/core/libs/prx/libScePlayGoDialog/Export.cpp index 5b85d52c3..16615b02e 100644 --- a/core/libs/prx/libScePlayGoDialog/Export.cpp +++ b/core/libs/prx/libScePlayGoDialog/Export.cpp @@ -74,10 +74,8 @@ int APS5_VABI scePlayGoDialogGetResult(void* result) { return 0; } -APS5_EXPORT("NOAMxY2EGS0", scePlayGoDialogUnknown00); -int APS5_VABI scePlayGoDialogUnknown00(void) { - NotImplemented_nid_no_patch("NOAMxY2EGS0"); - return 0; +int APS5_VABI scePlayGoDialogGetStatus(void) { + return g_status.load(); } } diff --git a/core/libs/tests/GuestPlayGoDialog.cpp b/core/libs/tests/GuestPlayGoDialog.cpp index 9c313bda1..40a6a6710 100644 --- a/core/libs/tests/GuestPlayGoDialog.cpp +++ b/core/libs/tests/GuestPlayGoDialog.cpp @@ -10,6 +10,7 @@ int APS5_VABI scePlayGoDialogTerminate(void); int APS5_VABI scePlayGoDialogOpen(const void* param); int APS5_VABI scePlayGoDialogClose(void); int APS5_VABI scePlayGoDialogUpdateStatus(void); +int APS5_VABI scePlayGoDialogGetStatus(void); int APS5_VABI scePlayGoDialogGetResult(void* result); } @@ -21,6 +22,7 @@ int main() { constexpr std::int32_t paramInvalid = static_cast(0x80ED0003); constexpr std::int32_t invalidState = static_cast(0x80ED0005); + Require(scePlayGoDialogGetStatus() == 0); Require(scePlayGoDialogTerminate() == notInitialized); Require(scePlayGoDialogOpen(nullptr) == paramInvalid); Require(scePlayGoDialogGetResult(nullptr) == notInitialized); @@ -29,11 +31,13 @@ int main() { Require(scePlayGoDialogOpen(&openParam) == invalidState); Require(scePlayGoDialogInitialize() == 0); + Require(scePlayGoDialogGetStatus() == 1); Require(scePlayGoDialogInitialize() == alreadyInitialized); Require(scePlayGoDialogOpen(nullptr) == paramInvalid); Require(scePlayGoDialogUpdateStatus() == 1); Require(scePlayGoDialogOpen(&openParam) == 0); + Require(scePlayGoDialogGetStatus() == 3); Require(scePlayGoDialogUpdateStatus() == 3); PlayGoDialogResult result{}; @@ -41,6 +45,7 @@ int main() { Require(result.result == 0); Require(scePlayGoDialogClose() == 0); Require(scePlayGoDialogTerminate() == 0); + Require(scePlayGoDialogGetStatus() == 0); Require(scePlayGoDialogTerminate() == notInitialized); return 0; } From 9b9194e543c8748e4ae1069a4efc20f0f398d89f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:35:25 +0200 Subject: [PATCH 155/164] feat(libSceShare): sceShareCaptureScreenshotExtended Co-Authored-By: Claude --- core/libs/prx/libSceShare/CMakeLists.txt | 20 ++++++++++++++++ core/libs/prx/libSceShare/Export.cpp | 17 ++++++++++---- core/libs/tests/GuestShare.cpp | 30 ++++++++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 - 4 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 core/libs/prx/libSceShare/CMakeLists.txt create mode 100644 core/libs/tests/GuestShare.cpp diff --git a/core/libs/prx/libSceShare/CMakeLists.txt b/core/libs/prx/libSceShare/CMakeLists.txt new file mode 100644 index 000000000..74ac02fb2 --- /dev/null +++ b/core/libs/prx/libSceShare/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(libSceShare SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceShare PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceShare) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_share_tests ${LIBS_INCLUDE_DIR}/tests/GuestShare.cpp) + target_include_directories(guest_share_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_share_tests PRIVATE libSceShare) + add_test(NAME guest_share COMMAND guest_share_tests) + if(WIN32) + set_tests_properties(guest_share PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceShare/Export.cpp b/core/libs/prx/libSceShare/Export.cpp index b75a31a22..caabed9b9 100644 --- a/core/libs/prx/libSceShare/Export.cpp +++ b/core/libs/prx/libSceShare/Export.cpp @@ -5,6 +5,13 @@ // Capture and sharing are not emulated; policy and parameter calls are accepted. +namespace { + +constexpr std::int32_t ERROR_NOT_SUPPORTED = static_cast(0x81960007); +constexpr std::int32_t REQUEST_ID_INVALID = -1; + +} + extern "C" { int APS5_VABI sceShareCaptureScreenshot(const void* param, int32_t* req_id) { @@ -85,10 +92,12 @@ int APS5_VABI sceShareUnregisterContentEventCallback(void* callback) { return 0; } -APS5_EXPORT("GQTObcITIXI", sceShareUnknown_GQTObcITIXI); -int32_t APS5_VABI sceShareUnknown_GQTObcITIXI(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceShareCaptureScreenshotExtended(const void* extended_param, int32_t* req_id) { + (void)extended_param; + if (req_id != nullptr) { + *req_id = REQUEST_ID_INVALID; + } + return ERROR_NOT_SUPPORTED; } } diff --git a/core/libs/tests/GuestShare.cpp b/core/libs/tests/GuestShare.cpp new file mode 100644 index 000000000..353b80bb4 --- /dev/null +++ b/core/libs/tests/GuestShare.cpp @@ -0,0 +1,30 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +extern "C" { +int APS5_VABI sceShareCaptureScreenshotExtended(const void* extended_param, std::int32_t* req_id); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +} + +int main() { + constexpr std::int32_t notSupported = static_cast(0x81960007); + std::uint8_t param[64]{}; + + std::int32_t reqId = 7; + Require(sceShareCaptureScreenshotExtended(param, &reqId) == notSupported); + Require(reqId == -1); + + reqId = 7; + Require(sceShareCaptureScreenshotExtended(nullptr, &reqId) == notSupported); + Require(reqId == -1); + + Require(sceShareCaptureScreenshotExtended(param, nullptr) == notSupported); + Require(sceShareCaptureScreenshotExtended(nullptr, nullptr) == notSupported); + return 0; +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 02ea84a84..354745637 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -55,7 +55,6 @@ Throughout the project, every function at every stage either **does exactly what - [sceAudioOutSetMixLevelPadSpk](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - no source applies the level (shadPS4 only stores it); it is assumed to be a linear gain on the port's own output, and the -9 dB default (11626) is applied to every pad speaker port, including those of titles that never call the function - [sceAudioOutGetLastOutputTime](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - signature and behaviour taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [fpPS4](https://github.com/red-prig/fpPS4) reimplementations of the PS4 library, not confirmed on a PS5 title: the time is the process time at which `sceAudioOutOutput` or `sceAudioOutOutputs` accepted the port's last block, not the time the block reached the device - [AOWqIYsgVHs](../../core/libs/prx/libSceContentExport/Export.cpp) (libSceContentExport) - unknown name, signature -- [GQTObcITIXI](../../core/libs/prx/libSceShare/Export.cpp) (libSceShare) - unknown name, signature - [scePsmlMfsrGetContextBufferRequirement1100, scePsmlMfsrCreateContext1100, scePsmlMfsrGetDispatchMfsrPacket1100](../../core/libs/prx/libScePsml_debug/Export.cpp) (libScePsml_debug) - argument layouts unknown; only 0x8A810001 (not initialized) is returned - [BnMAMrsfVWo](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libc) - unknown name, signature - [scePngEncEncode](../../core/libs/prx/libScePngEnc/Export.cpp) (libScePngEnc) - whether rows may stay unfiltered when `filter_type` names a subset of the filters is unknown; as in shadPS4, they may only for 0 and for the all-filters mask From e923bea317230767aa468f7ddfc22d5650881a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:41:59 +0200 Subject: [PATCH 156/164] feat(savedata): implement the PS4 transfer mount and directory search Rename the two unknown libSceSaveData.native stubs to sceSaveDataTransferringMountPs4 (RjMlsR8EXrw) and sceSaveDataDirNameSearchPs4 (X4MYzukPc3g). There is no PS4 save area: the mount returns NOT_FOUND like sceSaveDataTransferringMount, and the search reports zero hits. Co-Authored-By: Claude --- .../prx/libSceSaveData.native/CMakeLists.txt | 10 ++ .../libs/prx/libSceSaveData.native/Export.cpp | 26 ++++-- core/libs/tests/GuestSaveDataPs4.cpp | 93 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 core/libs/tests/GuestSaveDataPs4.cpp diff --git a/core/libs/prx/libSceSaveData.native/CMakeLists.txt b/core/libs/prx/libSceSaveData.native/CMakeLists.txt index 42ebeecd4..163948431 100644 --- a/core/libs/prx/libSceSaveData.native/CMakeLists.txt +++ b/core/libs/prx/libSceSaveData.native/CMakeLists.txt @@ -17,4 +17,14 @@ if(BUILD_TESTING) set_tests_properties(guest_savedata_delete PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_savedata_ps4_tests ${LIBS_INCLUDE_DIR}/tests/GuestSaveDataPs4.cpp) + target_include_directories(guest_savedata_ps4_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_savedata_ps4_tests PRIVATE libSceSaveData.native libc) + configure_windows_unwind(guest_savedata_ps4_tests) + add_test(NAME guest_savedata_ps4 COMMAND guest_savedata_ps4_tests) + if(WIN32) + set_tests_properties(guest_savedata_ps4 PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceSaveData.native/Export.cpp b/core/libs/prx/libSceSaveData.native/Export.cpp index 5c262309c..f0b798f5d 100644 --- a/core/libs/prx/libSceSaveData.native/Export.cpp +++ b/core/libs/prx/libSceSaveData.native/Export.cpp @@ -699,16 +699,24 @@ int APS5_VABI sceSaveDataUmount2(uint32_t mode, const SaveDataMountPoint* mount_ return rc; } - -APS5_EXPORT("RjMlsR8EXrw", sceSaveDataUnknown00); -int APS5_VABI sceSaveDataUnknown00(void) { - NotImplemented_nid_no_patch("RjMlsR8EXrw"); - return 0; +int APS5_VABI sceSaveDataTransferringMountPs4(const SaveDataTransferringMount* mount, SaveDataMountResult* mount_result) { + const int rc = transferringMount(mount, mount_result); + SAVEDATA_TRACE("transferringMountPs4 -> 0x%08x", static_cast(rc)); + return rc; } -APS5_EXPORT("X4MYzukPc3g", sceSaveDataUnknown01); -int APS5_VABI sceSaveDataUnknown01(void) { - NotImplemented_nid_no_patch("X4MYzukPc3g"); - return 0; +static int dirNameSearchPs4(const SaveDataDirNameSearchCond* cond, SaveDataDirNameSearchResult* result) { + if (cond == nullptr || result == nullptr) { + throw std::runtime_error("sceSaveDataDirNameSearchPs4: null argument"); + } + result->hit_num = 0; + result->set_num = 0; + return SAVE_DATA_OK; +} + +int APS5_VABI sceSaveDataDirNameSearchPs4(const SaveDataDirNameSearchCond* cond, SaveDataDirNameSearchResult* result) { + const int rc = dirNameSearchPs4(cond, result); + SAVEDATA_TRACE("dirNameSearchPs4 user=%d -> 0x%08x", cond->user_id, static_cast(rc)); + return rc; } } diff --git a/core/libs/tests/GuestSaveDataPs4.cpp b/core/libs/tests/GuestSaveDataPs4.cpp new file mode 100644 index 000000000..39e7b14d1 --- /dev/null +++ b/core/libs/tests/GuestSaveDataPs4.cpp @@ -0,0 +1,93 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include + +extern "C" int APS5_VABI sceSaveDataTransferringMountPs4(const SaveDataTransferringMount*, SaveDataMountResult*); +extern "C" int APS5_VABI sceSaveDataDirNameSearchPs4(const SaveDataDirNameSearchCond*, SaveDataDirNameSearchResult*); +extern "C" int APS5_VABI sceSaveDataDirNameSearch(const SaveDataDirNameSearchCond*, SaveDataDirNameSearchResult*); + +namespace { + +constexpr int SaveDataErrorNotFound = -2137063416; + +int failures = 0; + +void Check(bool condition, const std::string& what) { + if (condition) return; + std::fprintf(stderr, "savedata ps4 check failed: %s\n", what.c_str()); + ++failures; +} + +template +bool ThrowsRuntimeError(TFunction function) { + try { + function(); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +void TestTransferringMountPs4() { + SceSaveDataTitleId title{}; + std::strcpy(title.data, "CUSA00001"); + SceSaveDataDirName dir{}; + std::strcpy(dir.data, "kept"); + SaveDataTransferringMount mount{}; + mount.user_id = 1; + mount.title_id = &title; + mount.dir_name = &dir; + SaveDataMountResult result{}; + std::memset(&result, 0xAA, sizeof(result)); + Check(sceSaveDataTransferringMountPs4(&mount, &result) == SaveDataErrorNotFound, "TransferringMountPs4 returns NOT_FOUND"); + Check(result.mount_point.data[0] == '\0', "TransferringMountPs4 reports no mount point"); +} + +void TestDirNameSearchPs4() { + SceSaveDataDirName names[4]{}; + std::memset(names, 0x5A, sizeof(names)); + SaveDataDirNameSearchCond cond{}; + cond.user_id = 1; + SaveDataDirNameSearchResult result{}; + result.hit_num = 7; + result.set_num = 7; + result.dir_names = names; + result.dir_names_num = 4; + + Check(sceSaveDataDirNameSearch(&cond, &result) == 0 && result.hit_num == 1 && result.set_num == 1, "the PS5 search finds the PS5 save"); + + std::memset(names, 0x5A, sizeof(names)); + result.hit_num = 7; + result.set_num = 7; + Check(sceSaveDataDirNameSearchPs4(&cond, &result) == 0, "DirNameSearchPs4 succeeds"); + Check(result.hit_num == 0, "DirNameSearchPs4 writes zero hits"); + Check(result.set_num == 0, "DirNameSearchPs4 writes zero set entries"); + Check(static_cast(names[0].data[0]) == 0x5A, "DirNameSearchPs4 leaves the name buffer untouched"); + + Check(ThrowsRuntimeError([&] { sceSaveDataDirNameSearchPs4(nullptr, &result); }), "null cond throws"); + Check(ThrowsRuntimeError([&] { sceSaveDataDirNameSearchPs4(&cond, nullptr); }), "null result throws"); +} + +} + +int main() { + const auto root = std::filesystem::temp_directory_path() / ("anyps5-savedata-ps4-" + std::to_string(std::random_device{}())); + std::filesystem::create_directories(root / "_sd" / "kept"); + const auto previous = std::filesystem::current_path(); + std::filesystem::current_path(root); + + TestTransferringMountPs4(); + TestDirNameSearchPs4(); + + std::filesystem::current_path(previous); + std::error_code error; + std::filesystem::remove_all(root, error); + if (failures != 0) return 1; + std::printf("savedata ps4 tests passed\n"); + return 0; +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 02ea84a84..caecaa8c2 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -31,6 +31,7 @@ Throughout the project, every function at every stage either **does exactly what - [AMPR markers](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - signatures taken from the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation, not confirmed on a title: `sceAmprCommandBufferSetMarkerWithColor` takes the color by pointer, the push and measure variants by value. Colors are not recorded - [sceFontGetRenderScaledKerning](../../core/libs/prx/libSceFont/src/Render.cpp) (libSceFont) - arguments, the cleared output and the bound renderer check taken from `cellFontGetRenderScaledKerning` in [RPCS3](https://github.com/RPCS3/rpcs3/blob/master/rpcs3/Emu/Cell/Modules/cellFont.cpp) (PS3 predecessor of the library), not confirmed on a title. The kerning is the `sceFontGetKerning` one at the render scale - [sceFontGetPixelResolution](../../core/libs/prx/libSceFont/src/Library.cpp) (libSceFont) - signature taken from the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json), not confirmed on a title. The value is the one the library's driver reports (64 for FreeType); the argument checks and the cleared output follow `sceFontGetLibrary` +- [sceSaveDataTransferringMountPs4, sceSaveDataDirNameSearchPs4](../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - arguments assumed to be those of `sceSaveDataTransferringMount` and `sceSaveDataDirNameSearch`: KytyPS5 binds both NIDs to the PS5 functions, and [prosper](https://github.com/mattias800/prosper) captured the same result layout on a title for the search. There is no PS4 save area, so the mount returns `NOT_FOUND` and the search reports zero hits - [sceAgcSetSubmitMode](../../core/libs/prx/libSceAgc/Misc/src/Suspend.cpp) (libSceAgc) - mode values unknown; only 0 is accepted - [zARR5aCmkoY](../../core/libs/prx/libSceAgc/DcbFlow/src/Control.cpp) (libSceAgc) - unknown name, signature - [qj7QZpgr9Uw](../../core/libs/prx/libSceAgc/DcbState/src/ContextState.cpp) (libSceAgc) - unknown name From fbf0099790a00123e19fdb2ace5eed1972aad4f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:49:33 +0200 Subject: [PATCH 157/164] feat(libc): implement std::_Xrange_error and the range_error type Co-Authored-By: Claude --- core/libs/prx/libc/Export.cpp | 6 ------ core/libs/prx/libc/src/exception/StandardExceptions.cpp | 7 +++++++ core/libs/tests/ExceptionRuntime.cpp | 8 ++++++++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libc/Export.cpp b/core/libs/prx/libc/Export.cpp index 17128de2e..5e68aa41a 100644 --- a/core/libs/prx/libc/Export.cpp +++ b/core/libs/prx/libc/Export.cpp @@ -30,12 +30,6 @@ extern "C" { return 0; } -APS5_EXPORT("Pu0Ecyk-7FU", libcUnknown_Pu0Ecyk_M7FU); -int APS5_VABI libcUnknown_Pu0Ecyk_M7FU() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - // Dead import of Cyberpunk 2077 (PPSA04029): no call sites, but the // Windows loader resolves imports strictly, so it must be present. APS5_EXPORT("u2tMGOLaqnE", libcUnknown_u2tMGOLaqnE); diff --git a/core/libs/prx/libc/src/exception/StandardExceptions.cpp b/core/libs/prx/libc/src/exception/StandardExceptions.cpp index 353538a5f..16da81ad5 100644 --- a/core/libs/prx/libc/src/exception/StandardExceptions.cpp +++ b/core/libs/prx/libc/src/exception/StandardExceptions.cpp @@ -267,6 +267,12 @@ void APS5_VABI _ZNSt11regex_errorC1ERKS__nid_postfix(LibcException::ExceptionObj void APS5_VABI _ZNSt11regex_errorC2ERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source) { LibcException::Copy(self, source, _ZTVSt11regex_error_nid_postfix); } LibcException::ExceptionObject* APS5_VABI _ZNSt11regex_erroraSERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source) { return LibcException::Assign(self, source); } +LibcException::TypeRecord _ZTISt11range_error_nid_postfix {LibcException::SingleTypeVtable + 2, "St11range_error", &_ZTISt13runtime_error_nid_postfix}; +LibcException::ExceptionVtable _ZTVSt11range_error_nid_postfix {0, &_ZTISt11range_error_nid_postfix, LibcException::DestroyMessage, LibcException::DeleteMessage, LibcException::MessageWhat}; +void APS5_VABI _ZNSt11range_errorD1Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DestroyMessage(self); } +void APS5_VABI _ZNSt11range_errorD2Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DestroyMessage(self); } +void APS5_VABI _ZNSt11range_errorD0Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DeleteMessage(self); } + LibcException::TypeRecord _ZTINSt8ios_base7failureE_nid_postfix {LibcException::SingleTypeVtable + 2, "NSt8ios_base7failureE", &_ZTISt12system_error_nid_postfix}; LibcException::ExceptionVtable _ZTVNSt8ios_base7failureE_nid_postfix {0, &_ZTINSt8ios_base7failureE_nid_postfix, LibcException::DestroyMessage, LibcException::DeleteMessage, LibcException::MessageWhat}; void APS5_VABI _ZNSt8ios_base7failureD1Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DestroyMessage(self); } @@ -281,6 +287,7 @@ const char* APS5_VABI _ZNKSt8ios_base7failure4whatEv_nid_postfix(const LibcExcep [[noreturn]] void APS5_VABI _ZSt14_Xout_of_rangePKc_nid_postfix(const char* message) { LibcException::ThrowMessage(_ZTVSt12out_of_range_nid_postfix, message); } [[noreturn]] void APS5_VABI _ZSt14_Xlength_errorPKc_nid_postfix(const char* message) { LibcException::ThrowMessage(_ZTVSt12length_error_nid_postfix, message); } [[noreturn]] void APS5_VABI _ZSt18_Xinvalid_argumentPKc_nid_postfix(const char* message) { LibcException::ThrowMessage(_ZTVSt16invalid_argument_nid_postfix, message); } +[[noreturn]] void APS5_VABI _ZSt13_Xrange_errorPKc_nid_postfix(const char* message) { LibcException::ThrowMessage(_ZTVSt11range_error_nid_postfix, message); } [[noreturn]] void APS5_VABI _ZSt19_Xbad_function_callv_nid_postfix() { LibcException::ThrowPlain(_ZTVSt17bad_function_call_nid_postfix); } [[noreturn]] void APS5_VABI _ZSt13_Xregex_errorNSt15regex_constants10error_typeE_nid_postfix(std::regex_constants::error_type code) { struct RegexObject { LibcException::ExceptionObject base; std::regex_constants::error_type code; }; diff --git a/core/libs/tests/ExceptionRuntime.cpp b/core/libs/tests/ExceptionRuntime.cpp index 2e50cd0ba..bb8df2c16 100644 --- a/core/libs/tests/ExceptionRuntime.cpp +++ b/core/libs/tests/ExceptionRuntime.cpp @@ -17,6 +17,7 @@ extern "C" void __cxa_decrement_exception_refcount_nid_postfix(void*); extern "C" void __cxa_rethrow_primary_exception_nid_postfix(void*); extern "C" [[noreturn]] void _ZSt14_Xout_of_rangePKc_nid_postfix(const char*); +extern "C" [[noreturn]] void _ZSt13_Xrange_errorPKc_nid_postfix(const char*); extern "C" [[noreturn]] void __cxa_bad_cast_nid_postfix(); extern "C" [[noreturn]] void _ZNKSt9exception6_RaiseEv_nid_postfix(const void*); extern "C" void* __cxa_vec_new3_nid_postfix(std::size_t, std::size_t, std::size_t, void(*)(void*), void(*)(void*), void*(*)(std::size_t), void(*)(void*, std::size_t)); @@ -190,6 +191,13 @@ int main() { try { throw Virtual(); } catch (Base& value) { assert(value.value == 7); } try { _ZSt14_Xout_of_rangePKc_nid_postfix("test message"); } catch (const std::logic_error& value) { assert(std::strcmp(value.what(), "test message") == 0); } + try { _ZSt13_Xrange_errorPKc_nid_postfix("range message"); } + catch (const std::range_error& value) { if (std::strcmp(value.what(), "range message") != 0) std::abort(); } + try { _ZSt13_Xrange_errorPKc_nid_postfix(nullptr); } + catch (const std::runtime_error& value) { if (std::strcmp(value.what(), "") != 0) std::abort(); } + try { _ZSt13_Xrange_errorPKc_nid_postfix("not a logic error"); } + catch (const std::logic_error&) { std::abort(); } + catch (const std::range_error&) {} try { __cxa_bad_cast_nid_postfix(); } catch (const std::exception& value) { assert(value.what() != nullptr); } try { _ZNKSt9exception6_RaiseEv_nid_postfix(nullptr); assert(false); } From afe2b14a846b69e4ec4645cbd0f4321517bc20a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:53:12 +0200 Subject: [PATCH 158/164] feat(libc): implement div Co-Authored-By: Claude --- core/libs/prx/libc/Export.cpp | 6 ------ core/libs/prx/libc/src/Strings.cpp | 4 ++++ core/libs/tests/GuestMath.cpp | 9 +++++++++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libc/Export.cpp b/core/libs/prx/libc/Export.cpp index 17128de2e..e1eaa4de0 100644 --- a/core/libs/prx/libc/Export.cpp +++ b/core/libs/prx/libc/Export.cpp @@ -70,12 +70,6 @@ int APS5_VABI libcCyberUnknown19(void) { } -APS5_EXPORT("2gbcltk3swE", libcCyberUnknown00); -std::uint64_t APS5_VABI libcCyberUnknown00(void) { - NotImplemented_nid_no_patch("2gbcltk3swE"); - return 0; -} - APS5_EXPORT("DiGVep5yB5w", libcCyberUnknown01); std::uint64_t APS5_VABI libcCyberUnknown01(void) { NotImplemented_nid_no_patch("DiGVep5yB5w"); diff --git a/core/libs/prx/libc/src/Strings.cpp b/core/libs/prx/libc/src/Strings.cpp index 5bcad4d2a..b6264e15f 100644 --- a/core/libs/prx/libc/src/Strings.cpp +++ b/core/libs/prx/libc/src/Strings.cpp @@ -114,6 +114,10 @@ int APS5_VABI atoi_nid_postfix(const char* str) { return std::atoi(str); } +std::div_t APS5_VABI div_nid_postfix(int numerator, int denominator) { + return std::div(numerator, denominator); +} + const wchar_t* APS5_VABI wmemchr_nid_postfix(const wchar_t* s, wchar_t c, size_t n) { return std::wmemchr(s, c, n); } diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 7ef93b7ec..0a00a3de3 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -24,6 +24,7 @@ float APS5_VABI scalbnf_nid_postfix(float, int); double APS5_VABI frexp_nid_postfix(double, int*); float APS5_VABI frexpf_nid_postfix(float, int*); std::int64_t APS5_VABI lround_nid_postfix(double); +std::div_t APS5_VABI div_nid_postfix(int, int); std::int64_t APS5_VABI lroundf_nid_postfix(float); std::int64_t APS5_VABI llround_nid_postfix(double); int APS5_VABI __isfinitef_nid_postfix(float); @@ -156,4 +157,12 @@ int main() { Require(__isnormalf_nid_postfix(std::numeric_limits::denorm_min()) == 0); Require(__isnormal_nid_postfix(1.) == 1 && __isnormal_nid_postfix(0.) == 0); Require(__isnormal_nid_postfix(std::numeric_limits::denorm_min()) == 0); + const auto quotient = div_nid_postfix(7, 2); + Require(quotient.quot == 3 && quotient.rem == 1); + const auto negativeNumerator = div_nid_postfix(-7, 2); + Require(negativeNumerator.quot == -3 && negativeNumerator.rem == -1); + const auto negativeDenominator = div_nid_postfix(7, -2); + Require(negativeDenominator.quot == -3 && negativeDenominator.rem == 1); + const auto minimum = div_nid_postfix(std::numeric_limits::min(), 10); + Require(minimum.quot == -214748364 && minimum.rem == -8); } From 85f16c56ee1e3610bc8bdf7ca64a57c1ed2f6d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:55:41 +0200 Subject: [PATCH 159/164] fix(libkernel): name five sceCoredump stubs from their NIDs Co-Authored-By: Claude --- core/libs/prx/libkernel/System/src/Coredump.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/core/libs/prx/libkernel/System/src/Coredump.cpp b/core/libs/prx/libkernel/System/src/Coredump.cpp index 710d956cd..27efa3e65 100644 --- a/core/libs/prx/libkernel/System/src/Coredump.cpp +++ b/core/libs/prx/libkernel/System/src/Coredump.cpp @@ -31,8 +31,7 @@ int APS5_VABI sceKernelDebugWriteCppExceptionInfo(const void* exception, uint64_ } -APS5_EXPORT("5nc2gdLNsok", sceCoredumpUnknown00); -int APS5_VABI sceCoredumpUnknown00(void) { +int APS5_VABI sceCoredumpAttachUserFile(void) { NotImplemented_nid_no_patch("5nc2gdLNsok"); return 0; } @@ -43,26 +42,22 @@ int APS5_VABI sceCoredumpUnknown01(void) { return 0; } -APS5_EXPORT("MEJ7tc7ThwM", sceCoredumpUnknown02); -int APS5_VABI sceCoredumpUnknown02(void) { +int APS5_VABI sceCoredumpAttachMemoryRegionAsUserFile(void) { NotImplemented_nid_no_patch("MEJ7tc7ThwM"); return 0; } -APS5_EXPORT("Uxqkdta7wEg", sceCoredumpUnknown03); -int APS5_VABI sceCoredumpUnknown03(void) { +int APS5_VABI sceCoredumpSetUserDataType(void) { NotImplemented_nid_no_patch("Uxqkdta7wEg"); return 0; } -APS5_EXPORT("dei8oUx6DbU", sceCoredumpUnknown04); -int APS5_VABI sceCoredumpUnknown04(void) { +int APS5_VABI sceCoredumpDebugTextOut(void) { NotImplemented_nid_no_patch("dei8oUx6DbU"); return 0; } -APS5_EXPORT("kK0DUW1Ukgc", sceCoredumpUnknown05); -int APS5_VABI sceCoredumpUnknown05(void) { +int APS5_VABI sceCoredumpGetStopInfoCpu(void) { NotImplemented_nid_no_patch("kK0DUW1Ukgc"); return 0; } From 6d81cce0dbd628483c6ac4f4e4a47b6d454dec29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 19:58:20 +0200 Subject: [PATCH 160/164] fix(libkernel): name three Unimplemented stubs from their NIDs Co-Authored-By: Claude --- core/libs/prx/libkernel/Unimplemented.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libkernel/Unimplemented.cpp b/core/libs/prx/libkernel/Unimplemented.cpp index c969fc55e..96466573a 100644 --- a/core/libs/prx/libkernel/Unimplemented.cpp +++ b/core/libs/prx/libkernel/Unimplemented.cpp @@ -10,14 +10,12 @@ int APS5_VABI sceCoredumpWriteUserData() { return 0; } -APS5_EXPORT("vNe1w4diLCs", sceKernelUnknown00); -int APS5_VABI sceKernelUnknown00(void) { +int APS5_VABI __tls_get_addr_nid_postfix(void) { NotImplemented_nid_no_patch("vNe1w4diLCs"); return 0; } -APS5_EXPORT("teiItL2boFw", sceKernelUnknown01); -int APS5_VABI sceKernelUnknown01(void) { +int APS5_VABI sceKernelReleaseFlexibleMemory(void) { NotImplemented_nid_no_patch("teiItL2boFw"); return 0; } @@ -25,8 +23,7 @@ int APS5_VABI sceKernelUnknown01(void) { // Canonical lib is libScePosix (dead import of Cyberpunk 2077): 35 of its // 36 sibling imports resolve to libkernel, and libScePosix is not in the // game's NEEDED list so only a NEEDED module can satisfy the loader here. -APS5_EXPORT("0D4-FVvEikw", sceKernelUnknown02); -int APS5_VABI sceKernelUnknown02(void) { +int APS5_VABI pthread_cancel_nid_postfix(void) { NotImplemented_nid_no_patch("0D4-FVvEikw"); return 0; } From 357fd7e8063a7fa6263e7a5b8563ab911eca108c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 20:01:01 +0200 Subject: [PATCH 161/164] fix(agc): name nine Unimplemented stubs from their NIDs Co-Authored-By: Claude --- core/libs/prx/libSceAgc/Unimplemented.cpp | 27 ++++++++--------------- 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/core/libs/prx/libSceAgc/Unimplemented.cpp b/core/libs/prx/libSceAgc/Unimplemented.cpp index e53a08cb3..94217d10f 100644 --- a/core/libs/prx/libSceAgc/Unimplemented.cpp +++ b/core/libs/prx/libSceAgc/Unimplemented.cpp @@ -84,8 +84,7 @@ int APS5_VABI sceAgcSetAmmSemaphoreMemory() { return 0; } -APS5_EXPORT("6nths4DHNrs", sceAgcUnknown_6nths4DHNrs); -int APS5_VABI sceAgcUnknown_6nths4DHNrs() { +int APS5_VABI sceAgcCbMemsetExclusive() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -96,38 +95,32 @@ int APS5_VABI sceAgcUnknown_7Wa3aeJgeVU() { return 0; } -APS5_EXPORT("EJBA4dbmvfg", sceAgcUnknown_EJBA4dbmvfg); -int APS5_VABI sceAgcUnknown_EJBA4dbmvfg() { +int APS5_VABI sceAgcAsyncWriteDataPatchSetDst() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("ICkECTBxrMw", sceAgcUnknown_ICkECTBxrMw); -int APS5_VABI sceAgcUnknown_ICkECTBxrMw() { +int APS5_VABI sceAgcGetGsPrimPayload() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("RTpj-tIlvZc", sceAgcUnknown_RTpj_MtIlvZc); -int APS5_VABI sceAgcUnknown_RTpj_MtIlvZc() { +int APS5_VABI sceAgcSetShaderInstrumentation() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("SwI6QxqwAC0", sceAgcUnknown_SwI6QxqwAC0); -int APS5_VABI sceAgcUnknown_SwI6QxqwAC0() { +int APS5_VABI sceAgcGetShaderInstrumentation() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("d4NZIlguzv0", sceAgcUnknown_d4NZIlguzv0); -int APS5_VABI sceAgcUnknown_d4NZIlguzv0() { +int APS5_VABI sceAgcAsyncWriteDataPatchSetAddressOrOffset() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("eAy8eGNsCuU", sceAgcUnknown_eAy8eGNsCuU); -int APS5_VABI sceAgcUnknown_eAy8eGNsCuU() { +int APS5_VABI sceAgcWriteDataPatchSetCachePolicy() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -138,14 +131,12 @@ int APS5_VABI sceAgcUnknown_rP5xLdOf26k() { return 0; } -APS5_EXPORT("tmy-+rBpspY", sceAgcUnknown_tmy_M_PrBpspY); -int APS5_VABI sceAgcUnknown_tmy_M_PrBpspY() { +int APS5_VABI sceAgcWriteDataPatchSetDst() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("y5K5tPktiL8", sceAgcUnknown_y5K5tPktiL8); -int APS5_VABI sceAgcUnknown_y5K5tPktiL8() { +int APS5_VABI sceAgcAsyncWriteDataPatchSetCachePolicy() { NotImplemented_nid_no_patch(__func__); return 0; } From ea65b7e5bec315ef7bf99ffe1894f38a06548aab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 20:03:12 +0200 Subject: [PATCH 162/164] fix(agcdriver): name the four SubmitValidation stubs from their NIDs Co-Authored-By: Claude --- core/libs/prx/libSceAgcDriver/Unimplemented.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Unimplemented.cpp b/core/libs/prx/libSceAgcDriver/Unimplemented.cpp index 8166a546f..775fcced3 100644 --- a/core/libs/prx/libSceAgcDriver/Unimplemented.cpp +++ b/core/libs/prx/libSceAgcDriver/Unimplemented.cpp @@ -85,26 +85,22 @@ int APS5_VABI sceAgcDriverUnregisterWorkloadStream() { return 0; } -APS5_EXPORT("+iAOE3jCnkk", sceAgcDriverUnknown__PiAOE3jCnkk); -int APS5_VABI sceAgcDriverUnknown__PiAOE3jCnkk() { +int APS5_VABI sceAgcDriverSetSubmitValidationMode() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("ICaGtkEIXTk", sceAgcDriverUnknown_ICaGtkEIXTk); -int APS5_VABI sceAgcDriverUnknown_ICaGtkEIXTk() { +int APS5_VABI sceAgcDriverGetSubmitValidationConfig() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("WHIOMbb+iIU", sceAgcDriverUnknown_WHIOMbb_PiIU); -int APS5_VABI sceAgcDriverUnknown_WHIOMbb_PiIU() { +int APS5_VABI sceAgcDriverGetSubmitValidationMode() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("chJWZcNSzjk", sceAgcDriverUnknown_chJWZcNSzjk); -int APS5_VABI sceAgcDriverUnknown_chJWZcNSzjk() { +int APS5_VABI sceAgcDriverSetSubmitValidationConfig() { NotImplemented_nid_no_patch(__func__); return 0; } From 68b71c971a94c5a2639f8f91d07abd7c456d2a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Mon, 5 Oct 2026 20:10:34 +0200 Subject: [PATCH 163/164] feat(libc): implement _FDtest, _FInf and _FNan Co-Authored-By: Claude --- core/libs/prx/libc/src/Math.cpp | 18 ++++++++++++++++++ core/libs/tests/GuestMath.cpp | 26 ++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 40eda10b4..425a9edaf 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include "prx/libc/include/General.hpp" @@ -78,6 +80,22 @@ double APS5_VABI tanh_nid_postfix(double x) { return std::tanh(x); } float APS5_VABI tanhf_nid_postfix(float x) { return std::tanh(x); } float APS5_VABI _FSinh_nid_postfix(float x, float y) { return y * std::sinh(x); } float APS5_VABI _FCosh_nid_postfix(float x, float y) { return y * std::cosh(x); } + +struct alignas(16) LibcFloatConstant { std::uint32_t bits[4]; }; +LibcFloatConstant _FInf_nid_postfix {{0x7f800000u, 0, 0, 0}}; +LibcFloatConstant _FNan_nid_postfix {{0x7fc00000u, 0, 0, 0}}; + +short APS5_VABI _FDtest_nid_postfix(const float* value) { + constexpr short Denormal = -2, Finite = -1, Zero = 0, Infinite = 1, NotANumber = 2; + if (value == nullptr) throw std::invalid_argument("_FDtest: null value"); + std::uint32_t bits; + std::memcpy(&bits, value, sizeof(bits)); + const auto exponent = bits & 0x7f800000u; + const auto fraction = bits & 0x007fffffu; + if (exponent == 0x7f800000u) return fraction != 0 ? NotANumber : Infinite; + if (exponent == 0) return fraction != 0 ? Denormal : Zero; + return Finite; +} int APS5_VABI __isnanf_nid_postfix(float x) { return std::isnan(x) ? 1 : 0; } int APS5_VABI __signbitf_nid_postfix(float x) { return std::signbit(x) ? 1 : 0; } diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 7ef93b7ec..8b8e10729 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include extern "C" { double APS5_VABI atof_nid_postfix(const char*); @@ -11,6 +12,10 @@ long double APS5_VABI strtold_nid_postfix(const char*, char**); std::int64_t APS5_VABI strtol_nid_postfix(const char*, char**, int); std::uint64_t APS5_VABI strtoul_nid_postfix(const char*, char**, int); int* APS5_VABI __error_nid_postfix(); +struct LibcFloatConstant { std::uint32_t bits[4]; }; +extern LibcFloatConstant _FInf_nid_postfix; +extern LibcFloatConstant _FNan_nid_postfix; +short APS5_VABI _FDtest_nid_postfix(const float*); float APS5_VABI fmodf_nid_postfix(float, float); float APS5_VABI asinf_nid_postfix(float); float APS5_VABI acosf_nid_postfix(float); @@ -113,7 +118,28 @@ static void CheckIntegerConversions() { *__error_nid_postfix() = 0; } +static void CheckFloatClassification() { + Require(_FInf_nid_postfix.bits[0] == 0x7f800000u && _FNan_nid_postfix.bits[0] == 0x7fc00000u); + for (int word = 1; word < 4; ++word) Require(_FInf_nid_postfix.bits[word] == 0 && _FNan_nid_postfix.bits[word] == 0); + const struct { std::uint32_t bits; short code; } cases[] = { + {0x00000000u, 0}, {0x80000000u, 0}, {0x00000001u, -2}, {0x807fffffu, -2}, {0x00800000u, -1}, + {0xbf800000u, -1}, {0x7f7fffffu, -1}, {0x7f800000u, 1}, {0xff800000u, 1}, {0x7f800001u, 2}, + {0x7fc00000u, 2}, {0xff810000u, 2}, {0x7f810000u, 2}, + }; + for (const auto& test : cases) { + float value; + std::memcpy(&value, &test.bits, sizeof(value)); + if (_FDtest_nid_postfix(&value) != test.code) { + std::fprintf(stderr, "Guest _FDtest failed for %08x\n", test.bits); + std::abort(); + } + } + Require(_FDtest_nid_postfix(reinterpret_cast(&_FInf_nid_postfix)) == 1); + Require(_FDtest_nid_postfix(reinterpret_cast(&_FNan_nid_postfix)) == 2); +} + int main() { + CheckFloatClassification(); CheckIntegerConversions(); Require(atof_nid_postfix(" -12.5tail") == -12.5); char* end = nullptr; From 75e2e7252e88bff82a64af99e5537f5302801487 Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 5 Oct 2026 16:51:40 +0200 Subject: [PATCH 164/164] fix(agc): log the Vulkan features after tessellation is enabled The device constructor printed "Vulkan features tessellationAvailable" before it read VkPhysicalDeviceFeatures and set tessellationShader, so the line reported 0 on every GPU, including ones that enable the feature. The rect-list and tessellation paths already read the enabled value; only the diagnostic was wrong, and it suggested that tessellation was unavailable. Print the line right after the feature is enabled. The mesh, depth clip and unrestricted depth range values it also reports are set earlier and do not change. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 98d526ccb..b040bbb41 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -803,7 +803,6 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u state->capabilities.push_back(5283); state->spirvExtensions.push_back("SPV_EXT_mesh_shader"); } - APS5_LOG_OUT("Vulkan features tessellationAvailable=%u mesh=%u depthClip=%u depthRangeUnrestricted=%u", static_cast(state->tessellationShader), static_cast(state->meshShader), static_cast(state->depthClipControl), static_cast(state->depthRangeUnrestricted)); const float priority = 1.0f; VkDeviceQueueCreateInfo queueInfo{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}; queueInfo.queueFamilyIndex = family; @@ -824,6 +823,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u enabled.tessellationShader = available.tessellationShader; state->tessellationShader = enabled.tessellationShader == VK_TRUE; if (state->tessellationShader) state->capabilities.push_back(3); + APS5_LOG_OUT("Vulkan features tessellationAvailable=%u mesh=%u depthClip=%u depthRangeUnrestricted=%u", static_cast(state->tessellationShader), static_cast(state->meshShader), static_cast(state->depthClipControl), static_cast(state->depthRangeUnrestricted)); require(available.samplerAnisotropy && available.textureCompressionBC, "device lacks sampler anisotropy or BC texture compression support required for texture sampling"); enabled.samplerAnisotropy = VK_TRUE; enabled.textureCompressionBC = VK_TRUE;