diff --git a/core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp b/core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp index 8bae004ff1e7..ad00d374ce5c 100644 --- a/core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp +++ b/core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp @@ -36,6 +36,7 @@ constexpr char TrailerMagic[8] = {'b', 'a', 'r', 'e', 'f', 'o', 'o', 't'}; constexpr std::size_t TrailerCodeBytesOffset = sizeof(TrailerMagic) + 3 * sizeof(std::uint32_t); constexpr std::uint32_t Rsrc1VgprMask = 0x3fu; constexpr std::uint32_t Rsrc1SgprMask = 0xfu << 6u; +constexpr std::uint32_t Rsrc2OffchipLdsEnable = 1u << 18u; constexpr std::uint32_t Rsrc2UserSgprMask = (0x1fu << 1u) | (1u << 27u); bool ValidHalves(const Shader* front, const Shader* back) { @@ -127,8 +128,8 @@ std::uint32_t MergeRsrc1(std::uint32_t back, std::uint32_t front) { } std::uint32_t MergeRsrc2(const char* function, std::uint32_t back, std::uint32_t front) { - if ((front & ~Rsrc2UserSgprMask & ~back) != 0) Fail(function, "front half requires hardware state the back half does not enable"); - return (back & ~Rsrc2UserSgprMask) | (front & Rsrc2UserSgprMask); + if ((front & ~(Rsrc2UserSgprMask | Rsrc2OffchipLdsEnable) & ~back) != 0) Fail(function, "front half requires hardware state the back half does not enable"); + return (back & ~Rsrc2UserSgprMask) | (front & (Rsrc2UserSgprMask | Rsrc2OffchipLdsEnable)); } const ShaderRegister* FindHalfRegister(const Shader* shader, std::uint32_t offset) { diff --git a/core/libs/prx/libSceAgc/tests/ShaderFusion.cpp b/core/libs/prx/libSceAgc/tests/ShaderFusion.cpp index 2c901efc5b48..2f451acf50d2 100644 --- a/core/libs/prx/libSceAgc/tests/ShaderFusion.cpp +++ b/core/libs/prx/libSceAgc/tests/ShaderFusion.cpp @@ -195,6 +195,33 @@ void testScratchReuse() { check(reinterpret_cast(changed.code) != firstCode && word(reinterpret_cast(firstCode), 64) == SMovS0, "changed halves reused the earlier fused program"); } +void testOffchipLdsEnable() { + constexpr std::uint32_t offchip = 1u << 18u; + for (const auto frontEnable : {0u, offchip}) { + for (const auto backEnable : {0u, offchip}) { + Halves halves; + halves.frontSh[2].value |= frontEnable; + halves.backSh[3].value |= backEnable; + SizeAlign size{}; + check(sceAgcGetFusedShaderSize_0080(&size, &halves.front, &halves.back) == 0, "offchip LDS enable rejected by the size query"); + std::vector storage(size.m_size + 512, 0xcd); + Shader fused{}; + check(sceAgcFuseShaderHalves_0200(&fused, &halves.front, &halves.back, aligned(storage, 256)) == 0, "offchip LDS enable rejected by fusion"); + const auto* resource = find(fused, 0x08bu); + check(resource != nullptr && resource->value == ((6u << 1u) | (1u << 7u) | (1u << 15u) | frontEnable | backEnable), "fusion lost offchip LDS enable or changed the back-half state or front user SGPRs"); + } + } + Halves reported; + reported.frontSh[2].value = 0x00070004u; + reported.backSh[3].value = 0x00ab0020u; + SizeAlign size{}; + check(sceAgcGetFusedShaderSize_0080(&size, &reported.front, &reported.back) == 0, "reported shader resources rejected by size query"); + std::vector storage(size.m_size + 512, 0xcd); + Shader fused{}; + check(sceAgcFuseShaderHalves_0200(&fused, &reported.front, &reported.back, aligned(storage, 256)) == 0, "reported shader resources rejected by fusion"); + check(find(fused, 0x08bu)->value == 0x00af0004u, "reported shader resources did not retain the front offchip LDS enable"); +} + void testRejections() { Halves halves; SizeAlign size{}; @@ -277,6 +304,7 @@ int main() { testFusion(4); testFusion(0, SSwappcNullS6); testScratchReuse(); + testOffchipLdsEnable(); testRejections(); testHullHalves(); LibcRunShutdown_nid_postfix(); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp index 7434969df723..4dae8c1a9d40 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp @@ -160,7 +160,7 @@ inline Registers BuildInitialContextRegisters() { {0x105, 0}, {0x106, 0}, {0x107, 0}, {0x108, 0}, {0x10b, 0}, {0x10c, 0}, {0x10d, 0}, {0x1b1, 0}, {0x1b3, 0}, {0x1b4, 0}, {0x1b6, 0}, {0x1c3, 0}, {0x1c4, 0}, {0x1c5, 0}, - {0x1ff, 0}, {0x292, 2}, {0x293, 0}, {0x29b, 0}, + {0x1ff, 0}, {0x280, 0x00080008}, {0x281, 0xffff0000}, {0x292, 2}, {0x293, 0}, {0x29b, 0}, {0x2ce, 0}, {0x2d3, 0}, {0x2d5, 0}, {0x2d6, 0}, {0x2db, 0}, {0x2dc, 0xaa00}, {0x2df, 0}, {0x2e4, 0}, {0x2f8, 0}, {0x2f9, 0x2d}, {0x30e, 0xffffffff}, {0x30f, 0xffffffff}, {0x313, 0x6000}, diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index 391c4796e3ac..569b51ec7be0 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -173,16 +173,22 @@ void testRegisterFile() { void testContextAndBases() { AgcDriver::QueueState state; + check(state.context.contains(0x280) && state.context.at(0x280) == 0x00080008u && state.context.contains(0x281) && state.context.at(0x281) == 0xffff0000u, "initial point context differs from the SDK defaults"); + execute(state, makePacket(0x69, {0x280, 0x00180018u, 0x00400008u})); + check(state.context.at(0x280) == 0x00180018u && state.context.at(0x281) == 0x00400008u, "explicit point size and limits were not stored"); for (const auto offset : {0x10bu, 0x10cu, 0x10du}) execute(state, makePacket(0x69, {offset, 0xffffffffu})); execute(state, makePacket(0x12, {0})); for (const auto offset : {0x10bu, 0x10cu, 0x10du}) check(state.context.at(offset) == 0, "CLEAR_STATE did not restore stencil defaults"); + check(state.context.at(0x280) == 0x00080008u && state.context.at(0x281) == 0xffff0000u, "CLEAR_STATE did not restore point size and limits"); execute(state, makePacket(0x69, {0x10, 17})); execute(state, makePacket(0x76, {0x20c, 2})); execute(state, makePacket(0x10, {3, 0}, 0x68)); check(state.context == AgcDriver::InitialContextRegisters() && state.shader.at(0x20c) == 2, "push-clear reset wrong state"); expectFailure([&] { execute(state, makePacket(0x10, {1, 0}, 0x68)); }, "already pushed"); execute(state, makePacket(0x69, {0x10, 19})); + execute(state, makePacket(0x69, {0x280, 0x00100010u, 0x00800010u})); execute(state, makePacket(0x10, {2, 0}, 0x68)); + check(state.context.at(0x280) == 0x00080008u && state.context.at(0x281) == 0xffff0000u, "context pop did not restore saved point size and limits"); check(state.context.at(0x10) == 17, "pop did not restore context"); expectFailure([&] { execute(state, makePacket(0x10, {2, 0}, 0x68)); }, "not been pushed"); alignas(8) std::array arguments{7, 8, 9, 0}; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageMsaaAddressOnPlane.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageMsaaAddressOnPlane.cpp new file mode 100644 index 000000000000..a2d0b125935e --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageMsaaAddressOnPlane.cpp @@ -0,0 +1,115 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +using AgcDriver::Graphics::Require; +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t Words = 8; +alignas(256) std::array buffer{}; +alignas(256) std::array texture{}; +constexpr std::array Coordinates{0u, 1u, 3u, 4u}; +constexpr std::array Samples{0u, 1u, 7u, 0xffffffffu}; +constexpr std::array UnormExpected{0x0u, 0x3b808081u, 0x3c40c0c1u, 0x3e808081u, 0x3e828283u, 0x3e868687u, 0x3f40c0c1u, 0x3f41c1c2u, 0x3f43c3c4u}; +constexpr std::array SrgbExpected{0x0u, 0x399f0000u, 0x3a6f0000u, 0x3d520000u, 0x3d590000u, 0x3d660000u, 0x3f070000u, 0x3f090000u, 0x3f0c0000u}; +alignas(256) constexpr std::array MsaaCode{ + 0x34020085u, 0xe0301000u, 0x80000201u, 0xe0301004u, 0x80000301u, 0xe0301008u, 0x80000401u, + 0xbf8c3f70u, 0xf0000130u, 0x00010802u, 0xbf8c3f70u, 0xe0701010u, 0x80000801u, 0xbf810000u +}; +alignas(256) constexpr auto PlaneCode = [] { + auto code = MsaaCode; + code[8] = 0xf0000108u; + return code; +}(); + +std::array TextureDescriptor(std::uint32_t format) { + const auto address = reinterpret_cast(texture.data()); + return {static_cast(address >> 8u), static_cast((address >> 40u) & 0xffu) | (format << 20u) | 0xc0000000u, 0xc000u, 0x90000facu, 0u, 0u, 0u, 0u}; +} + +void FillTexture(const std::array& descriptor) { + const auto layout = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(descriptor)); + Require(layout.guestBytes <= texture.size(), "MSAA-address texture exceeds its storage"); + texture.fill(std::byte{0xee}); + const auto& mip = layout.mips.at(0); + for (std::uint32_t y = 0; y < 4; ++y) { + for (std::uint32_t x = 0; x < 4; ++x) { + const auto value = 0x12340000u + y * 64u + x; + std::memcpy(texture.data() + mip.tiledOffset + y * mip.pitchBytes + x * 4u, &value, sizeof(value)); + } + } +} + +std::array Load(AgcDriver::VulkanDevice& device, const std::array& image, std::span code, std::uint32_t wave) { + buffer.fill(0xdeadbeefu); + for (std::uint32_t lane = 0; lane < Threads; ++lane) { + buffer[lane * Words] = Coordinates[lane % 4u]; + buffer[lane * Words + 1u] = Coordinates[(lane / 4u) % 4u]; + buffer[lane * Words + 2u] = Samples[lane / 16u]; + } + const auto address = reinterpret_cast(buffer.data()); + std::vector data{static_cast(address), static_cast((address >> 32u) & 0xffffu), static_cast(buffer.size() * 4u), 0x31016facu}; + data.insert(data.end(), image.begin(), image.end()); + 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{ + {ShaderRecompiler::ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {wave, 0, data, compute, std::nullopt, std::nullopt, memory}, device.Target(), {0, 0, 0, 128} + }; + request.useCache = false; + const auto compiled = ShaderRecompiler::Recompile(request); + device.Dispatch(compiled, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + std::array result{}; + for (std::uint32_t lane = 0; lane < Threads; ++lane) result[lane] = buffer[lane * Words + 4u]; + return result; +} + +void Run(AgcDriver::VulkanDevice& device) { + for (const auto format : {20u, 22u, 56u, 130u}) { + const auto descriptor = TextureDescriptor(format); + FillTexture(descriptor); + for (const auto wave : {32u, 64u}) { + const auto plain = Load(device, descriptor, PlaneCode, wave); + const auto msaa = Load(device, descriptor, MsaaCode, wave); + for (std::uint32_t lane = 0; lane < Threads; ++lane) { + Require(msaa[lane] == plain[lane], "MSAA-address load differs from the measured 2D control at lane " + std::to_string(lane)); + const auto x = Coordinates[lane % 4u]; + const auto y = Coordinates[(lane / 4u) % 4u]; + if (x == 4u || y == 4u) Require(msaa[lane] == 0u, "MSAA-address out-of-range load did not read zero"); + else if (format == 20u || format == 22u) Require(msaa[lane] == 0x12340000u + y * 64u + x, "MSAA-address load lost the oracle texel marker"); + else { + const auto index = ((lane / 4u) % 4u) * 3u + lane % 4u; + const auto expected = std::bit_cast((format == 56u ? UnormExpected : SrgbExpected)[index]); + const auto actual = std::bit_cast(msaa[lane]); + if (format == 130u) { + const auto encoded = [](float linear) { return std::lround((linear <= 0.0031308f ? linear * 12.92f : 1.055f * std::pow(linear, 1.0f / 2.4f) - 0.055f) * 255.0f); }; + Require(std::isfinite(actual) && encoded(actual) == encoded(expected), "MSAA-address sRGB load changed the oracle's encoded texel"); + } else Require(std::abs(actual - expected) < 0.0001f, "MSAA-address normalized load differs from the Radeon oracle"); + } + } + } + } +} +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageStoreFaceOnCube.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageStoreFaceOnCube.cpp new file mode 100644 index 000000000000..148b6e45c74d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageStoreFaceOnCube.cpp @@ -0,0 +1,195 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.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 + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Size = 8; +constexpr std::uint32_t Threads = Size * Size; +constexpr std::uint32_t Faces = 12; +constexpr std::uint32_t TypeCube = 11; +constexpr std::uint32_t IdentitySwizzle = 0xfacu; +constexpr std::size_t TexelBytes = 32768; +constexpr std::size_t BlockBytes = 65536; + +alignas(256) std::array Input{}; + +alignas(256) constexpr std::array StoreCode{ + 0x34020084, 0xe0381000, 0x80001401, 0xbf8c3f70, 0x363c0087, 0x2c3e0083, 0xf0201f08, 0x0001141e, 0xbf810000, +}; + +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, "image store face on a cube: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, 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; +}; + +struct Format { + const char* name; + std::uint32_t format; +}; + +constexpr std::array Formats{{ + {"32_UINT", 20u}, + {"32_FLOAT", 22u}, +}}; + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +std::uint32_t Value(std::uint32_t tid) { + return 0x3f000000u + tid * 0x1357u; +} + +std::array CubeDescriptor(const void* data, std::uint32_t format, std::uint32_t baseArray) { + const auto address = static_cast(reinterpret_cast(data)); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (format << 20u) | (((Size - 1u) & 3u) << 30u), + ((Size - 1u) >> 2u) | ((Size - 1u) << 14u), + IdentitySwizzle | (TypeCube << 28u), + (Faces - 1u) | (baseArray << 16u), + 0u, + 0u, + 0u, + }; +} + +AgcDriver::Graphics::SurfaceGeometry Geometry(const void* data, std::uint32_t format) { + return AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(CubeDescriptor(data, format, 0u))); +} + +std::uint64_t TexelOffset(const AgcDriver::Graphics::SurfaceGeometry& geometry, std::uint32_t face, std::uint32_t x, std::uint32_t y) { + const auto& mip = geometry.mips.at(0); + return geometry.GuestLayerOffset(face) + mip.tiledOffset + static_cast(y) * mip.pitchBytes + x * 4u; +} + +std::vector Initial() { + std::vector bytes(TexelBytes); + for (std::size_t index = 0; index < bytes.size(); ++index) bytes[index] = static_cast(0x80u + 37u * index + 11u * (index >> 4u)); + return bytes; +} + +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}; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::uint8_t* texels, const Format& format, std::uint32_t baseArray) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Input[tid * 4u] = Value(tid); + const auto initial = Initial(); + std::copy(initial.begin(), initial.end(), texels); + std::vector userData(16, 0u); + const auto buffer = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto texture = CubeDescriptor(texels, format.format, baseArray); + std::copy(buffer.begin(), buffer.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + const std::span code(StoreCode); + 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(); + AgcDriver::Graphics::StorageTexture::FlushPending(reinterpret_cast(texels), TexelBytes, nullptr, "test"); + device.WaitIdle(); +} + +void CheckTexels(const std::uint8_t* texels, const Format& format, std::uint32_t baseArray) { + const auto geometry = Geometry(texels, format.format); + Require(geometry.layers == Faces && geometry.guestBytes <= TexelBytes, "image store face on a cube: the cube has " + std::to_string(geometry.layers) + " layers in " + std::to_string(geometry.guestBytes) + " bytes, expected " + std::to_string(Faces) + " in at most " + std::to_string(TexelBytes)); + const auto initial = Initial(); + std::vector written(TexelBytes, false); + const auto label = std::string("image_store 2d through a ") + format.name + " cube T# with BASE_ARRAY " + std::to_string(baseArray); + for (std::uint32_t y = 0; y < Size; ++y) { + for (std::uint32_t x = 0; x < Size; ++x) { + const auto offset = TexelOffset(geometry, baseArray, x, y); + std::uint32_t actual = 0; + std::memcpy(&actual, texels + offset, 4u); + const auto wanted = Value(y * Size + x); + Require(actual == wanted, label + ": texel (" + std::to_string(x) + ", " + std::to_string(y) + ") of face " + std::to_string(baseArray) + " is " + Hex(actual) + ", expected " + Hex(wanted)); + std::fill(written.begin() + static_cast(offset), written.begin() + static_cast(offset + 4u), true); + } + } + for (std::size_t index = 0; index < TexelBytes; ++index) { + Require(written[index] || texels[index] == initial[index], label + ": byte " + std::to_string(index) + " outside face " + std::to_string(baseArray) + " changed"); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock block; + auto* texels = block.Data(); + for (const auto& format : Formats) { + for (const auto baseArray : {0u, 6u}) { + Dispatch(*device, texels, format, baseArray); + CheckTexels(texels, format, baseArray); + } + } + std::puts("image store face on cube tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp index 3372e16599b7..16d6f40196c6 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp @@ -12,6 +12,12 @@ #include "SDL.h" #include "SceTypes.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif #include "prx/libc/include/PreciseWait.hpp" #include "prx/libc/include/Shutdown.hpp" #include "prx/libkernel/Time/include/Time.hpp" @@ -112,6 +118,17 @@ static std::array g_outputMutexes; static Port g_ports[PORTS_MAX]; static bool g_sdlInitialized = false; +static void raiseOutputThreadPriority() { +#ifdef _WIN32 + thread_local bool raised = false; + if (raised) return; + if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST) == 0) { + throw std::runtime_error("sceAudioOutOutput: SetThreadPriority failed (error " + std::to_string(GetLastError()) + ")"); + } + raised = true; +#endif +} + static bool ensureSdlAudio() { if (g_sdlInitialized) { return true; @@ -364,6 +381,7 @@ int APS5_VABI sceAudioOutClose(int handle) { int APS5_VABI sceAudioOutOutput(int handle, const void* ptr) try { const AudioOutCall call; if (handle <= 0 || handle > PORTS_MAX) return -2144993277; + raiseOutputThreadPriority(); std::lock_guard outputLock(g_outputMutexes[handle - 1]); std::unique_lock lock(g_mutex); Port* port = getPort(handle); @@ -393,6 +411,7 @@ int APS5_VABI sceAudioOutOutputs(AudioOutOutputParam* param, std::uint32_t num) if (param[i].handle <= 0 || param[i].handle > PORTS_MAX) return -2144993277; selected[param[i].handle - 1] = true; } + raiseOutputThreadPriority(); std::array, PORTS_MAX> outputLocks; for (int index = 0; index < PORTS_MAX; ++index) { if (selected[index]) outputLocks[index] = std::unique_lock(g_outputMutexes[index]); diff --git a/core/libs/prx/libSceHmd/CMakeLists.txt b/core/libs/prx/libSceHmd/CMakeLists.txt index d5a15e9ca81d..557372ff45d3 100644 --- a/core/libs/prx/libSceHmd/CMakeLists.txt +++ b/core/libs/prx/libSceHmd/CMakeLists.txt @@ -5,7 +5,8 @@ configure_windows_unwind(libSceHmd) if(BUILD_TESTING) add_test_executable(guest_hmd_tests ${LIBS_INCLUDE_DIR}/tests/GuestHmd.cpp) target_include_directories(guest_hmd_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_hmd_tests PRIVATE libSceHmd) + target_link_libraries(guest_hmd_tests PRIVATE libSceHmd libc) + configure_windows_unwind(guest_hmd_tests) add_test(NAME guest_hmd COMMAND guest_hmd_tests) if(WIN32) set_tests_properties(guest_hmd PROPERTIES ENVIRONMENT_MODIFICATION diff --git a/core/libs/prx/libSceHmd/Export.cpp b/core/libs/prx/libSceHmd/Export.cpp index 36c2ca3c0675..54c710f9df2b 100644 --- a/core/libs/prx/libSceHmd/Export.cpp +++ b/core/libs/prx/libSceHmd/Export.cpp @@ -1,4 +1,5 @@ #include "Hmd.hpp" +#include "prx/libc/include/General.hpp" #include @@ -68,4 +69,84 @@ std::int32_t APS5_VABI sceHmdInternalMmapIsConnect() { return 0; } +int APS5_VABI sceHmdDistortionInitialize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmdDistortionGet2dVrCommand() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmdDistortionGetCorrectionCommand() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmdDistortionGetWorkMemorySize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmdDistortionGetWorkMemoryAlign() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmdFillDistortionBuffer() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmdGet2DEyeOffset() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmdGet2dDistortionMap() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmdGet2dVrCommand() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmdGetDistortionCorrectionCommand() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmdGetDistortionMap() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmdGetDistortionParams() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmdGetDistortionWorkMemoryAlign() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmdGetDistortionWorkMemorySize() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmdGetEyeStatus() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHmdGetFieldOfView() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/core/libs/prx/libSceLibcInternal/FilesystemStatistics.cpp b/core/libs/prx/libSceLibcInternal/FilesystemStatistics.cpp index 083431cef66a..8cb50f1eca83 100644 --- a/core/libs/prx/libSceLibcInternal/FilesystemStatistics.cpp +++ b/core/libs/prx/libSceLibcInternal/FilesystemStatistics.cpp @@ -1,26 +1,7 @@ #include "prx/libc/include/general/VabiMacros.hpp" -#include "prx/libkernel/File/include/KernelStatfs.hpp" -#include -#include +#include "prx/libSceLibcInternal/include/Statvfs.hpp" #include -struct LibcStatvfs { - std::uint64_t f_bavail; - std::uint64_t f_bfree; - std::uint64_t f_blocks; - std::uint64_t f_favail; - std::uint64_t f_ffree; - std::uint64_t f_files; - std::uint64_t f_bsize; - std::uint64_t f_flag; - std::uint64_t f_frsize; - std::uint64_t f_fsid; - std::uint64_t f_namemax; -}; -static_assert(sizeof(LibcStatvfs) == 88); -static_assert(offsetof(LibcStatvfs, f_bsize) == 48); -static_assert(offsetof(LibcStatvfs, f_namemax) == 80); - extern "C" int APS5_VABI _fstatfs_nid_postfix(int, KernelStatfs*); extern "C" int* APS5_VABI __error_nid_postfix(); @@ -34,12 +15,7 @@ extern "C" int APS5_VABI fstatvfs_nid_postfix(int descriptor, LibcStatvfs* outpu *error = 14; return -1; } - const LibcStatvfs result{ - static_cast(volume.f_bavail), volume.f_bfree, volume.f_blocks, - static_cast(volume.f_ffree), static_cast(volume.f_ffree), volume.f_files, - volume.f_iosize, (volume.f_flags & 1) | ((volume.f_flags & 8) >> 2), volume.f_bsize, - 0, volume.f_namemax - }; + const LibcStatvfs result = StatvfsFromStatfs(volume); std::memcpy(output, &result, sizeof(result)); *error = saved; return 0; diff --git a/core/libs/prx/libSceLibcInternal/PathStatistics.cpp b/core/libs/prx/libSceLibcInternal/PathStatistics.cpp index 136ac24cd6cb..885ce9e10fda 100644 --- a/core/libs/prx/libSceLibcInternal/PathStatistics.cpp +++ b/core/libs/prx/libSceLibcInternal/PathStatistics.cpp @@ -10,6 +10,12 @@ #else #include #endif +#ifdef __APPLE__ +#include "prx/libSceLibcInternal/include/Statvfs.hpp" +#include +#include +#include +#endif struct LibcStatvfs; extern "C" int APS5_VABI fstatvfs_nid_postfix(int, LibcStatvfs*); @@ -44,6 +50,32 @@ int HostError(int error) { } } +#ifdef __APPLE__ +int PathVolume(const std::filesystem::path& path, bool directory, LibcStatvfs* output) { + struct statvfs host{}; + if (::statvfs(path.c_str(), &host) != 0) return Fail(HostError(errno)); + if (directory) { + struct stat file{}; + if (::stat(path.c_str(), &file) != 0) return Fail(HostError(errno)); + if (!S_ISDIR(file.st_mode)) return Fail(20); + } + if (output == nullptr) return Fail(14); + KernelStatfs volume{}; + if (host.f_flag & ST_RDONLY) volume.f_flags |= 0x1; + if (host.f_flag & ST_NOSUID) volume.f_flags |= 0x8; + volume.f_bsize = host.f_frsize != 0 ? host.f_frsize : host.f_bsize; + volume.f_iosize = host.f_bsize; + volume.f_blocks = host.f_blocks; + volume.f_bfree = host.f_bfree; + volume.f_bavail = static_cast(host.f_bavail); + volume.f_files = host.f_files; + volume.f_ffree = static_cast(host.f_favail); + volume.f_namemax = static_cast(host.f_namemax); + const LibcStatvfs result = StatvfsFromStatfs(volume); + std::memcpy(output, &result, sizeof(result)); + return 0; +} +#else struct Descriptor { int value; ~Descriptor() { @@ -110,6 +142,7 @@ int OpenMetadata(const std::filesystem::path& path, bool directory) { return descriptor < 0 ? Fail(HostError(errno)) : descriptor; #endif } +#endif } extern "C" int APS5_VABI statvfs_nid_postfix(const char* path, LibcStatvfs* output) { @@ -120,9 +153,15 @@ extern "C" int APS5_VABI statvfs_nid_postfix(const char* path, LibcStatvfs* outp const bool directory = last == '/' || last == '\\' || FinalPathComponentIsDot_nid_no_patch(path); std::filesystem::path native; if (const int error = ResolvePathError_nid_no_patch(path, native)) return Fail(error); +#ifdef __APPLE__ + if (PathVolume(native, directory, output) != 0) return -1; + errno = saved; + return 0; +#else const int descriptor = OpenMetadata(native, directory); if (descriptor < 0) return -1; const Descriptor owner{descriptor}; errno = saved; return fstatvfs_nid_postfix(descriptor, output); +#endif } diff --git a/core/libs/prx/libSceLibcInternal/include/Statvfs.hpp b/core/libs/prx/libSceLibcInternal/include/Statvfs.hpp new file mode 100644 index 000000000000..4d6b238770fd --- /dev/null +++ b/core/libs/prx/libSceLibcInternal/include/Statvfs.hpp @@ -0,0 +1,34 @@ +#ifndef CORE_LIBS_PRX_LIBSCELIBCINTERNAL_INCLUDE_STATVFS_HPP +#define CORE_LIBS_PRX_LIBSCELIBCINTERNAL_INCLUDE_STATVFS_HPP + +#include "prx/libkernel/File/include/KernelStatfs.hpp" +#include +#include + +struct LibcStatvfs { + std::uint64_t f_bavail; + std::uint64_t f_bfree; + std::uint64_t f_blocks; + std::uint64_t f_favail; + std::uint64_t f_ffree; + std::uint64_t f_files; + std::uint64_t f_bsize; + std::uint64_t f_flag; + std::uint64_t f_frsize; + std::uint64_t f_fsid; + std::uint64_t f_namemax; +}; +static_assert(sizeof(LibcStatvfs) == 88); +static_assert(offsetof(LibcStatvfs, f_bsize) == 48); +static_assert(offsetof(LibcStatvfs, f_namemax) == 80); + +inline LibcStatvfs StatvfsFromStatfs(const KernelStatfs& volume) { + return LibcStatvfs{ + static_cast(volume.f_bavail), volume.f_bfree, volume.f_blocks, + static_cast(volume.f_ffree), static_cast(volume.f_ffree), volume.f_files, + volume.f_iosize, (volume.f_flags & 1) | ((volume.f_flags & 8) >> 2), volume.f_bsize, + 0, volume.f_namemax + }; +} + +#endif diff --git a/core/libs/prx/libSceVideoOut/tests/MouseInput.cpp b/core/libs/prx/libSceVideoOut/tests/MouseInput.cpp index 759c9708e7a9..d5f03fba41b9 100644 --- a/core/libs/prx/libSceVideoOut/tests/MouseInput.cpp +++ b/core/libs/prx/libSceVideoOut/tests/MouseInput.cpp @@ -120,6 +120,70 @@ int main() { input.HandleEvent(Motion(3, 3, 3), 3); Sum(handle, x, y); Require(x == 6 && y == 6, "an empty presentation changed the scale"); + + { + SDL_Event leftDown{}; + leftDown.type = SDL_MOUSEBUTTONDOWN; + leftDown.button.windowID = 3; + leftDown.button.button = SDL_BUTTON_LEFT; + leftDown.button.state = SDL_PRESSED; + input.HandleEvent(leftDown, 3); + MouseData data{}; + int read = sceMouseRead(handle, &data, 1); + Require(read == 1, "failed to read mouse data"); + Require(data.buttons == 1, "left mouse button should use guest mouse bit 0"); + + SDL_Event leftUp{}; + leftUp.type = SDL_MOUSEBUTTONUP; + leftUp.button.windowID = 3; + leftUp.button.button = SDL_BUTTON_LEFT; + leftUp.button.state = SDL_RELEASED; + input.HandleEvent(leftUp, 3); + read = sceMouseRead(handle, &data, 1); + Require(read == 1, "failed to read mouse data"); + Require(data.buttons == 0, "left mouse button up should clear guest mouse bit 0"); + + SDL_Event rightDown{}; + rightDown.type = SDL_MOUSEBUTTONDOWN; + rightDown.button.windowID = 3; + rightDown.button.button = SDL_BUTTON_RIGHT; + rightDown.button.state = SDL_PRESSED; + input.HandleEvent(rightDown, 3); + read = sceMouseRead(handle, &data, 1); + Require(read == 1, "failed to read mouse data"); + Require(data.buttons == 2, "right mouse button should use guest mouse bit 1"); + + SDL_Event rightUp{}; + rightUp.type = SDL_MOUSEBUTTONUP; + rightUp.button.windowID = 3; + rightUp.button.button = SDL_BUTTON_RIGHT; + rightUp.button.state = SDL_RELEASED; + input.HandleEvent(rightUp, 3); + read = sceMouseRead(handle, &data, 1); + Require(read == 1, "failed to read mouse data"); + Require(data.buttons == 0, "right mouse button up should clear guest mouse bit 1"); + + SDL_Event middleDown{}; + middleDown.type = SDL_MOUSEBUTTONDOWN; + middleDown.button.windowID = 3; + middleDown.button.button = SDL_BUTTON_MIDDLE; + middleDown.button.state = SDL_PRESSED; + input.HandleEvent(middleDown, 3); + read = sceMouseRead(handle, &data, 1); + Require(read == 1, "failed to read mouse data"); + Require(data.buttons == 4, "middle mouse button should use guest mouse bit 2"); + + SDL_Event middleUp{}; + middleUp.type = SDL_MOUSEBUTTONUP; + middleUp.button.windowID = 3; + middleUp.button.button = SDL_BUTTON_MIDDLE; + middleUp.button.state = SDL_RELEASED; + input.HandleEvent(middleUp, 3); + read = sceMouseRead(handle, &data, 1); + Require(read == 1, "failed to read mouse data"); + Require(data.buttons == 0, "middle mouse button up should clear guest mouse bit 2"); + } + CaptureDecision(input, handle); CaptureApplied(input); std::puts("mouse input tests passed"); diff --git a/core/libs/prx/libSceVoice/Export.cpp b/core/libs/prx/libSceVoice/Export.cpp index fc8f38c3c9d2..027898f0fbe5 100644 --- a/core/libs/prx/libSceVoice/Export.cpp +++ b/core/libs/prx/libSceVoice/Export.cpp @@ -31,6 +31,7 @@ struct Voice { std::mutex mutex; bool initialized = false; bool started = false; + bool chatEnabled = false; std::uint32_t nextId = 0; std::map ports; std::set> connections; @@ -97,6 +98,7 @@ int APS5_VABI sceVoiceEnd_nid_postfix(void) { voice.ports.clear(); voice.connections.clear(); voice.started = false; + voice.chatEnabled = false; voice.initialized = false; return 0; } @@ -234,7 +236,10 @@ int APS5_VABI sceVoiceGetResourceInfo(VoiceResourceInfo* info) { } int APS5_VABI sceVoiceEnableChat(void) { - NotImplemented_nid_no_patch(__func__); + auto& voice = State(); + std::lock_guard lock(voice.mutex); + if (!voice.initialized) Fail(__func__, "library not initialized"); + voice.chatEnabled = true; return 0; } @@ -246,7 +251,10 @@ int APS5_VABI sceVoiceResetPort(uint32_t port_id) { } int APS5_VABI sceVoiceDisableChat(void) { - NotImplemented_nid_no_patch(__func__); + auto& voice = State(); + std::lock_guard lock(voice.mutex); + if (!voice.initialized) Fail(__func__, "library not initialized"); + voice.chatEnabled = false; return 0; } diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 45e982169fab..4e671555f3f1 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -16,6 +16,7 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/StringExtensions.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Math.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Random.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/FloatBuiltins.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/HostEntropy.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Time.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/FileAndHeap.cpp @@ -199,6 +200,15 @@ if(BUILD_TESTING) set_tests_properties(guest_wide_conversion PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_float_builtins_tests ${LIBS_INCLUDE_DIR}/tests/GuestFloatBuiltins.cpp) + target_include_directories(guest_float_builtins_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_float_builtins_tests PRIVATE libc) + configure_windows_unwind(guest_float_builtins_tests) + add_test(NAME guest_float_builtins COMMAND guest_float_builtins_tests) + if(WIN32) + set_tests_properties(guest_float_builtins PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_character_tests ${LIBS_INCLUDE_DIR}/tests/GuestCharacter.cpp) target_include_directories(guest_character_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_character_tests PRIVATE libc) @@ -528,6 +538,26 @@ if(BUILD_TESTING) set_tests_properties(guest_demangle PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(unwind_cursor_tests ${LIBS_INCLUDE_DIR}/tests/UnwindCursor.cpp) + target_include_directories(unwind_cursor_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(unwind_cursor_tests PRIVATE libc) + set_target_properties(unwind_cursor_tests PROPERTIES ENABLE_EXPORTS ON) + configure_windows_unwind(unwind_cursor_tests) + add_test(NAME unwind_cursor COMMAND unwind_cursor_tests) + set(unwindCursorTests unwind_cursor) + foreach(operation IN ITEMS get set) + add_test(NAME unwind_cursor_${operation}_register COMMAND unwind_cursor_tests ${operation}-register) + set_tests_properties(unwind_cursor_${operation}_register PROPERTIES TIMEOUT 20 + PASS_REGULAR_EXPRESSION "libunwind: ${operation}Register - unsupported x86_64 register") + add_test(NAME unwind_cursor_${operation}_float_register COMMAND unwind_cursor_tests ${operation}-float-register) + set_tests_properties(unwind_cursor_${operation}_float_register PROPERTIES TIMEOUT 20 + PASS_REGULAR_EXPRESSION "libunwind: ${operation}FloatRegister - no x86_64 float registers") + list(APPEND unwindCursorTests unwind_cursor_${operation}_register unwind_cursor_${operation}_float_register) + endforeach() + if(WIN32) + set_tests_properties(${unwindCursorTests} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_string_tests ${LIBS_INCLUDE_DIR}/tests/GuestStrings.cpp) target_include_directories(guest_string_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_string_tests PRIVATE libc libScePosixForWebKit) @@ -569,6 +599,10 @@ if(BUILD_TESTING) target_include_directories(exception_runtime_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(exception_runtime_tests PRIVATE libc) add_test(NAME exception_runtime COMMAND exception_runtime_tests) + add_test_executable(guest_backtrace_tests ${LIBS_INCLUDE_DIR}/tests/GuestBacktrace.cpp) + target_include_directories(guest_backtrace_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_backtrace_tests PRIVATE libc) + add_test(NAME guest_backtrace COMMAND guest_backtrace_tests) add_test_executable(exception_personality_tests ${LIBS_INCLUDE_DIR}/tests/ExceptionPersonality.cpp) target_include_directories(exception_personality_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(exception_personality_tests PRIVATE libc) diff --git a/core/libs/prx/libc/include/exceptions/Unwind.hpp b/core/libs/prx/libc/include/exceptions/Unwind.hpp index bb7f08357cc6..2c2cc1a1505a 100644 --- a/core/libs/prx/libc/include/exceptions/Unwind.hpp +++ b/core/libs/prx/libc/include/exceptions/Unwind.hpp @@ -24,6 +24,12 @@ struct _Unwind_Context { std::uintptr_t dataBase {}; std::uintptr_t personality {}; bool signalFrame {}; + std::uintptr_t regionEnd {}; + std::uintptr_t frameEntry {}; + std::uintptr_t frameEntrySize {}; + std::uintptr_t argumentsSize {}; + std::uintptr_t segment {}; + bool infoMissing {}; }; namespace LibcUnwind { @@ -99,7 +105,7 @@ inline unsigned EncodingSize(Byte encoding) { } } -struct Lookup { Word pc; const Byte* fde {}; Word text {}; Word data {}; }; +struct Lookup { Word pc; const Byte* fde {}; Word text {}; Word data {}; Word segment {}; }; struct Frame { const Byte* cieBegin {}; @@ -172,7 +178,7 @@ constexpr unsigned RuleCount = 33; constexpr unsigned RuleCount = 17; #endif -struct Rules { Rule registers[RuleCount] {}; unsigned cfaRegister {7}; std::intptr_t cfaOffset {}; const Byte* cfaExpression {}; }; +struct Rules { Rule registers[RuleCount] {}; unsigned cfaRegister {7}; std::intptr_t cfaOffset {}; const Byte* cfaExpression {}; Word argumentsSize {}; }; inline bool Instructions(const Byte* p, const Byte* end, const Frame& frame, Word target, Rules& state, const Rules& initial) { Word location = frame.start; @@ -221,7 +227,7 @@ inline bool Instructions(const Byte* p, const Byte* end, const Frame& frame, Wor if (reg < RuleCount) state.registers[reg] = {unsigned(opcode == 16 ? 3 : 6), 0, expr}; break; } - case 46: Uleb(p); break; + case 46: state.argumentsSize = Uleb(p); break; default: return false; } } diff --git a/core/libs/prx/libc/src/Backtrace.cpp b/core/libs/prx/libc/src/Backtrace.cpp index 5286225707c1..806ab6cdd9c2 100644 --- a/core/libs/prx/libc/src/Backtrace.cpp +++ b/core/libs/prx/libc/src/Backtrace.cpp @@ -1,15 +1,140 @@ +#include #include +#include +#include +#include +#include +#include "prx/libc/include/exceptions/Unwind.hpp" #include "prx/libc/include/General.hpp" +#if defined(__linux__) || defined(__APPLE__) +#include +#endif +#ifdef _WIN32 +#include +#endif extern "C" { +_Unwind_Reason_Code APS5_VABI _Unwind_Backtrace_nid_postfix(_Unwind_Trace_Fn, void*); +_Unwind_Ptr APS5_VABI _Unwind_GetIP_nid_postfix(_Unwind_Context*); +} + +namespace { + +struct CapturedFrame { + std::uintptr_t address; + std::string text; +}; + +struct CaptureState { + std::vector* frames; + std::uint32_t limit; + unsigned skip; +}; + +std::string Hex(std::uintptr_t value) { + std::string result("0x"); + char buffer[2 + sizeof(value) * 2]; + const auto conversion = std::to_chars(buffer, buffer + sizeof(buffer), value, 16); + result.append(buffer, conversion.ptr); + return result; +} + +std::string FrameText(std::uintptr_t address) { +#if defined(__linux__) || defined(__APPLE__) + Dl_info info{}; + if (dladdr(reinterpret_cast(address), &info) != 0 && info.dli_fname != nullptr) { + const char* name = std::strrchr(info.dli_fname, '/'); + std::string result(name != nullptr ? name + 1 : info.dli_fname); + if (info.dli_sname != nullptr) { + result += '!'; + result += info.dli_sname; + if (info.dli_saddr != nullptr) result += '+' + Hex(address - reinterpret_cast(info.dli_saddr)); + } else if (info.dli_fbase != nullptr) { + result += '+' + Hex(address - reinterpret_cast(info.dli_fbase)); + } + if (result.size() > 255) result.resize(255); + return result; + } +#endif +#ifdef _WIN32 + HMODULE module = nullptr; + if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(address), &module)) { + char path[MAX_PATH] = {}; + const auto length = GetModuleFileNameA(module, path, sizeof(path)); + if (length != 0 && length < sizeof(path)) { + const char* name = std::strrchr(path, '\\'); + std::string result(name != nullptr ? name + 1 : path); + result += '+' + Hex(address - reinterpret_cast(module)); + if (result.size() > 255) result.resize(255); + return result; + } + } +#endif + return Hex(address); +} + +_Unwind_Reason_Code CaptureFrame(_Unwind_Context* context, void* argument) { + auto& state = *static_cast(argument); + if (state.skip != 0) { + --state.skip; + return _URC_NO_REASON; + } + if (state.frames->size() == state.limit) return _URC_NORMAL_STOP; + const auto address = _Unwind_GetIP_nid_postfix(context); + if (address != 0) state.frames->push_back({address, FrameText(address)}); + return _URC_NO_REASON; +} -int APS5_VABI sceLibcBacktraceGetBufferSize_nid_postfix(int, int*, std::size_t*) { - NotImplemented_nid_no_patch(__func__); +bool CaptureFrames(std::uint32_t limit, std::vector& frames) { + if (limit == 0) return true; + CaptureState state{&frames, limit, 2}; + const auto result = _Unwind_Backtrace_nid_postfix(CaptureFrame, &state); + return result == _URC_END_OF_STACK || result == _URC_NORMAL_STOP; +} + +std::size_t RecordSize(const CapturedFrame& frame) { + return 0x18 + ((frame.text.size() + 7) & ~std::size_t(7)); +} + +int Capture(std::uint32_t limit, int* count, std::size_t* size) { + std::vector frames; + if (!CaptureFrames(limit, frames)) return 1; + std::size_t required = 0; + for (const auto& frame : frames) required += RecordSize(frame); + *count = static_cast(frames.size()); + *size = required; return 0; } -int APS5_VABI sceLibcBacktraceSelf_nid_postfix(int, void*, std::size_t, int*) { - NotImplemented_nid_no_patch(__func__); +} + +extern "C" { + +int APS5_VABI sceLibcBacktraceGetBufferSize_nid_postfix(int maxFrames, int* count, std::size_t* size) { + if (count == nullptr || size == nullptr) return 1; + return Capture(static_cast(maxFrames), count, size); +} + +int APS5_VABI sceLibcBacktraceSelf_nid_postfix(int maxFrames, void* buffer, std::size_t bufferSize, int* count) { + if (buffer == nullptr || count == nullptr) return 1; + std::vector frames; + if (!CaptureFrames(static_cast(maxFrames), frames)) return 1; + auto* current = static_cast(buffer); + std::size_t remaining = bufferSize; + int written = 0; + for (const auto& frame : frames) { + const auto recordSize = RecordSize(frame); + if (recordSize > remaining) break; + const auto next = recordSize < remaining ? current + recordSize : nullptr; + std::memset(current, 0, recordSize); + std::memcpy(current, &next, sizeof(next)); + std::memcpy(current + 0x10, frame.text.c_str(), frame.text.size() + 1); + current += recordSize; + remaining -= recordSize; + ++written; + } + *count = written; return 0; } diff --git a/core/libs/prx/libc/src/FloatBuiltins.cpp b/core/libs/prx/libc/src/FloatBuiltins.cpp new file mode 100644 index 000000000000..b0596522597d --- /dev/null +++ b/core/libs/prx/libc/src/FloatBuiltins.cpp @@ -0,0 +1,584 @@ +#include +#include +#include +#include +#include + +#include "prx/libc/include/General.hpp" + +namespace { + +using Int128 = __int128; +using Uint128 = unsigned __int128; + +constexpr std::uint32_t DefaultMxcsr = 0x1f80; + +template +struct FloatFormat; + +template<> +struct FloatFormat { + using Rep = std::uint32_t; + static constexpr int SignificandBits = 23; + static constexpr int Bias = 127; + static constexpr int MaxBiased = 0xff; +}; + +template<> +struct FloatFormat { + using Rep = std::uint64_t; + static constexpr int SignificandBits = 52; + static constexpr int Bias = 1023; + static constexpr int MaxBiased = 0x7ff; +}; + +template +struct Decomposed { + bool negative; + int exponent; + typename FloatFormat::Rep significand; +}; + +template +Decomposed Decompose(TFloat value) { + using Format = FloatFormat; + using Rep = typename Format::Rep; + const Rep bits = std::bit_cast(value); + const Rep implicitBit = static_cast(1) << Format::SignificandBits; + const int biased = static_cast((bits >> Format::SignificandBits) & Format::MaxBiased); + return {(bits >> (sizeof(Rep) * 8 - 1)) != 0, biased - Format::Bias, (bits & (implicitBit - 1)) | implicitBit}; +} + +struct ExtendedBits { + std::uint64_t mantissa; + bool negative; + int exponent; +}; + +ExtendedBits DecomposeExtended(long double value) { + unsigned char bytes[sizeof(long double)]; + std::memcpy(bytes, &value, sizeof(bytes)); + std::uint64_t mantissa; + std::uint16_t signExponent; + std::memcpy(&mantissa, bytes, sizeof(mantissa)); + std::memcpy(&signExponent, bytes + 8, sizeof(signExponent)); + return {mantissa, (signExponent & 0x8000u) != 0, static_cast(signExponent & 0x7fffu) - 16383}; +} + +long double ComposeExtended(bool negative, int biased, std::uint64_t mantissa) { + unsigned char bytes[sizeof(long double)] = {}; + const std::uint16_t signExponent = static_cast((negative ? 0x8000u : 0u) | static_cast(biased)); + std::memcpy(bytes, &mantissa, sizeof(mantissa)); + std::memcpy(bytes + 8, &signExponent, sizeof(signExponent)); + long double value; + std::memcpy(&value, bytes, sizeof(value)); + return value; +} + +template +constexpr int ShiftMask = static_cast(sizeof(TShift) * 8 - 1); + +template +TShift ShiftSignificand(const Decomposed& parts) { + constexpr int significandBits = FloatFormat::SignificandBits; + const TShift significand = parts.significand; + if (parts.exponent < significandBits) return significand >> (significandBits - parts.exponent); + return significand << ((parts.exponent - significandBits) & ShiftMask); +} + +template +TResult FixSigned(TFloat value) { + const Decomposed parts = Decompose(value); + if (parts.exponent < 0) return 0; + const TShift magnitude = ShiftSignificand(parts); + return static_cast(parts.negative ? static_cast(0) - magnitude : magnitude); +} + +template +TResult FixUnsigned(TFloat value) { + const Decomposed parts = Decompose(value); + if (parts.negative || parts.exponent < 0) return 0; + return ShiftSignificand(parts); +} + +template +TResult FixExtendedSigned(long double value) { + const ExtendedBits parts = DecomposeExtended(value); + if (parts.exponent < 0) return 0; + TResult magnitude; + if constexpr (sizeof(TResult) == 16) { + const Uint128 mantissa = parts.mantissa; + magnitude = static_cast(parts.exponent < 64 ? mantissa >> (63 - parts.exponent) + : mantissa << ((parts.exponent - 63) & 127)); + } else { + magnitude = static_cast(parts.mantissa >> ((63 - parts.exponent) & 63)); + } + using TUnsigned = std::conditional_t; + const TUnsigned bits = static_cast(magnitude); + return static_cast(parts.negative ? static_cast(0) - bits : bits); +} + +template +TResult FixExtendedUnsigned(long double value) { + const ExtendedBits parts = DecomposeExtended(value); + if (parts.negative || parts.exponent < 0) return 0; + if constexpr (sizeof(TResult) == 16) { + const Uint128 mantissa = parts.mantissa; + return parts.exponent < 64 ? mantissa >> (63 - parts.exponent) : mantissa << ((parts.exponent - 63) & 127); + } else if constexpr (sizeof(TResult) == 8) { + return parts.mantissa >> ((63 - parts.exponent) & 63); + } else { + return static_cast(parts.mantissa >> 32) >> ((31 - parts.exponent) & 31); + } +} + +int HighestBit(Uint128 value) { + const std::uint64_t high = static_cast(value >> 64); + return high != 0 ? 127 - std::countl_zero(high) : 63 - std::countl_zero(static_cast(value)); +} + +template +TFloat RoundToFloat(bool negative, Uint128 magnitude) { + using Format = FloatFormat; + using Rep = typename Format::Rep; + const Rep sign = negative ? static_cast(1) << (sizeof(Rep) * 8 - 1) : 0; + if (magnitude == 0) return std::bit_cast(sign); + int exponent = HighestBit(magnitude); + Uint128 significand; + if (exponent <= Format::SignificandBits) { + significand = magnitude << (Format::SignificandBits - exponent); + } else { + const int shift = exponent - Format::SignificandBits; + significand = magnitude >> shift; + const Uint128 rest = magnitude & ((static_cast(1) << shift) - 1); + const Uint128 half = static_cast(1) << (shift - 1); + if (rest > half || (rest == half && (significand & 1) != 0)) ++significand; + if (significand >> (Format::SignificandBits + 1) != 0) { + significand >>= 1; + ++exponent; + } + } + const int biased = exponent + Format::Bias; + if (biased >= Format::MaxBiased) return std::bit_cast(static_cast(sign | (static_cast(Format::MaxBiased) << Format::SignificandBits))); + const Rep fraction = static_cast(significand) & ((static_cast(1) << Format::SignificandBits) - 1); + return std::bit_cast(static_cast(sign | (static_cast(biased) << Format::SignificandBits) | fraction)); +} + +long double RoundToExtended(bool negative, Uint128 magnitude) { + if (magnitude == 0) return ComposeExtended(negative, 0, 0); + int exponent = HighestBit(magnitude); + Uint128 significand; + if (exponent <= 63) { + significand = magnitude << (63 - exponent); + } else { + const int shift = exponent - 63; + significand = magnitude >> shift; + const Uint128 rest = magnitude & ((static_cast(1) << shift) - 1); + const Uint128 half = static_cast(1) << (shift - 1); + if (rest > half || (rest == half && (significand & 1) != 0)) ++significand; + if (significand >> 64 != 0) { + significand >>= 1; + ++exponent; + } + } + return ComposeExtended(negative, exponent + 16383, static_cast(significand)); +} + +Uint128 Magnitude(Int128 value) { + return value < 0 ? static_cast(0) - static_cast(value) : static_cast(value); +} + +template +bool IsNan(TFloat value) { + using Format = FloatFormat; + using Rep = typename Format::Rep; + const Rep magnitude = std::bit_cast(value) & (~static_cast(0) >> 1); + return magnitude > (static_cast(Format::MaxBiased) << Format::SignificandBits); +} + +template +TFloat QuietNan() { + using Format = FloatFormat; + using Rep = typename Format::Rep; + return std::bit_cast(static_cast((static_cast(Format::MaxBiased) << Format::SignificandBits) + | (static_cast(1) << (Format::SignificandBits - 1)))); +} + +template +TFloat Negate(TFloat value) { + using Rep = typename FloatFormat::Rep; + return std::bit_cast(static_cast(std::bit_cast(value) ^ (static_cast(1) << (sizeof(Rep) * 8 - 1)))); +} + +template +TFloat GeneratedNan(TFloat result, TFloat a, TFloat b) { + return IsNan(result) && !IsNan(a) && !IsNan(b) ? QuietNan() : result; +} + +double AddDouble(double a, double b) { + std::uint32_t saved = 0; + asm volatile("stmxcsr %[saved]\n\tldmxcsr %[standard]\n\taddsd %[b], %[a]\n\tldmxcsr %[saved]" + : [a] "+x"(a), [saved] "+m"(saved) : [b] "x"(b), [standard] "m"(DefaultMxcsr)); + return a; +} + +double MultiplyDouble(double a, double b) { + std::uint32_t saved = 0; + asm volatile("stmxcsr %[saved]\n\tldmxcsr %[standard]\n\tmulsd %[b], %[a]\n\tldmxcsr %[saved]" + : [a] "+x"(a), [saved] "+m"(saved) : [b] "x"(b), [standard] "m"(DefaultMxcsr)); + return a; +} + +double DivideDouble(double a, double b) { + std::uint32_t saved = 0; + asm volatile("stmxcsr %[saved]\n\tldmxcsr %[standard]\n\tdivsd %[b], %[a]\n\tldmxcsr %[saved]" + : [a] "+x"(a), [saved] "+m"(saved) : [b] "x"(b), [standard] "m"(DefaultMxcsr)); + return a; +} + +float AddFloat(float a, float b) { + std::uint32_t saved = 0; + asm volatile("stmxcsr %[saved]\n\tldmxcsr %[standard]\n\taddss %[b], %[a]\n\tldmxcsr %[saved]" + : [a] "+x"(a), [saved] "+m"(saved) : [b] "x"(b), [standard] "m"(DefaultMxcsr)); + return a; +} + +float MultiplyFloat(float a, float b) { + std::uint32_t saved = 0; + asm volatile("stmxcsr %[saved]\n\tldmxcsr %[standard]\n\tmulss %[b], %[a]\n\tldmxcsr %[saved]" + : [a] "+x"(a), [saved] "+m"(saved) : [b] "x"(b), [standard] "m"(DefaultMxcsr)); + return a; +} + +float DivideFloat(float a, float b) { + std::uint32_t saved = 0; + asm volatile("stmxcsr %[saved]\n\tldmxcsr %[standard]\n\tdivss %[b], %[a]\n\tldmxcsr %[saved]" + : [a] "+x"(a), [saved] "+m"(saved) : [b] "x"(b), [standard] "m"(DefaultMxcsr)); + return a; +} + +constexpr std::uint32_t TowardZeroMxcsr = DefaultMxcsr | 0x6000; + +double DivideDoubleTowardZero(double a, double b) { + std::uint32_t saved = 0; + asm volatile("stmxcsr %[saved]\n\tldmxcsr %[standard]\n\tdivsd %[b], %[a]\n\tldmxcsr %[saved]" + : [a] "+x"(a), [saved] "+m"(saved) : [b] "x"(b), [standard] "m"(TowardZeroMxcsr)); + return a; +} + +float DivideFloatTowardZero(float a, float b) { + std::uint32_t saved = 0; + asm volatile("stmxcsr %[saved]\n\tldmxcsr %[standard]\n\tdivss %[b], %[a]\n\tldmxcsr %[saved]" + : [a] "+x"(a), [saved] "+m"(saved) : [b] "x"(b), [standard] "m"(TowardZeroMxcsr)); + return a; +} + +template +TFloat FlushSubnormalQuotient(TFloat quotient, TFloat truncated) { + using Format = FloatFormat; + using Rep = typename Format::Rep; + const Rep signBit = static_cast(1) << (sizeof(Rep) * 8 - 1); + const Rep magnitude = std::bit_cast(truncated) & ~signBit; + if (magnitude >= (static_cast(1) << Format::SignificandBits)) return quotient; + return std::bit_cast(static_cast(std::bit_cast(truncated) & signBit)); +} + +float TruncateDouble(double value) { + std::uint32_t saved = 0; + float result; + asm volatile("stmxcsr %[saved]\n\tldmxcsr %[standard]\n\tcvtsd2ss %[value], %[result]\n\tldmxcsr %[saved]" + : [result] "=x"(result), [saved] "+m"(saved) : [value] "x"(value), [standard] "m"(DefaultMxcsr)); + return result; +} + +double ExtendFloat(float value) { + const std::uint32_t bits = std::bit_cast(value); + const std::uint64_t sign = static_cast(bits >> 31) << 63; + const std::uint32_t magnitude = bits & 0x7fffffffu; + if (magnitude >= 0x7f800000u) return std::bit_cast(sign | 0x7ff0000000000000ull | (static_cast(magnitude & 0x400000u) << 29) | (magnitude & 0x3fffffu)); + if (magnitude >= 0x00800000u) return std::bit_cast(sign | ((static_cast(magnitude) << 29) + (static_cast(1023 - 127) << 52))); + if (magnitude == 0) return std::bit_cast(sign); + const int shift = std::countl_zero(magnitude) - 8; + const std::uint64_t fraction = static_cast(magnitude << shift) & 0x7fffffu; + const std::uint64_t biased = static_cast(1023 - 126 - shift); + return std::bit_cast(sign | (biased << 52) | (fraction << 29)); +} + +template +int LessEqual(TFloat a, TFloat b, int unordered) { + using Rep = typename FloatFormat::Rep; + using Signed = std::make_signed_t; + if (IsNan(a) || IsNan(b)) return unordered; + const Rep aBits = std::bit_cast(a), bBits = std::bit_cast(b); + const Rep magnitudeMask = ~static_cast(0) >> 1; + if (((aBits | bBits) & magnitudeMask) == 0) return 0; + const Signed aInt = static_cast(aBits), bInt = static_cast(bBits); + if ((aInt & bInt) >= 0) { + if (aInt < bInt) return -1; + return aInt == bInt ? 0 : 1; + } + if (aInt > bInt) return -1; + return aInt == bInt ? 0 : 1; +} + +template +bool IsInfinite(TFloat value) { return std::isinf(value); } + +template +TFloat Box(TFloat value) { return std::copysign(std::isinf(value) ? TFloat(1) : TFloat(0), value); } + +template +TFloat ZeroIfNan(TFloat value) { return std::isnan(value) ? std::copysign(TFloat(0), value) : value; } + +template +TFloat MultiplyAdd(TFloat a, TFloat b, TFloat product) { + if constexpr (Fused) return std::fma(a, b, product); + else return a * b + product; +} + +template +TFloat MultiplySubtract(TFloat a, TFloat b, TFloat product) { + return MultiplyAdd(a, b, std::isnan(product) ? product : -product); +} + +template +void MultiplyComplex(TFloat a, TFloat b, TFloat c, TFloat d, TFloat& real, TFloat& imaginary) { + const TFloat ac = a * c, bd = b * d, ad = a * d, bc = b * c; + real = ac - bd; + imaginary = bc + ad; + if (!std::isnan(real) || !std::isnan(imaginary)) return; + bool recalculate = false; + if (IsInfinite(a) || IsInfinite(b)) { + a = Box(a); + b = Box(b); + c = ZeroIfNan(c); + d = ZeroIfNan(d); + recalculate = true; + } + if (IsInfinite(c) || IsInfinite(d)) { + c = Box(c); + d = Box(d); + a = ZeroIfNan(a); + b = ZeroIfNan(b); + recalculate = true; + } + if (!recalculate && (IsInfinite(ac) || IsInfinite(bd) || IsInfinite(ad) || IsInfinite(bc))) { + a = ZeroIfNan(a); + b = ZeroIfNan(b); + c = ZeroIfNan(c); + d = ZeroIfNan(d); + recalculate = true; + } + if (recalculate) { + real = MultiplySubtract(a, c, b * d) * static_cast(INFINITY); + imaginary = MultiplyAdd(a, d, b * c) * static_cast(INFINITY); + } +} + +template +TFloat Scale(TFloat value, int exponent) { + return static_cast(std::scalbn(static_cast(value), exponent)); +} + +template +TFloat LargerMagnitude(TFloat c, TFloat d) { + const TFloat magnitudeC = std::fabs(c), magnitudeD = std::fabs(d); + if (std::isnan(magnitudeC)) return magnitudeD; + if (std::isnan(magnitudeD)) return magnitudeC; + return magnitudeC < magnitudeD ? magnitudeD : magnitudeC; +} + +template +void DivideComplex(TFloat a, TFloat b, TFloat c, TFloat d, TFloat& real, TFloat& imaginary) { + int scale = 0; + const TFloat logScale = std::logb(LargerMagnitude(c, d)); + if (std::isfinite(logScale)) { + scale = static_cast(logScale); + c = Scale(c, -scale); + d = Scale(d, -scale); + } + const TFloat denominator = MultiplyAdd(c, c, d * d); + real = Scale(MultiplyAdd(c, a, d * b) / denominator, -scale); + imaginary = Scale(MultiplySubtract(c, b, d * a) / denominator, -scale); + if (!std::isnan(real) || !std::isnan(imaginary)) return; + if (denominator == TFloat(0) && (!std::isnan(a) || !std::isnan(b))) { + real = std::copysign(static_cast(INFINITY), c) * a; + imaginary = std::copysign(static_cast(INFINITY), c) * b; + } else if ((IsInfinite(a) || IsInfinite(b)) && std::isfinite(c) && std::isfinite(d)) { + a = Box(a); + b = Box(b); + real = MultiplyAdd(a, c, b * d) * static_cast(INFINITY); + imaginary = MultiplySubtract(b, c, d * a) * static_cast(INFINITY); + } else if (IsInfinite(logScale) && logScale > TFloat(0) && std::isfinite(a) && std::isfinite(b)) { + c = Box(c); + d = Box(d); + real = MultiplyAdd(a, c, d * b) * TFloat(0); + imaginary = MultiplySubtract(b, c, d * a) * TFloat(0); + } +} + +using ComplexFloat = __complex__ float; +using ComplexDouble = __complex__ double; +using ComplexExtended = __complex__ long double; + +ComplexFloat MakeComplex(float real, float imaginary) { + ComplexFloat result; + __real__ result = real; + __imag__ result = imaginary; + return result; +} + +ComplexDouble MakeComplex(double real, double imaginary) { + ComplexDouble result; + __real__ result = real; + __imag__ result = imaginary; + return result; +} + +ComplexExtended MakeComplex(long double real, long double imaginary) { + ComplexExtended result; + __real__ result = real; + __imag__ result = imaginary; + return result; +} + +} + +extern "C" { + +double APS5_VABI __adddf3_nid_postfix(double a, double b) { return GeneratedNan(AddDouble(a, b), a, b); } +float APS5_VABI __addsf3_nid_postfix(float a, float b) { return GeneratedNan(AddFloat(a, b), a, b); } + +double APS5_VABI __subdf3_nid_postfix(double a, double b) { + const double negated = Negate(b); + return GeneratedNan(AddDouble(a, negated), a, negated); +} + +float APS5_VABI __subsf3_nid_postfix(float a, float b) { + const float negated = Negate(b); + return GeneratedNan(AddFloat(a, negated), a, negated); +} + +double APS5_VABI __muldf3_nid_postfix(double a, double b) { return GeneratedNan(MultiplyDouble(a, b), a, b); } +float APS5_VABI __mulsf3_nid_postfix(float a, float b) { return GeneratedNan(MultiplyFloat(a, b), a, b); } +double APS5_VABI __divdf3_nid_postfix(double a, double b) { + if (IsNan(a) || IsNan(b)) return GeneratedNan(DivideDouble(a, b), a, b); + return GeneratedNan(FlushSubnormalQuotient(DivideDouble(a, b), DivideDoubleTowardZero(a, b)), a, b); +} +float APS5_VABI __divsf3_nid_postfix(float a, float b) { + if (IsNan(a) || IsNan(b)) return GeneratedNan(DivideFloat(a, b), a, b); + return GeneratedNan(FlushSubnormalQuotient(DivideFloat(a, b), DivideFloatTowardZero(a, b)), a, b); +} +double APS5_VABI __negdf2_nid_postfix(double a) { return Negate(a); } +float APS5_VABI __negsf2_nid_postfix(float a) { return Negate(a); } + +int APS5_VABI __eqdf2_nid_postfix(double a, double b) { return LessEqual(a, b, 1); } +int APS5_VABI __nedf2_nid_postfix(double a, double b) { return LessEqual(a, b, 1); } +int APS5_VABI __ltdf2_nid_postfix(double a, double b) { return LessEqual(a, b, 1); } +int APS5_VABI __ledf2_nid_postfix(double a, double b) { return LessEqual(a, b, 1); } +int APS5_VABI __gtdf2_nid_postfix(double a, double b) { return LessEqual(a, b, -1); } +int APS5_VABI __gedf2_nid_postfix(double a, double b) { return LessEqual(a, b, -1); } +int APS5_VABI __unorddf2_nid_postfix(double a, double b) { return IsNan(a) || IsNan(b) ? 1 : 0; } +int APS5_VABI __eqsf2_nid_postfix(float a, float b) { return LessEqual(a, b, 1); } +int APS5_VABI __nesf2_nid_postfix(float a, float b) { return LessEqual(a, b, 1); } +int APS5_VABI __ltsf2_nid_postfix(float a, float b) { return LessEqual(a, b, 1); } +int APS5_VABI __lesf2_nid_postfix(float a, float b) { return LessEqual(a, b, 1); } +int APS5_VABI __gtsf2_nid_postfix(float a, float b) { return LessEqual(a, b, -1); } +int APS5_VABI __gesf2_nid_postfix(float a, float b) { return LessEqual(a, b, -1); } +int APS5_VABI __unordsf2_nid_postfix(float a, float b) { return IsNan(a) || IsNan(b) ? 1 : 0; } + +double APS5_VABI __extendsfdf2_nid_postfix(float a) { return ExtendFloat(a); } +float APS5_VABI __truncdfsf2_nid_postfix(double a) { + if (!IsNan(a)) return TruncateDouble(a); + const std::uint64_t bits = std::bit_cast(a); + return std::bit_cast(static_cast(((bits >> 32) & 0x80000000u) | 0x7fc00000u | (bits & 0x3fffffu))); +} + +std::int32_t APS5_VABI __fixsfsi_nid_postfix(float a) { return FixSigned(a); } +std::int64_t APS5_VABI __fixsfdi_nid_postfix(float a) { return FixSigned(a); } +Int128 APS5_VABI __fixsfti_nid_postfix(float a) { return FixSigned(a); } +std::int32_t APS5_VABI __fixdfsi_nid_postfix(double a) { return FixSigned(a); } +std::int64_t APS5_VABI __fixdfdi_nid_postfix(double a) { return FixSigned(a); } +Int128 APS5_VABI __fixdfti_nid_postfix(double a) { return FixSigned(a); } +std::uint32_t APS5_VABI __fixunssfsi_nid_postfix(float a) { return FixUnsigned(a); } +std::uint64_t APS5_VABI __fixunssfdi_nid_postfix(float a) { return FixUnsigned(a); } +Uint128 APS5_VABI __fixunssfti_nid_postfix(float a) { return FixUnsigned(a); } + +std::uint32_t APS5_VABI __fixunsdfsi_nid_postfix(double a) { + const Decomposed parts = Decompose(a); + if (parts.negative || parts.exponent < 0) return 0; + return static_cast(parts.significand >> 21) >> ((31 - parts.exponent) & 31); +} + +std::uint64_t APS5_VABI __fixunsdfdi_nid_postfix(double a) { return FixUnsigned(a); } +Uint128 APS5_VABI __fixunsdfti_nid_postfix(double a) { return FixUnsigned(a); } +std::int64_t APS5_VABI __fixxfdi_nid_postfix(long double a) { return FixExtendedSigned(a); } +Int128 APS5_VABI __fixxfti_nid_postfix(long double a) { return FixExtendedSigned(a); } +std::uint32_t APS5_VABI __fixunsxfsi_nid_postfix(long double a) { return FixExtendedUnsigned(a); } +std::uint64_t APS5_VABI __fixunsxfdi_nid_postfix(long double a) { return FixExtendedUnsigned(a); } +Uint128 APS5_VABI __fixunsxfti_nid_postfix(long double a) { return FixExtendedUnsigned(a); } + +double APS5_VABI __floatsidf_nid_postfix(std::int32_t a) { return static_cast(a); } +double APS5_VABI __floatunsidf_nid_postfix(std::uint32_t a) { return static_cast(a); } +float APS5_VABI __floatsisf_nid_postfix(std::int32_t a) { return RoundToFloat(a < 0, Magnitude(a)); } +float APS5_VABI __floatunsisf_nid_postfix(std::uint32_t a) { return RoundToFloat(false, a); } +double APS5_VABI __floatdidf_nid_postfix(std::int64_t a) { return static_cast(a); } +float APS5_VABI __floatdisf_nid_postfix(std::int64_t a) { return static_cast(a); } +long double APS5_VABI __floatdixf_nid_postfix(std::int64_t a) { return static_cast(a); } +double APS5_VABI __floatundidf_nid_postfix(std::uint64_t a) { return static_cast(a); } +float APS5_VABI __floatundisf_nid_postfix(std::uint64_t a) { return static_cast(a); } +long double APS5_VABI __floatundixf_nid_postfix(std::uint64_t a) { return static_cast(a); } +double APS5_VABI __floattidf_nid_postfix(Int128 a) { return RoundToFloat(a < 0, Magnitude(a)); } +float APS5_VABI __floattisf_nid_postfix(Int128 a) { return RoundToFloat(a < 0, Magnitude(a)); } +long double APS5_VABI __floattixf_nid_postfix(Int128 a) { return RoundToExtended(a < 0, Magnitude(a)); } +double APS5_VABI __floatuntidf_nid_postfix(Uint128 a) { return RoundToFloat(false, a); } +float APS5_VABI __floatuntisf_nid_postfix(Uint128 a) { return RoundToFloat(false, a); } +long double APS5_VABI __floatuntixf_nid_postfix(Uint128 a) { return RoundToExtended(false, a); } + +__complex__ float APS5_VABI __mulsc3_nid_postfix(float a, float b, float c, float d) { + float real, imaginary; + MultiplyComplex(a, b, c, d, real, imaginary); + return MakeComplex(real, imaginary); +} + +__complex__ double APS5_VABI __muldc3_nid_postfix(double a, double b, double c, double d) { + double real, imaginary; + MultiplyComplex(a, b, c, d, real, imaginary); + return MakeComplex(real, imaginary); +} + +__complex__ long double APS5_VABI __mulxc3_nid_postfix(long double a, long double b, long double c, long double d) { + long double real, imaginary; + MultiplyComplex(a, b, c, d, real, imaginary); + return MakeComplex(real, imaginary); +} + +__complex__ float APS5_VABI __divsc3_nid_postfix(float a, float b, float c, float d) { + float real, imaginary; + DivideComplex(a, b, c, d, real, imaginary); + return MakeComplex(real, imaginary); +} + +__complex__ double APS5_VABI __divdc3_nid_postfix(double a, double b, double c, double d) { + double real, imaginary; + DivideComplex(a, b, c, d, real, imaginary); + return MakeComplex(real, imaginary); +} + +__complex__ long double APS5_VABI __divxc3_nid_postfix(long double a, long double b, long double c, long double d) { + long double real, imaginary; + DivideComplex(a, b, c, d, real, imaginary); + return MakeComplex(real, imaginary); +} + +long double APS5_VABI __powixf2_nid_postfix(long double base, int exponent) { + const bool reciprocal = exponent < 0; + long double result = (exponent & 1) != 0 ? base : 1.0L; + for (exponent /= 2; exponent != 0; exponent /= 2) { + base *= base; + if (exponent & 1) result *= base; + } + return reciprocal ? 1 / result : result; +} + +} diff --git a/core/libs/prx/libc/src/exception/Unwind.cpp b/core/libs/prx/libc/src/exception/Unwind.cpp index 6aac4ddddbed..fe5883725a03 100644 --- a/core/libs/prx/libc/src/exception/Unwind.cpp +++ b/core/libs/prx/libc/src/exception/Unwind.cpp @@ -2,6 +2,7 @@ #include "prx/libc/include/exceptions/Runtime.hpp" #include "prx/libc/include/specifics/linux/ElfTypes.hpp" #include "prx/libc/src/specifics/x86_64/RegisterContext.cpp" +#include #if defined(__linux__) #include @@ -21,73 +22,6 @@ #include #endif -namespace { -constexpr int CursorInstructionRegister = -1; -constexpr int CursorStackRegister = -2; -constexpr int CursorRegisterCount = 17; - -std::uintptr_t* CursorRegister(_Unwind_Context* context, int number) { - if (number == CursorInstructionRegister) return &context->registers[16]; - if (number == CursorStackRegister) return &context->registers[7]; - if (number >= 0 && number < CursorRegisterCount) return &context->registers[number]; - return nullptr; -} - -bool APS5_VABI CursorValidRegister(_Unwind_Context* context, int number) { - return CursorRegister(context, number) != nullptr; -} - -std::uintptr_t APS5_VABI CursorGetRegister(_Unwind_Context* context, int number) { - auto* value = CursorRegister(context, number); - if (!value) NotImplemented_nid_no_patch("libunwind cursor: read of an unknown register"); - return value ? *value : 0; -} - -void APS5_VABI CursorSetRegister(_Unwind_Context* context, int number, std::uintptr_t value) { - auto* slot = CursorRegister(context, number); - if (!slot) NotImplemented_nid_no_patch("libunwind cursor: write of an unknown register"); - else *slot = value; -} - -bool APS5_VABI CursorValidFloatRegister(_Unwind_Context*, int) { - return false; -} - -bool APS5_VABI CursorIsSignalFrame(_Unwind_Context* context) { - return context->signalFrame; -} - -void APS5_VABI CursorUnsupported(_Unwind_Context*) { - NotImplemented_nid_no_patch("libunwind cursor: operation other than register access or isSignalFrame"); -} - -template std::uintptr_t Slot(Function function) { - return reinterpret_cast(function); -} - -const std::uintptr_t CursorSlots[] = { - Slot(&CursorUnsupported), - Slot(&CursorUnsupported), - Slot(&CursorValidRegister), - Slot(&CursorGetRegister), - Slot(&CursorSetRegister), - Slot(&CursorValidFloatRegister), - Slot(&CursorUnsupported), - Slot(&CursorUnsupported), - Slot(&CursorUnsupported), - Slot(&CursorUnsupported), - Slot(&CursorUnsupported), - Slot(&CursorIsSignalFrame), - Slot(&CursorUnsupported), - Slot(&CursorUnsupported), - Slot(&CursorUnsupported), -}; -} - -const std::uintptr_t* UnwindCursorSlots_nid_no_patch() { - return CursorSlots; -} - #if defined(__linux__) || defined(_WIN32) || defined(__APPLE__) extern "C" _Unwind_Reason_Code __gxx_personality_v0(int, _Unwind_Action, std::uint64_t, _Unwind_Exception*, _Unwind_Context*); @@ -117,7 +51,10 @@ int FindFrame(dl_phdr_info* info, std::size_t, void* argument) { for (unsigned i = 0; i < info->dlpi_phnum; ++i) { const auto& ph = info->dlpi_phdr[i]; Word start = info->dlpi_addr + ph.p_vaddr; - if (ph.p_type == PT_LOAD && query.pc >= start && query.pc - start < ph.p_memsz) contains = true; + if (ph.p_type == PT_LOAD && query.pc >= start && query.pc - start < ph.p_memsz) { + contains = true; + query.segment = start; + } if (ph.p_type == PT_GNU_EH_FRAME) header = reinterpret_cast(start); if (ph.p_type == PT_LOAD && (ph.p_flags & PF_X)) query.text = start; if (ph.p_type == PT_LOAD && (ph.p_flags & PF_W)) query.data = start; @@ -163,6 +100,10 @@ bool DecodeCandidate(_Unwind_Context& context, Frame& frame, const Lookup& query context.personality = frame.personality; context.textBase = query.text; context.dataBase = query.data; + context.regionEnd = frame.start + frame.length; + context.frameEntry = Word(query.fde); + context.frameEntrySize = Word(frame.end - query.fde); + context.segment = query.segment; return true; } @@ -208,6 +149,30 @@ const IMAGE_NT_HEADERS64* ImageHeaders(Word pc, const Byte*& base) { return nt->Signature == IMAGE_NT_SIGNATURE ? nt : nullptr; } +bool NearestExport(Word pc, const char*& name, Word& address) { + const Byte* base = nullptr; + const auto* nt = ImageHeaders(pc, base); + if (!nt) return false; + const auto& directory = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; + if (!directory.VirtualAddress || !directory.Size) return false; + const auto* exports = reinterpret_cast(base + directory.VirtualAddress); + const auto* functions = reinterpret_cast(base + exports->AddressOfFunctions); + const auto* names = reinterpret_cast(base + exports->AddressOfNames); + const auto* ordinals = reinterpret_cast(base + exports->AddressOfNameOrdinals); + const Word rva = pc - Word(base); + name = nullptr; + DWORD nearest = 0; + for (DWORD i = 0; i < exports->NumberOfNames; ++i) { + if (ordinals[i] >= exports->NumberOfFunctions) continue; + const DWORD target = functions[ordinals[i]]; + if (target - directory.VirtualAddress < directory.Size || target > rva || (name && target <= nearest)) continue; + name = reinterpret_cast(base + names[i]); + nearest = target; + } + address = Word(base) + nearest; + return name != nullptr; +} + bool VirtualUnwind(const _Unwind_Context& context, _Unwind_Context& caller) { const Word pc = context.registers[16]; const Byte* base = nullptr; @@ -243,6 +208,7 @@ bool DecodeFrame(_Unwind_Context& context, Frame& frame) { if (!query.fde) { Dl_info image {}; if (!dladdr(reinterpret_cast(query.pc), &image) || !image.dli_fbase) return false; + query.segment = Word(image.dli_fbase); unsigned long size = 0; const Byte* p = getsectiondata(static_cast(image.dli_fbase), "__TEXT", "__eh_frame", &size); if (!p) return false; @@ -269,6 +235,10 @@ bool DecodeFrame(_Unwind_Context& context, Frame& frame) { const auto* nt = ImageHeaders(query.pc, base); if (!nt) return false; const auto* sections = IMAGE_FIRST_SECTION(nt); + for (unsigned i = 0; i < nt->FileHeader.NumberOfSections; ++i) { + const Word start = Word(base) + sections[i].VirtualAddress; + if (query.pc >= start && query.pc - start < sections[i].Misc.VirtualSize) query.segment = start; + } for (unsigned i = 0; i < nt->FileHeader.NumberOfSections; ++i) { const auto& section = sections[i]; if (std::memcmp(section.Name, ".ehmeta", 8) == 0) { @@ -358,14 +328,18 @@ bool Expression(const Byte* p, const _Unwind_Context& context, Word cfa, Word& r result = stack[0]; return true; } -bool GetRules(_Unwind_Context& context, Frame& frame, Rules& rules) { +void ClearInfo(_Unwind_Context& context) { + context.region = context.lsda = context.personality = context.textBase = context.dataBase = 0; + context.regionEnd = context.frameEntry = context.frameEntrySize = context.argumentsSize = context.segment = 0; +} + +bool FrameRules(_Unwind_Context& context, Frame& frame, Rules& rules) { if (!DecodeFrame(context, frame)) { #ifdef _WIN32 frame = {}; if (!VirtualUnwind(context, frame.caller)) return false; frame.foreign = true; context.cfa = frame.caller.registers[7]; - context.region = context.lsda = context.personality = context.textBase = context.dataBase = 0; return true; #else return false; @@ -382,24 +356,28 @@ bool GetRules(_Unwind_Context& context, Frame& frame, Rules& rules) { #endif rules = initial; if (!Instructions(frame.begin, frame.end, frame, context.registers[16] - !context.signalFrame, rules, initial)) return false; + context.argumentsSize = rules.argumentsSize; if (rules.cfaExpression) return Expression(rules.cfaExpression, context, 0, context.cfa); if (rules.cfaRegister >= 17) return false; context.cfa = context.registers[rules.cfaRegister] + rules.cfaOffset; return true; } -bool Step(_Unwind_Context& context) { - Frame frame; Rules rules; - if (!GetRules(context, frame, rules)) return false; +bool GetRules(_Unwind_Context& context, Frame& frame, Rules& rules) { + ClearInfo(context); + context.infoMissing = !FrameRules(context, frame, rules); + if (context.infoMissing) ClearInfo(context); + return !context.infoMissing; +} + +bool Caller(const _Unwind_Context& context, const Frame& frame, const Rules& rules, _Unwind_Context& next) { #ifdef _WIN32 if (frame.foreign) { - const auto& next = frame.caller; - if (!next.registers[16] || (next.registers[7] == context.registers[7] && next.registers[16] == context.registers[16])) return false; - context = next; + next = frame.caller; return true; } #endif - _Unwind_Context next = context; + next = context; for (unsigned i = 0; i < 17; ++i) { const auto& rule = rules.registers[i]; Word value; @@ -434,7 +412,18 @@ bool Step(_Unwind_Context& context) { next.registers[7] = context.cfa; next.registers[16] = next.registers[frame.returnRegister]; next.signalFrame = frame.signal; - if (!next.registers[16] || (next.registers[7] == context.registers[7] && next.registers[16] == context.registers[16])) return false; + return true; +} + +bool Moved(const _Unwind_Context& context, const _Unwind_Context& next) { + return next.registers[7] != context.registers[7] || next.registers[16] != context.registers[16]; +} + +bool Step(_Unwind_Context& context) { + Frame frame; Rules rules; + _Unwind_Context next; + if (!GetRules(context, frame, rules) || !Caller(context, frame, rules, next)) return false; + if (!next.registers[16] || !Moved(context, next)) return false; context = next; return true; } @@ -475,6 +464,181 @@ _Unwind_Reason_Code PhaseTwo(_Unwind_Context context, _Unwind_Exception* excepti } } +namespace { +constexpr int CursorInstructionRegister = -1; +constexpr int CursorStackRegister = -2; +constexpr int CursorRegisterCount = 17; +constexpr int CursorStepSuccess = 1; +constexpr int CursorStepEnd = 0; +constexpr int CursorBadFrame = -6546; +constexpr std::uint32_t CursorDwarfFormat = 0x04000000; + +struct CursorProcedureInfo { + std::uintptr_t start; + std::uintptr_t end; + std::uintptr_t lsda; + std::uintptr_t handler; + std::uintptr_t gp; + std::uintptr_t flags; + std::uint32_t format; + std::uint32_t unwindInfoSize; + std::uintptr_t unwindInfo; + std::uintptr_t extra; +}; + +static_assert(sizeof(CursorProcedureInfo) == 72); + +[[noreturn]] void CursorAbort(const char* function, const char* message) { + std::fprintf(stderr, "libunwind: %s - %s\n", function, message); + std::fflush(stderr); + std::abort(); +} + +std::uintptr_t* CursorRegister(_Unwind_Context* context, int number) { + if (number == CursorInstructionRegister) return &context->registers[16]; + if (number == CursorStackRegister) return &context->registers[7]; + if (number >= 0 && number < CursorRegisterCount) return &context->registers[number]; + return nullptr; +} + +void CursorDescribe(_Unwind_Context& context) { + LibcUnwind::Frame frame; + LibcUnwind::Rules rules; + if (context.registers[16]) { + LibcUnwind::GetRules(context, frame, rules); + return; + } + LibcUnwind::ClearInfo(context); + context.infoMissing = true; +} + +void APS5_VABI CursorDestroy(_Unwind_Context*) {} + +bool APS5_VABI CursorValidRegister(_Unwind_Context* context, int number) { + return CursorRegister(context, number) != nullptr; +} + +std::uintptr_t APS5_VABI CursorGetRegister(_Unwind_Context* context, int number) { + auto* value = CursorRegister(context, number); + if (!value) CursorAbort("getRegister", "unsupported x86_64 register"); + return *value; +} + +void APS5_VABI CursorSetRegister(_Unwind_Context* context, int number, std::uintptr_t value) { + auto* slot = CursorRegister(context, number); + if (!slot) CursorAbort("setRegister", "unsupported x86_64 register"); + *slot = value; +} + +bool APS5_VABI CursorValidFloatRegister(_Unwind_Context*, int) { + return false; +} + +double APS5_VABI CursorGetFloatRegister(_Unwind_Context*, int) { + CursorAbort("getFloatRegister", "no x86_64 float registers"); +} + +void APS5_VABI CursorSetFloatRegister(_Unwind_Context*, int, double) { + CursorAbort("setFloatRegister", "no x86_64 float registers"); +} + +int APS5_VABI CursorStep(_Unwind_Context* context, bool) { + if (context->infoMissing) return CursorStepEnd; + LibcUnwind::Frame frame; + LibcUnwind::Rules rules; + _Unwind_Context current = *context; + _Unwind_Context caller; + if (!LibcUnwind::GetRules(current, frame, rules) || !LibcUnwind::Caller(current, frame, rules, caller)) return CursorBadFrame; + if (caller.registers[16] && !LibcUnwind::Moved(current, caller)) return CursorStepEnd; + CursorDescribe(caller); + *context = caller; + return context->infoMissing ? CursorStepEnd : CursorStepSuccess; +} + +void APS5_VABI CursorGetInfo(_Unwind_Context* context, CursorProcedureInfo* info) { + *info = {}; + if (context->infoMissing) return; + info->start = context->region; + info->end = context->regionEnd; + info->lsda = context->lsda; + info->handler = context->personality; + info->gp = context->argumentsSize; + info->format = context->frameEntry ? CursorDwarfFormat : 0; + info->unwindInfoSize = static_cast(context->frameEntrySize); + info->unwindInfo = context->frameEntry; + info->extra = context->segment; +} + +[[noreturn]] void APS5_VABI CursorJumpTo(_Unwind_Context* context) { + LibcRestoreRegisters(context->registers); +} + +bool APS5_VABI CursorIsSignalFrame(_Unwind_Context* context) { + return context->signalFrame; +} + +bool APS5_VABI CursorGetFunctionName(_Unwind_Context* context, char* buffer, std::size_t length, std::uintptr_t* offset) { + const auto pc = context->registers[16]; +#ifdef _WIN32 + const char* name = nullptr; + LibcUnwind::Word address = 0; + if (!LibcUnwind::NearestExport(pc, name, address)) return false; +#else + Dl_info image {}; + if (!dladdr(reinterpret_cast(pc), &image) || !image.dli_sname) return false; + const char* name = image.dli_sname; + const auto address = reinterpret_cast(image.dli_saddr); +#endif + std::snprintf(buffer, length, "%s", name); + *offset = pc - address; + return true; +} + +void APS5_VABI CursorSetInfoBasedOnIPRegister(_Unwind_Context* context, bool returnAddress) { + _Unwind_Context described = *context; + described.signalFrame = !returnAddress; + CursorDescribe(described); + described.signalFrame = context->signalFrame; + described.cfa = context->cfa; + *context = described; +} + +const char* APS5_VABI CursorGetRegisterName(_Unwind_Context*, int number) { + static constexpr const char* names[] {"rax", "rdx", "rcx", "rbx", "rsi", "rdi", "rbp", "rsp", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", "rip", + "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15"}; + if (number == CursorInstructionRegister) return "rip"; + if (number == CursorStackRegister) return "rsp"; + if (number >= 0 && number < static_cast(sizeof(names) / sizeof(names[0]))) return names[number]; + return "unknown register"; +} + +template std::uintptr_t Slot(Function function) { + return reinterpret_cast(function); +} + +const std::uintptr_t CursorSlots[] = { + Slot(&CursorDestroy), + Slot(&CursorDestroy), + Slot(&CursorValidRegister), + Slot(&CursorGetRegister), + Slot(&CursorSetRegister), + Slot(&CursorValidFloatRegister), + Slot(&CursorGetFloatRegister), + Slot(&CursorSetFloatRegister), + Slot(&CursorStep), + Slot(&CursorGetInfo), + Slot(&CursorJumpTo), + Slot(&CursorIsSignalFrame), + Slot(&CursorGetFunctionName), + Slot(&CursorSetInfoBasedOnIPRegister), + Slot(&CursorGetRegisterName), +}; +} + +const std::uintptr_t* UnwindCursorSlots_nid_no_patch() { + return CursorSlots; +} + extern "C" { _Unwind_Reason_Code APS5_VABI _Unwind_RaiseException_nid_postfix(_Unwind_Exception* exception) { _Unwind_Context start; @@ -567,6 +731,10 @@ _Unwind_Reason_Code APS5_VABI _Unwind_Backtrace_nid_postfix(_Unwind_Trace_Fn tra #else +const std::uintptr_t* UnwindCursorSlots_nid_no_patch() { + return nullptr; +} + extern "C" { _Unwind_Reason_Code APS5_VABI _Unwind_RaiseException_nid_postfix(_Unwind_Exception*) { std::abort(); } [[noreturn]] void APS5_VABI _Unwind_Resume_nid_postfix(_Unwind_Exception*) { std::abort(); } diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index aee221ee47a8..ad0e6c4e9c69 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -663,6 +663,16 @@ if(BUILD_TESTING) set_tests_properties(guest_pthread_rwlock PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_rwlock_retire_tests ${LIBS_INCLUDE_DIR}/tests/GuestRwlockRetire.cpp) + target_include_directories(guest_rwlock_retire_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_rwlock_retire_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_rwlock_retire_tests) + add_test(NAME guest_rwlock_retire COMMAND guest_rwlock_retire_tests) + set_tests_properties(guest_rwlock_retire PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_rwlock_retire PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_pthread_join_self_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadJoinSelf.cpp) target_include_directories(guest_pthread_join_self_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_pthread_join_self_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index f06fe88fd966..d40afaeb68c8 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -1339,14 +1339,46 @@ bool GuestProtection(uintptr_t addr, int* prot) { } void GuestPageTableUsage(std::uint64_t granularity, std::uint64_t* cpuEntries, std::uint64_t* gpuEntries) { + constexpr std::uint64_t GpuDirectorySpan = 1ULL << 30; + struct DirectSpan { + std::uintptr_t start; + std::uintptr_t end; + std::uint64_t phys; + }; + std::vector direct; + { + std::lock_guard lock(g_directLock); + direct.reserve(g_directMappings.size()); + for (const auto& [start, mapping] : g_directMappings) direct.push_back({start, mapping.end, mapping.phys}); + } + const auto largePage = [&](std::uintptr_t granule) { + const auto next = std::upper_bound(direct.begin(), direct.end(), granule, [](std::uintptr_t address, const DirectSpan& span) { return address < span.start; }); + if (next == direct.begin()) return false; + const auto& span = *std::prev(next); + return granule + granularity <= span.end && (span.phys + (granule - span.start)) % granularity == 0; + }; std::uint64_t cpu = 0; std::uint64_t gpu = 0; + std::uint64_t cpuNext = 0; + std::uint64_t gpuNext = 0; + std::uint64_t directoryNext = 0; std::lock_guard lock(g_protectionLock); for (const auto& [start, range] : g_protections) { if (range.end <= start) continue; - const auto entries = (range.end - 1) / granularity - start / granularity + 1; - if (range.prot & 0x7) cpu += entries; - if (range.prot & GuestProtGpuReadWrite) gpu += entries; + const auto first = start / granularity; + const auto last = (range.end - 1) / granularity; + if (last + 1 > std::max(first, cpuNext)) cpu += last + 1 - std::max(first, cpuNext); + cpuNext = std::max(cpuNext, last + 1); + if ((range.prot & GuestProtGpuReadWrite) == 0) continue; + const auto firstDirectory = start / GpuDirectorySpan; + const auto lastDirectory = (range.end - 1) / GpuDirectorySpan; + if (lastDirectory + 1 > std::max(firstDirectory, directoryNext)) gpu += lastDirectory + 1 - std::max(firstDirectory, directoryNext); + directoryNext = std::max(directoryNext, lastDirectory + 1); + for (auto granule = std::max(first, gpuNext); granule <= last; ++granule) { + const auto address = static_cast(granule * granularity); + if (address < start || address + granularity > range.end || !largePage(address)) ++gpu; + } + gpuNext = std::max(gpuNext, last + 1); } *cpuEntries = cpu; *gpuEntries = gpu; diff --git a/core/libs/prx/libkernel/DirectMemory/Export.cpp b/core/libs/prx/libkernel/DirectMemory/Export.cpp index 3ed9487a6185..435744b9dbf8 100644 --- a/core/libs/prx/libkernel/DirectMemory/Export.cpp +++ b/core/libs/prx/libkernel/DirectMemory/Export.cpp @@ -109,6 +109,20 @@ size_t _flexibleUsedLocked() { return used; } +void _releaseFlexibleLocked(uintptr_t start, size_t len) { + const uintptr_t end = start + len; + auto it = g_flexibleRanges.upper_bound(start); + if (it != g_flexibleRanges.begin()) --it; + while (it != g_flexibleRanges.end() && it->first < end) { + const uintptr_t rangeStart = it->first; + const uintptr_t rangeEnd = rangeStart + it->second; + if (rangeEnd <= start) { ++it; continue; } + it = g_flexibleRanges.erase(it); + if (rangeStart < start) g_flexibleRanges[rangeStart] = start - rangeStart; + if (rangeEnd > end) g_flexibleRanges[end] = rangeEnd - end; + } +} + int _mapFlexible(void** addr, size_t len, int prot, int flags) { const size_t configured = _configuredFlexibleSize(); const size_t images = GuestImageSegmentBytes_nid_no_patch(); @@ -119,23 +133,17 @@ int _mapFlexible(void** addr, size_t len, int prot, int flags) { return SCE_KERNEL_ERROR_ENOMEM; } const int result = DoMapAnon(addr, len, prot, flags); - if (result == 0) g_flexibleRanges[reinterpret_cast(*addr)] = len; + if (result == 0) { + const auto start = reinterpret_cast(*addr); + _releaseFlexibleLocked(start, len); + g_flexibleRanges[start] = len; + } return result; } void _releaseFlexible(uintptr_t start, size_t len) { std::lock_guard lock(g_flexibleLock); - const uintptr_t end = start + len; - auto it = g_flexibleRanges.upper_bound(start); - if (it != g_flexibleRanges.begin()) --it; - while (it != g_flexibleRanges.end() && it->first < end) { - const uintptr_t rangeStart = it->first; - const uintptr_t rangeEnd = rangeStart + it->second; - if (rangeEnd <= start) { ++it; continue; } - it = g_flexibleRanges.erase(it); - if (rangeStart < start) g_flexibleRanges[rangeStart] = start - rangeStart; - if (rangeEnd > end) g_flexibleRanges[end] = rangeEnd - end; - } + _releaseFlexibleLocked(start, len); } std::mutex g_lockedRangesLock; @@ -232,12 +240,16 @@ int APS5_VABI sceKernelEnableDmemAliasing(void) { int APS5_VABI sceKernelMapDirectMemory(void** addr, size_t len, int prot, int flags, int64_t direct_memory_start, size_t alignment) { (void)alignment; - return DoMapDirect(addr, len, prot, flags, direct_memory_start, alignment); + const int result = DoMapDirect(addr, len, prot, flags, direct_memory_start, alignment); + if (result == 0) _releaseFlexible(reinterpret_cast(*addr), len); + return result; } int APS5_VABI sceKernelMapDirectMemory2(void** addr, size_t len, int type, int prot, int flags, int64_t direct_memory_start, size_t alignment) { (void)type; - return DoMapDirect(addr, len, prot, flags, direct_memory_start, alignment); + const int result = DoMapDirect(addr, len, prot, flags, direct_memory_start, alignment); + if (result == 0) _releaseFlexible(reinterpret_cast(*addr), len); + return result; } int APS5_VABI sceKernelMapFlexibleMemory(void** addr_in_out, size_t len, int prot, int flags) { @@ -249,6 +261,7 @@ int APS5_VABI sceKernelSetVirtualRangeName(const void* addr, uint64_t len, const int APS5_VABI sceKernelMapNamedDirectMemory(void** addr, size_t len, int prot, int flags, int64_t direct_memory_start, size_t alignment, const char* name) { const int result = DoMapDirect(addr, len, prot, flags, direct_memory_start, alignment); + if (result == 0) _releaseFlexible(reinterpret_cast(*addr), len); if (result == 0 && name) sceKernelSetVirtualRangeName(*addr, len, name); return result; } @@ -301,7 +314,9 @@ int APS5_VABI sceKernelReleaseDirectMemory(int64_t start, size_t len) { } int APS5_VABI sceKernelReserveVirtualRange(void** addr, size_t len, int flags, size_t alignment) { - return DoReserveVirtual(addr, len, flags, alignment); + const int result = DoReserveVirtual(addr, len, flags, alignment); + if (result == 0) _releaseFlexible(reinterpret_cast(*addr), len); + return result; } int APS5_VABI sceKernelVirtualQuery(const void* addr, int flags, VirtualQueryInfo* info, uint64_t info_size) { @@ -467,7 +482,7 @@ int APS5_VABI sceKernelClearVirtualRangeName(const void* addr, uint64_t len) { int APS5_VABI sceKernelGetPageTableStats(int* cpu_total, int* cpu_available, int* gpu_total, int* gpu_available) { if (!cpu_total || !cpu_available || !gpu_total || !gpu_available) return SCE_KERNEL_ERROR_EFAULT; constexpr std::uint64_t PageTableGranularity = 2ULL * 1024 * 1024; - constexpr std::uint64_t PageTablePoolEntries = 2048; + constexpr std::uint64_t PageTablePoolEntries = 8192; std::uint64_t cpuUsed = 0; std::uint64_t gpuUsed = 0; GuestPageTableUsage(PageTableGranularity, &cpuUsed, &gpuUsed); @@ -518,6 +533,7 @@ int APS5_VABI sceKernelBatchMap2(KernelBatchMapEntry* entries, int num_entries, switch (entry.operation) { case OpMapDirect: result = DoMapDirect(&entry.start, entry.length, static_cast(entry.protection), flags, static_cast(entry.offset), 0); + if (result == 0) _releaseFlexible(reinterpret_cast(entry.start), entry.length); break; case OpUnmap: result = sceKernelMunmap(reinterpret_cast(entry.start), entry.length); diff --git a/core/libs/prx/libkernel/File/src/Open.cpp b/core/libs/prx/libkernel/File/src/Open.cpp index 1ac2b039beec..0db45fa68ff8 100644 --- a/core/libs/prx/libkernel/File/src/Open.cpp +++ b/core/libs/prx/libkernel/File/src/Open.cpp @@ -157,8 +157,7 @@ extern "C" int APS5_VABI fcntl_nid_postfix(int descriptor, int command, ...); static constexpr int GuestSetFlags = 4; static int SceErrorFromErrno(int error) { - constexpr int GuestEio = 5; - const int guest = error > 0 && error <= 34 ? error : GuestEio; + const int guest = error > 0 && error <= 34 ? error : FilesystemError(std::error_code(error, std::generic_category())); return static_cast(0x80020000u | static_cast(guest)); } @@ -330,6 +329,7 @@ int APS5_VABI sceKernelStat(const char* path, FileStat* sb) { constexpr int GuestEfault = 14; const auto status = std::filesystem::status(native, error); if (error == std::errc::not_a_directory) return SceErrorFromErrno(GuestEnotdir); + if (error == std::errc::filename_too_long || error == std::errc::too_many_symbolic_link_levels) return SceKernelError(FilesystemError(error)); if (!std::filesystem::exists(status)) { return SceErrorFromErrno(2); } diff --git a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp index f436b5a4c298..17b35514a964 100644 --- a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp @@ -71,6 +71,8 @@ struct PthreadSemPrivate { std::mutex _mutex; TimedWait::Condition _cv; int _count = 0; + int _waiters = 0; + bool _destroyed = false; explicit PthreadSemPrivate(unsigned int value) : _count(static_cast(value)) {} }; diff --git a/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp b/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp index a77cd365f3af..35a043a8e90b 100644 --- a/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp @@ -138,7 +138,6 @@ int APS5_VABI scePthreadRwlockTimedwrlock(PthreadRwlock* rwlock, KernelUseconds const bool acquired = lock->_changed.WaitUntil(state, deadline, [&] { return WriterMayEnter(lock); }); --lock->_waitingWriters; if (acquired) return EnterWrite(lock); - state.unlock(); lock->_changed.NotifyAll(); return SCE_KERNEL_ERROR_ETIMEDOUT; } @@ -155,7 +154,6 @@ int APS5_VABI scePthreadRwlockUnlock(PthreadRwlock* rwlock) { if (readLocksHeld != 0) --readLocksHeld; if (lock->_readers.fetch_sub(1, std::memory_order_acq_rel) != 1) return SCE_OK; } - state.unlock(); lock->_changed.NotifyAll(); return SCE_OK; } diff --git a/core/libs/prx/libkernel/Pthread/src/Sem.cpp b/core/libs/prx/libkernel/Pthread/src/Sem.cpp index ea9006380e26..0b0113684e3c 100644 --- a/core/libs/prx/libkernel/Pthread/src/Sem.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Sem.cpp @@ -3,10 +3,13 @@ #include "prx/libc/include/General.hpp" #include "prx/libkernel/KernelErrors.hpp" #include "prx/libkernel/Time/include/TimedWait.hpp" +#include #include #include #include #include +#include +#include namespace { @@ -14,19 +17,59 @@ constexpr int sceBusy = static_cast(0x80020010u); constexpr int sceTimedOut = static_cast(0x8002003cu); constexpr int sceSemValueMax = 0x7fffffff; std::mutex semInitializationMutex; +std::unordered_map> semRegistry; +std::atomic semApiPauseTarget{0}; +std::atomic semApiPauseEntered{0}; +std::atomic semApiPauseReleased{true}; + +void PauseSemApi(int kind) { + if (semApiPauseTarget.load(std::memory_order_acquire) != kind) return; + semApiPauseEntered.fetch_add(1, std::memory_order_acq_rel); + while (!semApiPauseReleased.load(std::memory_order_acquire)) std::this_thread::yield(); +} PthreadSem destroyedSem() { return reinterpret_cast(std::uintptr_t{2}); } -PthreadSemPrivate* resolveSem(PthreadSem* sem) { +std::shared_ptr resolveSem(PthreadSem* sem) { if (!sem) throw std::invalid_argument("Semaphore pointer is null"); - std::atomic_ref slot(*sem); - const auto current = slot.load(std::memory_order_acquire); + std::lock_guard lock(semInitializationMutex); + const auto current = std::atomic_ref(*sem).load(std::memory_order_acquire); if (!current || current == destroyedSem()) throw std::runtime_error("Semaphore is not initialized or has been destroyed"); - return current; + const auto it = semRegistry.find(current); + if (it == semRegistry.end()) + throw std::runtime_error("Semaphore is not initialized or has been destroyed"); + return it->second; +} + +struct SemaphoreWaitCleanupContext { + std::shared_ptr owner; + bool waiterRegistered = false; +}; + +static const auto CleanupSemaphoreWait = [](void* argument) APS5_VABI { + auto* context = static_cast(argument); + if (!context || !context->owner) + return; + auto* owner = context->owner.get(); + if (context->waiterRegistered) { + std::lock_guard lock(owner->_mutex); + if (owner->_waiters > 0) + --owner->_waiters; + context->waiterRegistered = false; + } + context->owner.reset(); +}; + +void finishSemaphoreWait(PthreadSemPrivate* sem, SemaphoreWaitCleanupContext& context, void* cleanupMark) { + if (context.waiterRegistered) { + --sem->_waiters; + context.waiterRegistered = false; + } + ThreadCancel::CleanupRestore(cleanupMark); } } @@ -40,72 +83,164 @@ int APS5_VABI scePthreadSemInit(PthreadSem* sem, int flag, unsigned int value, c return SCE_KERNEL_ERROR_EINVAL; (void)flag; (void)name; - auto replacement = std::make_unique(value); + auto replacement = std::make_shared(value); std::lock_guard lock(semInitializationMutex); - std::atomic_ref(*sem).store(replacement.release(), std::memory_order_release); + semRegistry.emplace(replacement.get(), replacement); + std::atomic_ref(*sem).store(replacement.get(), std::memory_order_release); return 0; } int APS5_VABI scePthreadSemDestroy(PthreadSem* sem) { if (!sem) throw std::invalid_argument("Semaphore pointer is null"); - std::lock_guard lock(semInitializationMutex); - auto current = *sem; - if (!current || current == destroyedSem()) - throw std::runtime_error("Semaphore is not initialized or has been destroyed"); - *sem = destroyedSem(); - delete current; + std::lock_guard registryLock(semInitializationMutex); + const auto current = std::atomic_ref(*sem).load(std::memory_order_acquire); + if (current == destroyedSem()) + return SCE_KERNEL_ERROR_EINVAL; + if (!current) + throw std::runtime_error("Semaphore is not initialized"); + const auto it = semRegistry.find(current); + if (it == semRegistry.end()) + return SCE_KERNEL_ERROR_EINVAL; + const auto owner = it->second; + std::lock_guard semLock(owner->_mutex); + if (owner->_waiters != 0) + return SCE_KERNEL_ERROR_EBUSY; + owner->_destroyed = true; + std::atomic_ref(*sem).store(destroyedSem(), std::memory_order_release); + semRegistry.erase(it); return 0; } int APS5_VABI scePthreadSemPost(PthreadSem* sem) { - auto* current = resolveSem(sem); - { - std::lock_guard lock(current->_mutex); - if (current->_count == sceSemValueMax) - return SCE_KERNEL_ERROR_EOVERFLOW; - ++current->_count; - } - current->_cv.NotifyOne(); + const auto owner = resolveSem(sem); + PauseSemApi(1); + std::lock_guard lock(owner->_mutex); + if (owner->_destroyed) + return SCE_KERNEL_ERROR_EINVAL; + if (owner->_count == sceSemValueMax) + return SCE_KERNEL_ERROR_EOVERFLOW; + ++owner->_count; + owner->_cv.NotifyOne(); return 0; } int APS5_VABI scePthreadSemWait(PthreadSem* sem) { - auto* current = resolveSem(sem); ThreadCancel::Check(); + SemaphoreWaitCleanupContext cleanupContext{resolveSem(sem), false}; + auto* current = cleanupContext.owner.get(); std::unique_lock lock(current->_mutex); - ThreadCancel::WaitUntil(current->_cv, lock, std::nullopt, [&] { return current->_count > 0; }); - --current->_count; - return 0; + if (current->_destroyed) + return SCE_KERNEL_ERROR_EINVAL; + ++current->_waiters; + cleanupContext.waiterRegistered = true; + CleanupHandler cleanup{}; + void* const cleanupMark = ThreadCancel::CleanupMark(); + __pthread_cleanup_push_imp_nid_postfix(CleanupSemaphoreWait, &cleanupContext, &cleanup); + try { + ThreadCancel::WaitUntil(current->_cv, lock, std::nullopt, [&] { return current->_count > 0 || current->_destroyed; }); + if (current->_destroyed) { + finishSemaphoreWait(current, cleanupContext, cleanupMark); + return SCE_KERNEL_ERROR_EINVAL; + } + --current->_count; + finishSemaphoreWait(current, cleanupContext, cleanupMark); + return 0; + } catch (...) { + if (cleanupContext.waiterRegistered) { + if (!lock.owns_lock()) + lock.lock(); + --current->_waiters; + cleanupContext.waiterRegistered = false; + } + ThreadCancel::CleanupRestore(cleanupMark); + throw; + } } int APS5_VABI scePthreadSemTrywait(PthreadSem* sem) { - auto* current = resolveSem(sem); - std::lock_guard lock(current->_mutex); - if (current->_count <= 0) + const auto owner = resolveSem(sem); + std::lock_guard lock(owner->_mutex); + if (owner->_destroyed) + return SCE_KERNEL_ERROR_EINVAL; + if (owner->_count <= 0) return sceBusy; - --current->_count; + --owner->_count; return 0; } int APS5_VABI scePthreadSemTimedwait(PthreadSem* sem, KernelUseconds usec) { - auto* current = resolveSem(sem); ThreadCancel::Check(); + SemaphoreWaitCleanupContext cleanupContext{resolveSem(sem), false}; + auto* current = cleanupContext.owner.get(); std::unique_lock lock(current->_mutex); - const auto acquired = ThreadCancel::WaitUntil(current->_cv, lock, TimedWait::DeadlineNanos(usec), [&] { return current->_count > 0; }); - if (!acquired) - return sceTimedOut; - --current->_count; - return 0; + if (current->_destroyed) + return SCE_KERNEL_ERROR_EINVAL; + ++current->_waiters; + cleanupContext.waiterRegistered = true; + CleanupHandler cleanup{}; + void* const cleanupMark = ThreadCancel::CleanupMark(); + __pthread_cleanup_push_imp_nid_postfix(CleanupSemaphoreWait, &cleanupContext, &cleanup); + try { + const auto acquired = ThreadCancel::WaitUntil(current->_cv, lock, TimedWait::DeadlineNanos(usec), [&] { return current->_count > 0 || current->_destroyed; }); + if (current->_destroyed) { + finishSemaphoreWait(current, cleanupContext, cleanupMark); + return SCE_KERNEL_ERROR_EINVAL; + } + if (!acquired) { + finishSemaphoreWait(current, cleanupContext, cleanupMark); + return sceTimedOut; + } + --current->_count; + finishSemaphoreWait(current, cleanupContext, cleanupMark); + return 0; + } catch (...) { + if (cleanupContext.waiterRegistered) { + if (!lock.owns_lock()) + lock.lock(); + --current->_waiters; + cleanupContext.waiterRegistered = false; + } + ThreadCancel::CleanupRestore(cleanupMark); + throw; + } } int APS5_VABI scePthreadSemGetvalue(PthreadSem* sem, int* value) { - auto* current = resolveSem(sem); + const auto owner = resolveSem(sem); if (!value) throw std::invalid_argument("Semaphore value pointer is null"); - std::lock_guard lock(current->_mutex); - *value = current->_count; + std::lock_guard lock(owner->_mutex); + if (owner->_destroyed) + return SCE_KERNEL_ERROR_EINVAL; + *value = owner->_count; return 0; } +int APS5_Test_GetPthreadSemWaiterCount(PthreadSem* sem) { + std::shared_ptr owner; + try { + owner = resolveSem(sem); + } catch (...) { + return -1; + } + std::lock_guard lock(owner->_mutex); + return owner->_destroyed ? -1 : owner->_waiters; +} + +void APS5_Test_SetPthreadSemApiPause(int kind) { + semApiPauseEntered.store(0, std::memory_order_relaxed); + semApiPauseReleased.store(false, std::memory_order_relaxed); + semApiPauseTarget.store(kind, std::memory_order_release); +} + +int APS5_Test_GetPthreadSemApiPauseCount() { + return semApiPauseEntered.load(std::memory_order_acquire); +} + +void APS5_Test_ReleasePthreadSemApiPause() { + semApiPauseReleased.store(true, std::memory_order_release); + semApiPauseTarget.store(0, std::memory_order_release); +} + } diff --git a/core/libs/tests/GuestBacktrace.cpp b/core/libs/tests/GuestBacktrace.cpp new file mode 100644 index 000000000000..6cc1c03c033a --- /dev/null +++ b/core/libs/tests/GuestBacktrace.cpp @@ -0,0 +1,48 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceLibcBacktraceGetBufferSize_nid_postfix(int, int*, std::size_t*); +int APS5_VABI sceLibcBacktraceSelf_nid_postfix(int, void*, std::size_t, int*); +} + +static void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stdout, "%s\n", message); + std::abort(); +} + +int main() { + Require(sceLibcBacktraceGetBufferSize_nid_postfix(8, nullptr, nullptr) == 1, "null size outputs accepted"); + Require(sceLibcBacktraceSelf_nid_postfix(8, nullptr, 0, nullptr) == 1, "null self outputs accepted"); + + int expectedCount = 0; + std::size_t expectedSize = 0; + Require(sceLibcBacktraceGetBufferSize_nid_postfix(8, &expectedCount, &expectedSize) == 0, "buffer size failed"); + Require(expectedCount > 0 && expectedSize >= expectedCount * 0x18, "buffer size was not populated"); + + std::vector buffer(expectedSize); + int count = 0; + Require(sceLibcBacktraceSelf_nid_postfix(8, buffer.data(), buffer.size(), &count) == 0, "self backtrace failed"); + Require(count > 0 && count <= 8, "self count was invalid"); + + std::size_t offset = 0; + for (int index = 0; index < count; ++index) { + Require(offset + 0x18 <= buffer.size(), "backtrace record header exceeded buffer"); + std::uintptr_t next = 0; + std::memcpy(&next, buffer.data() + offset, sizeof(next)); + const auto* text = reinterpret_cast(buffer.data() + offset + 0x10); + const auto length = std::strlen(text); + Require(length > 0 && length <= 255, "backtrace record text was invalid"); + const auto size = 0x18 + ((length + 7) & ~std::size_t(7)); + Require(offset + size <= buffer.size(), "backtrace record exceeded buffer"); + if (index + 1 == count) Require(next == 0, "last backtrace record was linked"); + else Require(next == reinterpret_cast(buffer.data() + offset + size), "backtrace records were not linked"); + offset += size; + } +} diff --git a/core/libs/tests/GuestFilesystem.cpp b/core/libs/tests/GuestFilesystem.cpp index 372551fd470c..c877b1d11e1d 100644 --- a/core/libs/tests/GuestFilesystem.cpp +++ b/core/libs/tests/GuestFilesystem.cpp @@ -987,6 +987,25 @@ int main() { const auto longLeaf = rootName + "/" + std::string(300, 'n'); Require(sceKernelMkdir(longLeaf.c_str(), 0700) == static_cast(0x8002003fu)); Require(mkdir_nid_postfix(longLeaf.c_str(), 0700) == -1 && *__error_nid_postfix() == 63); + FileStat unreachable{}; + Require(sceKernelOpen(longLeaf.c_str(), 0, 0) == static_cast(0x8002003fu)); + Require(sceKernelOpen(longLeaf.c_str(), 0x201, 0600) == static_cast(0x8002003fu)); + Require(open_nid_postfix(longLeaf.c_str(), 0, 0) == -1 && *__error_nid_postfix() == 63); + Require(sceKernelUnlink(longLeaf.c_str()) == static_cast(0x8002003fu)); + Require(unlink_nid_postfix(longLeaf.c_str()) == -1 && *__error_nid_postfix() == 63); + Require(sceKernelStat(longLeaf.c_str(), &unreachable) == static_cast(0x8002003fu)); + Require(stat_nid_postfix(longLeaf.c_str(), &unreachable) == -1 && *__error_nid_postfix() == 63); + const auto loop = root / "loop"; + std::filesystem::create_symlink("loop", loop); + const auto loopName = loop.string(); + const auto underLoop = loopName + "/leaf"; + Require(sceKernelOpen(loopName.c_str(), 0, 0) == static_cast(0x8002003eu)); + Require(open_nid_postfix(loopName.c_str(), 0, 0) == -1 && *__error_nid_postfix() == 62); + Require(sceKernelStat(loopName.c_str(), &unreachable) == static_cast(0x8002003eu)); + Require(stat_nid_postfix(loopName.c_str(), &unreachable) == -1 && *__error_nid_postfix() == 62); + Require(sceKernelUnlink(underLoop.c_str()) == static_cast(0x8002003eu)); + Require(unlink_nid_postfix(underLoop.c_str()) == -1 && *__error_nid_postfix() == 62); + Require(unlink_nid_postfix(loopName.c_str()) == 0 && !std::filesystem::is_symlink(loop)); #endif const auto invalidNameUnderFile = presentName + "/a:b/x"; Require(mkdir_nid_postfix(invalidNameUnderFile.c_str(), 0700) == -1 && *__error_nid_postfix() == 20); diff --git a/core/libs/tests/GuestFloatBuiltins.cpp b/core/libs/tests/GuestFloatBuiltins.cpp new file mode 100644 index 000000000000..a64f17eb8516 --- /dev/null +++ b/core/libs/tests/GuestFloatBuiltins.cpp @@ -0,0 +1,228 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +using Int128 = __int128; +using Uint128 = unsigned __int128; + +extern "C" { +double APS5_VABI __adddf3_nid_postfix(double, double); +float APS5_VABI __addsf3_nid_postfix(float, float); +double APS5_VABI __subdf3_nid_postfix(double, double); +float APS5_VABI __subsf3_nid_postfix(float, float); +double APS5_VABI __muldf3_nid_postfix(double, double); +float APS5_VABI __mulsf3_nid_postfix(float, float); +double APS5_VABI __divdf3_nid_postfix(double, double); +float APS5_VABI __divsf3_nid_postfix(float, float); +double APS5_VABI __negdf2_nid_postfix(double); +float APS5_VABI __negsf2_nid_postfix(float); +int APS5_VABI __eqdf2_nid_postfix(double, double); +int APS5_VABI __nedf2_nid_postfix(double, double); +int APS5_VABI __ltdf2_nid_postfix(double, double); +int APS5_VABI __ledf2_nid_postfix(double, double); +int APS5_VABI __gtdf2_nid_postfix(double, double); +int APS5_VABI __gedf2_nid_postfix(double, double); +int APS5_VABI __unorddf2_nid_postfix(double, double); +int APS5_VABI __eqsf2_nid_postfix(float, float); +int APS5_VABI __ltsf2_nid_postfix(float, float); +int APS5_VABI __gesf2_nid_postfix(float, float); +int APS5_VABI __unordsf2_nid_postfix(float, float); +double APS5_VABI __extendsfdf2_nid_postfix(float); +float APS5_VABI __truncdfsf2_nid_postfix(double); +std::int32_t APS5_VABI __fixsfsi_nid_postfix(float); +std::int64_t APS5_VABI __fixsfdi_nid_postfix(float); +Int128 APS5_VABI __fixsfti_nid_postfix(float); +std::int32_t APS5_VABI __fixdfsi_nid_postfix(double); +std::int64_t APS5_VABI __fixdfdi_nid_postfix(double); +Int128 APS5_VABI __fixdfti_nid_postfix(double); +std::uint32_t APS5_VABI __fixunssfsi_nid_postfix(float); +std::uint64_t APS5_VABI __fixunssfdi_nid_postfix(float); +Uint128 APS5_VABI __fixunssfti_nid_postfix(float); +std::uint32_t APS5_VABI __fixunsdfsi_nid_postfix(double); +std::uint64_t APS5_VABI __fixunsdfdi_nid_postfix(double); +Uint128 APS5_VABI __fixunsdfti_nid_postfix(double); +std::int64_t APS5_VABI __fixxfdi_nid_postfix(long double); +Int128 APS5_VABI __fixxfti_nid_postfix(long double); +std::uint32_t APS5_VABI __fixunsxfsi_nid_postfix(long double); +std::uint64_t APS5_VABI __fixunsxfdi_nid_postfix(long double); +Uint128 APS5_VABI __fixunsxfti_nid_postfix(long double); +double APS5_VABI __floatsidf_nid_postfix(std::int32_t); +double APS5_VABI __floatunsidf_nid_postfix(std::uint32_t); +float APS5_VABI __floatsisf_nid_postfix(std::int32_t); +float APS5_VABI __floatunsisf_nid_postfix(std::uint32_t); +double APS5_VABI __floatdidf_nid_postfix(std::int64_t); +float APS5_VABI __floatdisf_nid_postfix(std::int64_t); +long double APS5_VABI __floatdixf_nid_postfix(std::int64_t); +double APS5_VABI __floatundidf_nid_postfix(std::uint64_t); +float APS5_VABI __floatundisf_nid_postfix(std::uint64_t); +long double APS5_VABI __floatundixf_nid_postfix(std::uint64_t); +double APS5_VABI __floattidf_nid_postfix(Int128); +float APS5_VABI __floattisf_nid_postfix(Int128); +long double APS5_VABI __floattixf_nid_postfix(Int128); +double APS5_VABI __floatuntidf_nid_postfix(Uint128); +float APS5_VABI __floatuntisf_nid_postfix(Uint128); +long double APS5_VABI __floatuntixf_nid_postfix(Uint128); +__complex__ float APS5_VABI __mulsc3_nid_postfix(float, float, float, float); +__complex__ double APS5_VABI __muldc3_nid_postfix(double, double, double, double); +__complex__ long double APS5_VABI __mulxc3_nid_postfix(long double, long double, long double, long double); +__complex__ float APS5_VABI __divsc3_nid_postfix(float, float, float, float); +__complex__ double APS5_VABI __divdc3_nid_postfix(double, double, double, double); +__complex__ long double APS5_VABI __divxc3_nid_postfix(long double, long double, long double, long double); +long double APS5_VABI __powixf2_nid_postfix(long double, int); +} + +static void Require(bool value) { if (!value) std::abort(); } + +static std::uint64_t Bits(double value) { return std::bit_cast(value); } +static std::uint32_t Bits(float value) { return std::bit_cast(value); } +static double Double(std::uint64_t bits) { return std::bit_cast(bits); } +static float Float(std::uint32_t bits) { return std::bit_cast(bits); } +static Uint128 Make(std::uint64_t high, std::uint64_t low) { return (static_cast(high) << 64) | low; } + +static constexpr unsigned GuestMxcsr = 0x1f80 | 0x6000 | 0x8040; + +static void CheckArithmetic() { + Require(__adddf3_nid_postfix(1., 2.) == 3. && __subsf3_nid_postfix(1.f, 2.f) == -1.f); + Require(__muldf3_nid_postfix(3., -4.) == -12. && __divsf3_nid_postfix(1.f, 4.f) == .25f); + Require(Bits(__adddf3_nid_postfix(INFINITY, -INFINITY)) == 0x7ff8000000000000ull); + Require(Bits(__muldf3_nid_postfix(0., INFINITY)) == 0x7ff8000000000000ull); + Require(Bits(__divdf3_nid_postfix(0., 0.)) == 0x7ff8000000000000ull); + Require(Bits(__subsf3_nid_postfix(INFINITY, INFINITY)) == 0x7fc00000u); + Require(Bits(__mulsf3_nid_postfix(0.f, -INFINITY)) == 0x7fc00000u); + Require(Bits(__adddf3_nid_postfix(Double(0xfff0000000000001ull), 1.)) == 0xfff8000000000001ull); + Require(Bits(__adddf3_nid_postfix(Double(0x7ff8000000000002ull), Double(0x7ff0000000000003ull))) == 0x7ff8000000000002ull); + Require(Bits(__muldf3_nid_postfix(2., Double(0x7ff0000000000003ull))) == 0x7ff8000000000003ull); + Require(Bits(__subdf3_nid_postfix(1., Double(0x7ff8000000000004ull))) == 0xfff8000000000004ull); + Require(Bits(__negdf2_nid_postfix(Double(0x7ff8000000000004ull))) == 0xfff8000000000004ull); + Require(Bits(__negsf2_nid_postfix(0.f)) == 0x80000000u); + Require(Bits(__divdf3_nid_postfix(-0x1p-1000, 0x1p30)) == 0x8000000000000000ull); + Require(Bits(__divsf3_nid_postfix(0x1p-120f, 0x1p10f)) == 0u); + Require(Bits(__divdf3_nid_postfix(0x1p-1022, 0x1.0000000000001p0)) == 0u); + Require(__divdf3_nid_postfix(0x1p-1000, 0x1p-22) == 0x1p-978 && __divsf3_nid_postfix(1.f, 3.f) == 1.f / 3.f); + const unsigned saved = _mm_getcsr(); + _mm_setcsr(GuestMxcsr); + const double sum = __adddf3_nid_postfix(1., 0x1.8p-53); + const float denormal = __mulsf3_nid_postfix(0x1p-126f, .5f); + const double quotient = __divdf3_nid_postfix(1., 3.); + const unsigned after = _mm_getcsr(); + _mm_setcsr(saved); + Require(sum == 1. + 0x1p-52); + Require(Bits(denormal) == 0x00400000u); + Require(Bits(quotient) == 0x3fd5555555555555ull); + Require(after == GuestMxcsr); +} + +static void CheckComparisons() { + Require(__ledf2_nid_postfix(1., 2.) == -1 && __ledf2_nid_postfix(2., 1.) == 1 && __ledf2_nid_postfix(2., 2.) == 0); + Require(__ltdf2_nid_postfix(-2., -1.) == -1 && __gtdf2_nid_postfix(-1., -2.) == 1); + Require(__eqdf2_nid_postfix(0., -0.) == 0 && __nedf2_nid_postfix(1., -1.) != 0); + Require(__ledf2_nid_postfix(NAN, 1.) == 1 && __gedf2_nid_postfix(1., NAN) == -1 && __gtdf2_nid_postfix(NAN, NAN) == -1); + Require(__unorddf2_nid_postfix(1., NAN) == 1 && __unorddf2_nid_postfix(1., INFINITY) == 0); + Require(__eqsf2_nid_postfix(-0.f, 0.f) == 0 && __ltsf2_nid_postfix(-INFINITY, -1e30f) == -1); + Require(__gesf2_nid_postfix(NAN, 0.f) == -1 && __unordsf2_nid_postfix(NAN, 0.f) == 1); + Require(__ledf2_nid_postfix(Double(0x0000000000000001ull), 0.) == 1); +} + +static void CheckFloatConversions() { + Require(Bits(__extendsfdf2_nid_postfix(Float(0x7f800001u))) == 0x7ff0000000000001ull); + Require(Bits(__extendsfdf2_nid_postfix(Float(0xffc00001u))) == 0xfff8000000000001ull); + Require(__extendsfdf2_nid_postfix(Float(1u)) == std::ldexp(1., -149)); + Require(__extendsfdf2_nid_postfix(Float(0x80400000u)) == -std::ldexp(1., -127)); + Require(__extendsfdf2_nid_postfix(1.5f) == 1.5 && __extendsfdf2_nid_postfix(-INFINITY) == -INFINITY); + Require(Bits(__truncdfsf2_nid_postfix(Double(0x7ff0000020000001ull))) == 0x7fc00001u); + Require(Bits(__truncdfsf2_nid_postfix(Double(0xfff4000000123456ull))) == 0xffd23456u); + const unsigned saved = _mm_getcsr(); + _mm_setcsr(GuestMxcsr); + const float third = __truncdfsf2_nid_postfix(1. / 3.); + const float tiny = __truncdfsf2_nid_postfix(0x1p-140); + _mm_setcsr(saved); + Require(Bits(third) == 0x3eaaaaabu && Bits(tiny) == 0x00000200u); +} + +static void CheckFloatToInteger() { + Require(__fixdfsi_nid_postfix(-3.7) == -3 && __fixsfsi_nid_postfix(.99f) == 0); + Require(__fixdfsi_nid_postfix(0x1p31) == INT32_MIN && __fixdfsi_nid_postfix(1e10) == 1410065408); + Require(__fixsfsi_nid_postfix(Float(0x7fc00000u)) == INT32_MIN); + Require(__fixdfdi_nid_postfix(0x1p63) == INT64_MIN && __fixdfdi_nid_postfix(-0x1p62) == -0x4000000000000000); + Require(__fixsfdi_nid_postfix(-0x1p40f) == -(1ll << 40)); + Require(__fixdfti_nid_postfix(0x1p100) == static_cast(Make(1ull << 36, 0))); + Require(__fixsfti_nid_postfix(-0x1p70f) == -static_cast(Make(1ull << 6, 0))); + Require(__fixdfdi_nid_postfix(0x1p64) == 0 && __fixdfdi_nid_postfix(0x1p120) == 1ll << 56); + Require(__fixdfti_nid_postfix(0x1p200) == static_cast(1) << 72); + Require(__fixunsdfsi_nid_postfix(-1.) == 0 && __fixunsdfsi_nid_postfix(4294967295.) == 0xffffffffu); + Require(__fixunsdfsi_nid_postfix(0x1p32) == 1); + Require(__fixunssfsi_nid_postfix(0x1p32f) == 0 && __fixunssfsi_nid_postfix(0x1p55f) == 1u << 23 && __fixunssfdi_nid_postfix(0x1p63f) == 1ull << 63); + Require(__fixunsdfdi_nid_postfix(18446744073709549568.) == 18446744073709549568ull); + Require(__fixunssfti_nid_postfix(0x1p127f) == Make(1ull << 63, 0) && __fixunsdfti_nid_postfix(-2.) == 0); + Require(__fixxfdi_nid_postfix(-5.5L) == -5 && __fixxfdi_nid_postfix(0x1p63L) == INT64_MIN); + Require(__fixunsxfdi_nid_postfix(0x1p64L) == 1 && __fixunsxfdi_nid_postfix(18446744073709551615.L) == ~0ull); + Require(__fixxfti_nid_postfix(-0x1p70L) == -static_cast(Make(1ull << 6, 0))); + Require(__fixunsxfti_nid_postfix(0x1.8p100L) == Make(3ull << 35, 0) && __fixunsxfti_nid_postfix(-1.L) == 0); + Require(__fixunsxfsi_nid_postfix(3e9L) == 3000000000u && __fixunsxfsi_nid_postfix(0x1p32L) == 1); +} + +static void CheckIntegerToFloat() { + Require(__floatsidf_nid_postfix(INT32_MIN) == -0x1p31 && __floatunsidf_nid_postfix(0xffffffffu) == 4294967295.); + Require(__floatdidf_nid_postfix(-3) == -3. && __floatdisf_nid_postfix(1ll << 40) == 0x1p40f); + Require(__floatundidf_nid_postfix(~0ull) == 0x1p64 && __floatundisf_nid_postfix(~0ull) == 0x1p64f); + Require(__floatdixf_nid_postfix(INT64_MIN) == -0x1p63L && __floatundixf_nid_postfix(~0ull) == 18446744073709551615.L); + const unsigned saved = _mm_getcsr(); + _mm_setcsr(GuestMxcsr); + const float tie = __floatsisf_nid_postfix(16777217); + const float up = __floatunsisf_nid_postfix(16777219u); + const float negative = __floatsisf_nid_postfix(-16777219); + const double wide = __floattidf_nid_postfix(static_cast(Make(0, (1ull << 63) | 0x601))); + _mm_setcsr(saved); + Require(tie == 16777216.f && up == 16777220.f && negative == -16777220.f); + Require(wide == 0x1p63 + 0x800); + Require(__floattidf_nid_postfix(static_cast(Make(1, 1))) == 0x1p64); + Require(__floattisf_nid_postfix(-static_cast(Make(1ull << 40, 0))) == -0x1p104f); + Require(__floatuntisf_nid_postfix(Make(~0ull, ~0ull)) == INFINITY); + Require(__floatuntidf_nid_postfix(Make(~0ull, ~0ull)) == 0x1p128); + Require(__floatuntidf_nid_postfix(0) == 0. && !std::signbit(__floattidf_nid_postfix(0))); + Require(__floattixf_nid_postfix(-static_cast(Make(1, 1))) == -0x1p64L); + Require(__floattixf_nid_postfix(-static_cast(Make(1, 3))) == -(0x1p64L + 4)); + Require(__floatuntixf_nid_postfix(Make(~0ull, ~0ull)) == 0x1p128L); + Require(__floatuntixf_nid_postfix(Make(0, ~0ull)) == 18446744073709551615.L); +} + +static void CheckComplex() { + const __complex__ float product = __mulsc3_nid_postfix(1.f, 2.f, 3.f, 4.f); + Require(__real__ product == -5.f && __imag__ product == 10.f); + const __complex__ double infinite = __muldc3_nid_postfix(INFINITY, NAN, 1., 0.); + Require(__real__ infinite == INFINITY); + const __complex__ long double extended = __mulxc3_nid_postfix(1.L, 1.L, 1.L, -1.L); + Require(__real__ extended == 2.L && __imag__ extended == 0.L); + const __complex__ double quotient = __divdc3_nid_postfix(6., 8., 3., 4.); + Require(__real__ quotient == 2. && __imag__ quotient == 0.); + const __complex__ float single = __divsc3_nid_postfix(1.f, 0.f, 0.f, 1.f); + Require(__real__ single == 0.f && __imag__ single == -1.f); + const __complex__ double byZero = __divdc3_nid_postfix(1., 0., 0., 0.); + Require(__real__ byZero == INFINITY); + const __complex__ long double large = __divxc3_nid_postfix(1e4000L, 1e4000L, 1e4000L, 1e4000L); + Require(__real__ large == 1.L && __imag__ large == 0.L); + const __complex__ double byInfinity = __divdc3_nid_postfix(1., 1., INFINITY, INFINITY); + Require(__real__ byInfinity == 0. && __imag__ byInfinity == 0.); +} + +int main() { + CheckArithmetic(); + CheckComparisons(); + CheckFloatConversions(); + CheckFloatToInteger(); + CheckIntegerToFloat(); + CheckComplex(); + Require(__powixf2_nid_postfix(2.L, 10) == 1024.L && __powixf2_nid_postfix(2.L, -2) == .25L); + Require(__powixf2_nid_postfix(-3.L, 3) == -27.L && __powixf2_nid_postfix(5.L, 0) == 1.L); + long double signaling; + const unsigned char signalingBytes[10] = {1, 0, 0, 0, 0, 0, 0, 0x80, 0xff, 0x7f}; + std::memset(&signaling, 0, sizeof(signaling)); + std::memcpy(&signaling, signalingBytes, sizeof(signalingBytes)); + const long double same = __powixf2_nid_postfix(signaling, 1); + Require(std::memcmp(&same, signalingBytes, sizeof(signalingBytes)) == 0); + return 0; +} diff --git a/core/libs/tests/GuestHmd.cpp b/core/libs/tests/GuestHmd.cpp index 6b97bea9cc46..e68fdd3d08de 100644 --- a/core/libs/tests/GuestHmd.cpp +++ b/core/libs/tests/GuestHmd.cpp @@ -4,6 +4,13 @@ #include #include #include +#include + +extern "C" { +int APS5_VABI sceHmdDistortionInitialize(); +int APS5_VABI sceHmdDistortionGet2dVrCommand(); +int APS5_VABI sceHmdGetFieldOfView(); +} namespace { @@ -80,4 +87,22 @@ int main() { Require(sceHmdGetDeviceInformation(&info) == Hmd::NotInitialized, "query after terminate"); CheckDisconnected(); } + + try { + sceHmdDistortionInitialize(); + Require(false, "sceHmdDistortionInitialize should throw"); + } catch (const std::runtime_error&) { + } + + try { + sceHmdDistortionGet2dVrCommand(); + Require(false, "sceHmdDistortionGet2dVrCommand should throw"); + } catch (const std::runtime_error&) { + } + + try { + sceHmdGetFieldOfView(); + Require(false, "sceHmdGetFieldOfView should throw"); + } catch (const std::runtime_error&) { + } } diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp index fae91df84546..4c5d4d3ddd3a 100644 --- a/core/libs/tests/GuestMemory.cpp +++ b/core/libs/tests/GuestMemory.cpp @@ -235,6 +235,59 @@ static void CheckInternalNamedFlexibleMapping() { Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); } +static void CheckFlexibleFixedReplacement() { + constexpr std::size_t page = 0x4000; + std::size_t before = 0; + std::size_t available = 0; + Require(sceKernelAvailableFlexibleMemorySize(&before) == 0); + void* base = nullptr; + Require(sceKernelMapFlexibleMemory(&base, page * 4, 3, 0) == 0 && base != nullptr); + Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before - page * 4); + auto* bytes = static_cast(base); + for (std::size_t index = 0; index < 4; ++index) bytes[index * page] = static_cast(17 + index); + void* middle = bytes + page; + void* replacement = middle; + Require(sceKernelMapFlexibleMemory(&replacement, page, 3, 0x10) == 0 && replacement == middle); + Require(bytes[0] == 17 && bytes[page] == 0 && bytes[page * 2] == 19 && bytes[page * 3] == 20); + Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before - page * 4); + Require(sceKernelMunmap(base, page * 4) == 0); + Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); +} + +static void CheckMunmapScopedFlexibleRelease() { + constexpr std::size_t page = 0x4000; + std::size_t before = 0; + std::size_t available = 0; + Require(sceKernelAvailableFlexibleMemorySize(&before) == 0); + void* base = nullptr; + Require(sceKernelMapFlexibleMemory(&base, page * 2, 3, 0) == 0 && base != nullptr); + Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before - page * 2); + std::int64_t phys = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page, 0, 0, &phys) == 0); + void* direct = static_cast(base) + page; + Require(sceKernelMapDirectMemory(&direct, page, 3, 0x10, phys, 0) == 0 && direct == static_cast(base) + page); + std::size_t mid = 0; + Require(sceKernelAvailableFlexibleMemorySize(&mid) == 0); + Require(sceKernelMunmap(direct, page) == 0); + Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == mid); + void* reserved = nullptr; + Require(sceKernelReserveVirtualRange(&reserved, page * 2, 0, 0) == 0 && reserved != nullptr); + Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == mid); + Require(sceKernelMunmap(reserved, page * 2) == 0); + Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == mid); + Require(sceKernelMunmap(base, page) == 0); + Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == mid + page); + Require(sceKernelReleaseDirectMemory(phys, page) == 0); + void* fresh = nullptr; + Require(sceKernelMapFlexibleMemory(&fresh, page * 4, 3, 0) == 0 && fresh != nullptr); + Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before - page * 4); + Require(sceKernelMunmap(static_cast(fresh) + page, page * 2) == 0); + Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before - page * 2); + Require(sceKernelMunmap(fresh, page) == 0); + Require(sceKernelMunmap(static_cast(fresh) + page * 3, page) == 0); + Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); +} + static void CheckGuestModuleUsesFlexibleMemory() { constexpr std::size_t moduleData = 0x10000; const auto path = std::filesystem::relative(GUEST_MEMORY_MODULE).generic_string(); @@ -2245,6 +2298,8 @@ int main(int argc, char** argv) { CheckVirtualQueryWireLayout(); CheckNamedAndHintedMappings(); CheckInternalNamedFlexibleMapping(); + CheckFlexibleFixedReplacement(); + CheckMunmapScopedFlexibleRelease(); CheckGuestModuleUsesFlexibleMemory(); CheckBatchMapStopsAtInvalidEntry(); CheckCheckedReleaseDirectMemory(); diff --git a/core/libs/tests/GuestPageTableStats.cpp b/core/libs/tests/GuestPageTableStats.cpp index 6a47951c8aea..eba38dd12ccb 100644 --- a/core/libs/tests/GuestPageTableStats.cpp +++ b/core/libs/tests/GuestPageTableStats.cpp @@ -26,24 +26,37 @@ static void Require(bool condition, std::source_location location = std::source_ } constexpr std::size_t PageTableGranule = 0x200000; -constexpr int PageTableTotal = 2048; +constexpr std::size_t GpuDirectorySpan = 0x40000000; +constexpr int PageTableTotal = 8192; -struct Stats { - int cpuTotal; - int cpuAvailable; - int gpuTotal; - int gpuAvailable; +struct Usage { + int cpu; + int gpu; }; -static Stats Query() { - Stats stats{}; - Require(sceKernelGetPageTableStats(&stats.cpuTotal, &stats.cpuAvailable, &stats.gpuTotal, &stats.gpuAvailable) == 0); - return stats; +static Usage Used() { + int cpuTotal = 0, cpuAvailable = 0, gpuTotal = 0, gpuAvailable = 0; + Require(sceKernelGetPageTableStats(&cpuTotal, &cpuAvailable, &gpuTotal, &gpuAvailable) == 0); + Require(cpuTotal == PageTableTotal && gpuTotal == PageTableTotal); + Require(cpuAvailable >= 0 && cpuAvailable <= cpuTotal && gpuAvailable >= 0 && gpuAvailable <= gpuTotal); + return {cpuTotal - cpuAvailable, gpuTotal - gpuAvailable}; } -static int Granules(const void* addr, std::size_t len) { - const auto start = reinterpret_cast(addr); - return static_cast((start + len - 1) / PageTableGranule - start / PageTableGranule + 1); +static void RequireTaken(const Usage& before, int cpu, int gpu, std::source_location location = std::source_location::current()) { + const auto now = Used(); + Require(now.cpu - before.cpu == cpu && now.gpu - before.gpu == gpu, location); +} + +static unsigned char* Reserve() { + void* reserved = nullptr; + Require(sceKernelReserveVirtualRange(&reserved, GpuDirectorySpan * 2, 0, GpuDirectorySpan) == 0); + Require(reinterpret_cast(reserved) % GpuDirectorySpan == 0); + return static_cast(reserved); +} + +static void Map(unsigned char* at, std::size_t len, int prot) { + void* address = at; + Require(sceKernelMapFlexibleMemory(&address, len, prot, 0x10) == 0 && address == at); } static void CheckRejectedArguments() { @@ -54,85 +67,54 @@ static void CheckRejectedArguments() { Require(sceKernelGetPageTableStats(&value, &value, &value, nullptr) == SCE_KERNEL_ERROR_EFAULT); } -static void CheckFlexibleMapping() { - const auto before = Query(); - Require(before.cpuTotal == PageTableTotal && before.gpuTotal == PageTableTotal); - Require(before.cpuAvailable <= before.cpuTotal && before.gpuAvailable <= before.gpuTotal); - - constexpr std::size_t len = 3 * PageTableGranule; - void* mapped = nullptr; - Require(sceKernelMapFlexibleMemory(&mapped, len, 3, 0) == 0 && mapped != nullptr); - const auto entries = Granules(mapped, len); - auto during = Query(); - Require(during.cpuAvailable == before.cpuAvailable - entries); - Require(during.gpuAvailable == before.gpuAvailable); - - Require(sceKernelMprotect(mapped, len, 0x30) == 0); - during = Query(); - Require(during.cpuAvailable == before.cpuAvailable); - Require(during.gpuAvailable == before.gpuAvailable - entries); - - Require(sceKernelMunmap(mapped, len) == 0); - const auto after = Query(); - Require(after.cpuAvailable == before.cpuAvailable && after.gpuAvailable == before.gpuAvailable); -} - -static void CheckDualAccessMapping() { - const auto before = Query(); - constexpr std::size_t len = PageTableGranule; - void* mapped = nullptr; - Require(sceKernelMapFlexibleMemory(&mapped, len, 0x33, 0) == 0 && mapped != nullptr); - const auto entries = Granules(mapped, len); - const auto during = Query(); - Require(during.cpuAvailable == before.cpuAvailable - entries); - Require(during.gpuAvailable == before.gpuAvailable - entries); - Require(sceKernelMunmap(mapped, len) == 0); - const auto after = Query(); - Require(after.cpuAvailable == before.cpuAvailable && after.gpuAvailable == before.gpuAvailable); +static void CheckCpuGranules() { + auto* base = Reserve(); + const auto before = Used(); + Map(base, 0x4000, 3); + Map(base + 0x4000, 0x4000, 3); + Map(base + 0x8000, 0x4000, 1); + RequireTaken(before, 1, 0); + Map(base + PageTableGranule, 0x4000, 0); + RequireTaken(before, 2, 0); + Require(sceKernelMunmap(base, GpuDirectorySpan * 2) == 0); + RequireTaken(before, 0, 0); } -static void CheckSharedGranule() { - const auto before = Query(); - constexpr std::size_t page = 0x4000; - void* span = nullptr; - Require(sceKernelReserveVirtualRange(&span, 2 * PageTableGranule, 0, PageTableGranule) == 0 && span != nullptr); - const auto base = reinterpret_cast(span); - const auto aligned = base % PageTableGranule == 0 ? base : (base / PageTableGranule + 1) * PageTableGranule; - void* first = reinterpret_cast(aligned); - void* second = reinterpret_cast(aligned + page); - Require(sceKernelMapFlexibleMemory(&first, page, 3, 0) == 0 && first == reinterpret_cast(aligned)); - Require(sceKernelMapFlexibleMemory(&second, page, 3, 0) == 0 && second == reinterpret_cast(aligned + page)); - const auto during = Query(); - Require(during.cpuAvailable == before.cpuAvailable - 2); - Require(during.gpuAvailable == before.gpuAvailable); - Require(sceKernelMunmap(first, page) == 0); - Require(sceKernelMunmap(second, page) == 0); - const auto after = Query(); - Require(after.cpuAvailable == before.cpuAvailable && after.gpuAvailable == before.gpuAvailable); +static void CheckGpuEntries() { + auto* base = Reserve(); + const auto before = Used(); + Map(base, 3 * PageTableGranule, 3); + RequireTaken(before, 3, 0); + Require(sceKernelMprotect(base, 3 * PageTableGranule, 0x30) == 0); + RequireTaken(before, 3, 4); + Map(base + GpuDirectorySpan, 0x4000, 0x33); + RequireTaken(before, 4, 6); + Require(sceKernelMunmap(base, GpuDirectorySpan * 2) == 0); + RequireTaken(before, 0, 0); } static void CheckGpuDirectMapping() { - const auto before = Query(); - constexpr std::size_t len = 2 * PageTableGranule; + auto* base = Reserve(); + const auto before = Used(); std::int64_t phys = 0; - Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, len, 0, 0, &phys) == 0); - void* mapped = nullptr; - Require(sceKernelMapDirectMemory(&mapped, len, 0x33, 0, phys, 0) == 0 && mapped != nullptr); - const auto entries = Granules(mapped, len); - const auto during = Query(); - Require(during.gpuAvailable == before.gpuAvailable - entries); - Require(during.cpuAvailable == before.cpuAvailable - entries); - Require(sceKernelMunmap(mapped, len) == 0); - Require(sceKernelReleaseDirectMemory(phys, len) == 0); - const auto after = Query(); - Require(after.cpuAvailable == before.cpuAvailable && after.gpuAvailable == before.gpuAvailable); + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, 3 * PageTableGranule, PageTableGranule, 0, &phys) == 0); + Require(phys % static_cast(PageTableGranule) == 0); + void* large = base; + Require(sceKernelMapDirectMemory(&large, 2 * PageTableGranule, 0x33, 0x10, phys, PageTableGranule) == 0 && large == base); + RequireTaken(before, 2, 1); + void* small = base + 2 * PageTableGranule + 0x10000; + Require(sceKernelMapDirectMemory(&small, 0x10000, 0x33, 0x10, phys + static_cast(2 * PageTableGranule), 0x10000) == 0); + Require(small == base + 2 * PageTableGranule + 0x10000); + RequireTaken(before, 3, 2); + Require(sceKernelMunmap(base, GpuDirectorySpan * 2) == 0); + Require(sceKernelReleaseDirectMemory(phys, 3 * PageTableGranule) == 0); + RequireTaken(before, 0, 0); } int main() { CheckRejectedArguments(); - CheckFlexibleMapping(); - CheckDualAccessMapping(); - CheckSharedGranule(); + CheckCpuGranules(); + CheckGpuEntries(); CheckGpuDirectMapping(); return 0; } diff --git a/core/libs/tests/GuestPthreadSem.cpp b/core/libs/tests/GuestPthreadSem.cpp index 574228ddf4d1..18f872c1941a 100644 --- a/core/libs/tests/GuestPthreadSem.cpp +++ b/core/libs/tests/GuestPthreadSem.cpp @@ -10,6 +10,7 @@ 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 scePthreadCancel(Pthread thread); int APS5_VABI scePthreadSemInit(PthreadSem* sem, int flag, unsigned int value, const char* name); int APS5_VABI scePthreadSemDestroy(PthreadSem* sem); int APS5_VABI scePthreadSemPost(PthreadSem* sem); @@ -17,6 +18,10 @@ int APS5_VABI scePthreadSemWait(PthreadSem* sem); int APS5_VABI scePthreadSemTrywait(PthreadSem* sem); int APS5_VABI scePthreadSemTimedwait(PthreadSem* sem, unsigned int usec); int APS5_VABI scePthreadSemGetvalue(PthreadSem* sem, int* value); +int APS5_Test_GetPthreadSemWaiterCount(PthreadSem* sem); +int APS5_Test_GetPthreadSemApiPauseCount(); +void APS5_Test_SetPthreadSemApiPause(int kind); +void APS5_Test_ReleasePthreadSemApiPause(); } static constexpr int SCE_OK = 0; @@ -27,6 +32,22 @@ static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = static_cast(0x8002003C); static void Require(bool value) { if (!value) std::abort(); } +static void WaitForSemaphoreWaiters(PthreadSem* sem, int expected) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (APS5_Test_GetPthreadSemWaiterCount(sem) != expected) { + if (std::chrono::steady_clock::now() >= deadline) Require(false); + std::this_thread::yield(); + } +} + +static void WaitForSemaphoreApiPause() { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (APS5_Test_GetPthreadSemApiPauseCount() != 1) { + if (std::chrono::steady_clock::now() >= deadline) Require(false); + std::this_thread::yield(); + } +} + static void CheckValueLimit() { constexpr unsigned int maximum = 0x7fffffffu; PthreadSem sem = nullptr; @@ -66,6 +87,16 @@ static void* APS5_VABI Poster(void* arg) { return nullptr; } +struct CancellableWaitContext { + PthreadSem* sem; +}; + +static void* APS5_VABI CancellableWaiter(void* arg) { + auto& context = *static_cast(arg); + (void)scePthreadSemWait(context.sem); + return nullptr; +} + int main() { CheckValueLimit(); @@ -90,9 +121,59 @@ int main() { Require(scePthreadJoin(thread, nullptr) == SCE_OK); Require(scePthreadSemDestroy(&context.sem) == SCE_OK); - bool rejected = false; - try { scePthreadSemWait(&context.sem); } - catch (const std::runtime_error&) { rejected = true; } - Require(rejected); + Require(scePthreadSemDestroy(&context.sem) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadSemDestroy(&sem) == SCE_OK); + + PthreadSem busySem = nullptr; + Require(scePthreadSemInit(&busySem, 0, 0, nullptr) == SCE_OK); + std::atomic waitResult{-1}; + std::thread waiterThread([&] { + waitResult = scePthreadSemWait(&busySem); + }); + WaitForSemaphoreWaiters(&busySem, 1); + Require(scePthreadSemDestroy(&busySem) == SCE_KERNEL_ERROR_EBUSY); + Require(scePthreadSemPost(&busySem) == SCE_OK); + waiterThread.join(); + Require(waitResult == SCE_OK); + Require(scePthreadSemDestroy(&busySem) == SCE_OK); + + PthreadSem cancelSem = nullptr; + Require(scePthreadSemInit(&cancelSem, 0, 0, nullptr) == SCE_OK); + CancellableWaitContext cancelContext{&cancelSem}; + Pthread cancelThread = nullptr; + Require(scePthreadCreate(&cancelThread, nullptr, CancellableWaiter, &cancelContext, nullptr) == SCE_OK); + WaitForSemaphoreWaiters(&cancelSem, 1); + Require(scePthreadCancel(cancelThread) == SCE_OK); + Require(scePthreadJoin(cancelThread, nullptr) == SCE_OK); + Require(APS5_Test_GetPthreadSemWaiterCount(&cancelSem) == 0); + Require(scePthreadSemDestroy(&cancelSem) == SCE_OK); + + for (int iter = 0; iter < 50; ++iter) { + PthreadSem raceSem = nullptr; + Require(scePthreadSemInit(&raceSem, 0, 0, nullptr) == SCE_OK); + std::thread racer([&] { + Require(scePthreadSemWait(&raceSem) == SCE_OK); + Require(scePthreadSemDestroy(&raceSem) == SCE_OK); + }); + WaitForSemaphoreWaiters(&raceSem, 1); + Require(scePthreadSemPost(&raceSem) == SCE_OK); + racer.join(); + } + + PthreadSem queuedSem = nullptr; + Require(scePthreadSemInit(&queuedSem, 0, 0, nullptr) == SCE_OK); + APS5_Test_SetPthreadSemApiPause(1); + std::atomic postResult{-1}; + std::thread concurrentPost([&] { + try { + postResult = scePthreadSemPost(&queuedSem); + } catch (...) { + postResult = -2; + } + }); + WaitForSemaphoreApiPause(); + Require(scePthreadSemDestroy(&queuedSem) == SCE_OK); + APS5_Test_ReleasePthreadSemApiPause(); + concurrentPost.join(); + Require(postResult == SCE_KERNEL_ERROR_EINVAL); } diff --git a/core/libs/tests/GuestRwlockRetire.cpp b/core/libs/tests/GuestRwlockRetire.cpp new file mode 100644 index 000000000000..f622a0ee6d43 --- /dev/null +++ b/core/libs/tests/GuestRwlockRetire.cpp @@ -0,0 +1,53 @@ +#include "SceTypes.hpp" +#include "prx/libkernel/Pthread/include/Rwlock.hpp" +#include +#include +#include + +static void Require(bool result) { + if (!result) std::abort(); +} + +static void CheckHandoff(bool write) { + constexpr int rounds = 2000; + std::atomic phase{0}; + PthreadRwlock published = nullptr; + std::thread successor([&] { + for (int round = 0; round < rounds; ++round) { + phase.wait(0); + auto slot = published; + phase.store(2); + phase.notify_one(); + for (;;) { + const int result = scePthreadRwlockTrywrlock(&slot); + if (result == 0) break; + Require(result == static_cast(0x80020010u)); + std::this_thread::yield(); + } + Require(scePthreadRwlockUnlock(&slot) == 0); + Require(scePthreadRwlockDestroy(&slot) == 0); + phase.store(3); + phase.notify_one(); + phase.wait(3); + } + }); + for (int round = 0; round < rounds; ++round) { + PthreadRwlock slot = nullptr; + Require(scePthreadRwlockInit(&slot, nullptr, "retire") == 0); + Require((write ? scePthreadRwlockWrlock(&slot) : scePthreadRwlockRdlock(&slot)) == 0); + published = slot; + phase.store(1); + phase.notify_one(); + phase.wait(1); + Require(scePthreadRwlockUnlock(&slot) == 0); + while (phase.load() != 3) phase.wait(2); + phase.store(0); + phase.notify_one(); + } + successor.join(); +} + +int main() { + CheckHandoff(false); + CheckHandoff(true); +} diff --git a/core/libs/tests/GuestStatvfs.cpp b/core/libs/tests/GuestStatvfs.cpp index a71484b6f8ce..ad1f8e5de027 100644 --- a/core/libs/tests/GuestStatvfs.cpp +++ b/core/libs/tests/GuestStatvfs.cpp @@ -64,7 +64,12 @@ static std::size_t OpenHandles() { Require(GetProcessHandleCount(GetCurrentProcess(), &count) != 0); return count; #else - return static_cast(std::distance(std::filesystem::directory_iterator("/proc/self/fd"), +#ifdef __APPLE__ + const std::filesystem::path descriptors = "/dev/fd"; +#else + const std::filesystem::path descriptors = "/proc/self/fd"; +#endif + return static_cast(std::distance(std::filesystem::directory_iterator(descriptors), std::filesystem::directory_iterator{})); #endif } diff --git a/core/libs/tests/GuestVoice.cpp b/core/libs/tests/GuestVoice.cpp index 9b244b42d1d7..98c2b5f18790 100644 --- a/core/libs/tests/GuestVoice.cpp +++ b/core/libs/tests/GuestVoice.cpp @@ -19,6 +19,8 @@ int APS5_VABI sceVoiceGetPortInfo(std::uint32_t, VoicePortInfo*); int APS5_VABI sceVoiceReadFromOPort(std::uint32_t, void*, std::uint32_t*); int APS5_VABI sceVoiceResetPort(std::uint32_t); int APS5_VABI sceVoiceGetResourceInfo(VoiceResourceInfo*); +int APS5_VABI sceVoiceEnableChat(void); +int APS5_VABI sceVoiceDisableChat(void); } namespace { @@ -46,6 +48,8 @@ std::uint32_t CreatePort(std::int32_t type, float volume) { int main() { RequireThrows([] { sceVoiceResetPort(0); }); RequireThrows([] { VoiceResourceInfo info{}; sceVoiceGetResourceInfo(&info); }); + RequireThrows([] { sceVoiceEnableChat(); }); + RequireThrows([] { sceVoiceDisableChat(); }); VoiceInitParam init{}; VoiceStartParam start{}; Require(sceVoiceInit(&init, 0) == 0); @@ -58,6 +62,8 @@ int main() { Require(resourceInfo.max_device_input_ports == 1); Require(resourceInfo.max_device_output_ports == 1); Require(resourceInfo.max_ports == 64); + Require(sceVoiceEnableChat() == 0); + Require(sceVoiceDisableChat() == 0); const auto input = CreatePort(2, 0.5f); const auto output = CreatePort(3, 0.25f); Require(sceVoiceConnectIPortToOPort(input, output) == 0); @@ -87,4 +93,6 @@ int main() { Require(sceVoiceEnd_nid_postfix() == 0); RequireThrows([&] { sceVoiceResetPort(output); }); RequireThrows([] { VoiceResourceInfo info{}; sceVoiceGetResourceInfo(&info); }); + RequireThrows([] { sceVoiceEnableChat(); }); + RequireThrows([] { sceVoiceDisableChat(); }); } diff --git a/core/libs/tests/UnwindCursor.cpp b/core/libs/tests/UnwindCursor.cpp new file mode 100644 index 000000000000..e21b53901473 --- /dev/null +++ b/core/libs/tests/UnwindCursor.cpp @@ -0,0 +1,223 @@ +#include "prx/libc/include/exceptions/Unwind.hpp" +#include "prx/libc/src/specifics/x86_64/RegisterContext.cpp" + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#define CURSOR_TEST_EXPORT [[gnu::dllexport]] +#else +#define CURSOR_TEST_EXPORT [[gnu::visibility("default")]] +#endif + +extern "C" _Unwind_Reason_Code APS5_VABI _Unwind_Backtrace_nid_postfix(_Unwind_Trace_Fn trace, void* argument); +extern "C" std::uintptr_t APS5_VABI _Unwind_GetRegionStart_nid_postfix(_Unwind_Context* context); +extern "C" std::uintptr_t APS5_VABI _Unwind_GetLanguageSpecificData_nid_postfix(_Unwind_Context* context); +extern "C" [[gnu::noinline]] CURSOR_TEST_EXPORT std::uintptr_t UnwindCursorProbe(int mode); +extern "C" [[gnu::noinline]] CURSOR_TEST_EXPORT std::uintptr_t UnwindCursorCaller(int mode); + +namespace { + +struct ProcedureInfo { + std::uintptr_t start; + std::uintptr_t end; + std::uintptr_t lsda; + std::uintptr_t handler; + std::uintptr_t gp; + std::uintptr_t flags; + std::uint32_t format; + std::uint32_t unwindInfoSize; + std::uintptr_t unwindInfo; + std::uintptr_t extra; +}; + +using Destroy = void (APS5_VABI *)(_Unwind_Context*); +using ValidRegister = bool (APS5_VABI *)(_Unwind_Context*, int); +using GetRegister = std::uintptr_t (APS5_VABI *)(_Unwind_Context*, int); +using SetRegister = void (APS5_VABI *)(_Unwind_Context*, int, std::uintptr_t); +using GetFloatRegister = double (APS5_VABI *)(_Unwind_Context*, int); +using SetFloatRegister = void (APS5_VABI *)(_Unwind_Context*, int, double); +using Step = int (APS5_VABI *)(_Unwind_Context*, bool); +using GetInfo = void (APS5_VABI *)(_Unwind_Context*, ProcedureInfo*); +using JumpTo = void (APS5_VABI *)(_Unwind_Context*); +using IsSignalFrame = bool (APS5_VABI *)(_Unwind_Context*); +using GetFunctionName = bool (APS5_VABI *)(_Unwind_Context*, char*, std::size_t, std::uintptr_t*); +using SetInfo = void (APS5_VABI *)(_Unwind_Context*, bool); +using GetRegisterName = const char* (APS5_VABI *)(_Unwind_Context*, int); + +enum Mode { Checks, GetUnknownRegister, SetUnknownRegister, GetFloat, SetFloat }; + +volatile int jumps = 0; + +template Function Slot(const _Unwind_Context& context, int index) { + return reinterpret_cast(context.cursor[index]); +} + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "unwind cursor check failed: %s\n", message); + std::exit(1); +} + +ProcedureInfo Info(_Unwind_Context& context) { + ProcedureInfo info; + std::memset(&info, 0xa5, sizeof(info)); + Slot(context, 9)(&context, &info); + return info; +} + +std::string FunctionName(_Unwind_Context& context, std::size_t length, std::uintptr_t& offset) { + char buffer[64]; + std::memset(buffer, 'x', sizeof(buffer)); + offset = 0; + if (!Slot(context, 12)(&context, buffer, length, &offset)) return {}; + return buffer; +} + +void CheckRegisters(_Unwind_Context& context) { + const auto valid = Slot(context, 2); + const auto get = Slot(context, 3); + const auto set = Slot(context, 4); + Require(valid(&context, -1) && valid(&context, -2) && valid(&context, 0) && valid(&context, 16), "registers -2 to 16 are valid"); + Require(!valid(&context, -3) && !valid(&context, 17), "registers outside -2 to 16 are not valid"); + Require(get(&context, -1) == context.registers[16] && get(&context, 16) == context.registers[16], "UNW_REG_IP and rip read the instruction pointer"); + Require(get(&context, -2) == context.registers[7] && get(&context, 7) == context.registers[7], "UNW_REG_SP and rsp read the stack pointer"); + const auto rbx = get(&context, 3); + set(&context, 3, rbx ^ 1); + Require(context.registers[3] == (rbx ^ 1), "setReg writes rbx"); + set(&context, 3, rbx); + Require(!Slot(context, 5)(&context, 17), "no float register is valid"); + Require(!Slot(context, 11)(&context), "a captured frame is not a signal frame"); +} + +void CheckRegisterNames(_Unwind_Context& context) { + const auto name = Slot(context, 14); + const auto named = [&](int number, const char* expected) { return std::strcmp(name(&context, number), expected) == 0; }; + Require(named(-1, "rip") && named(-2, "rsp") && named(0, "rax") && named(3, "rbx") && named(7, "rsp") && named(15, "r15") && named(16, "rip"), + "general register names"); + Require(named(17, "xmm0") && named(32, "xmm15") && named(33, "unknown register") && named(-3, "unknown register"), "vector and unknown register names"); +} + +void CheckInfo(_Unwind_Context& context, std::uintptr_t probe) { + const auto ip = context.registers[16]; + const auto info = Info(context); + Require(info.start == probe && info.start < ip && ip < info.end, "getInfo gives the range of the function holding the IP"); + Require(info.unwindInfo != 0 && info.format == 0x04000000 && info.flags == 0, "getInfo gives the FDE in DWARF mode"); + std::uint32_t length; + std::memcpy(&length, reinterpret_cast(info.unwindInfo), sizeof(length)); + Require(info.unwindInfoSize == length + 4, "the unwind info size covers the whole FDE"); + Require(info.extra != 0 && info.extra <= info.start, "getInfo gives the start of the segment holding the function"); + Require(info.start == _Unwind_GetRegionStart_nid_postfix(&context) && info.lsda == _Unwind_GetLanguageSpecificData_nid_postfix(&context), + "getInfo agrees with _Unwind_GetRegionStart and _Unwind_GetLanguageSpecificData"); +} + +_Unwind_Reason_Code TraceFirstFrame(_Unwind_Context* context, void* argument) { + *static_cast(argument) = Info(*context); + return _URC_NORMAL_STOP; +} + +void CheckCursor(_Unwind_Context& context, const ProcedureInfo& traced) { + const auto probe = reinterpret_cast(&UnwindCursorProbe); + const auto caller = reinterpret_cast(&UnwindCursorCaller); + const auto ip = context.registers[16]; + const auto set = Slot(context, 4); + const auto setInfo = Slot(context, 13); + const auto step = Slot(context, 8); + setInfo(&context, true); + CheckInfo(context, probe); + CheckRegisters(context); + CheckRegisterNames(context); + + std::uintptr_t offset = 0; + Require(FunctionName(context, 64, offset) == "UnwindCursorProbe" && offset == ip - probe, "getFunctionName names the exported probe"); + Require(FunctionName(context, 8, offset) == "UnwindC" && offset == ip - probe, "getFunctionName truncates the name to the buffer"); + + set(&context, -1, caller); + setInfo(&context, false); + Require(Info(context).start == caller, "setInfoBasedOnIPRegister(false) looks the new IP up as it is"); + set(&context, -1, 0); + setInfo(&context, false); + const ProcedureInfo none {}; + const auto missing = Info(context); + Require(std::memcmp(&missing, &none, sizeof(none)) == 0, "getInfo is all zero without unwind info"); + Require(step(&context, false) == 0 && context.registers[16] == 0, "step ends without moving when there is no unwind info"); + set(&context, -1, ip); + setInfo(&context, true); + CheckInfo(context, probe); + + _Unwind_Context walk = context; + Require(step(&walk, false) == 1, "step moves to the caller"); + Require(Info(walk).start == caller && walk.registers[7] > context.registers[7], "step reaches UnwindCursorCaller"); + Require(FunctionName(walk, 64, offset) == "UnwindCursorCaller" && offset == walk.registers[16] - caller, "getFunctionName names the caller"); + int frames = 1; + int result; + while ((result = step(&walk, false)) == 1 && frames < 4096) ++frames; + Require(result == 0 && frames >= 2, "step reaches the end of the stack"); + Require(traced.start == probe && traced.end == Info(context).end, "_Unwind_Backtrace passes a cursor that describes its frame"); + + Slot(context, 0)(&context); + Slot(context, 1)(&context); + Require(context.registers[16] == ip && Info(context).start == probe, "the destructors leave the cursor alone"); +} + +[[gnu::noinline]] void JumpBack(_Unwind_Context& context) { + Slot(context, 10)(&context); +} + +[[gnu::noinline]] void CheckJumpTo() { + _Unwind_Context context; + LibcCaptureRegisters(context.registers); + const int seen = jumps; + jumps = seen + 1; + if (seen == 0) JumpBack(context); + Require(jumps == 2, "jumpto resumes at the captured registers"); +} + +} + +extern "C" std::uintptr_t UnwindCursorProbe(int mode) { + _Unwind_Context context; + LibcCaptureRegisters(context.registers); + ProcedureInfo traced {}; + switch (mode) { + case Checks: + Require(_Unwind_Backtrace_nid_postfix(TraceFirstFrame, &traced) == _URC_NORMAL_STOP, "_Unwind_Backtrace stops when the callback asks"); + CheckCursor(context, traced); + break; + case GetUnknownRegister: Slot(context, 3)(&context, 17); break; + case SetUnknownRegister: Slot(context, 4)(&context, 17, 0); break; + case GetFloat: Slot(context, 6)(&context, 17); break; + case SetFloat: Slot(context, 7)(&context, 17, 1.0); break; + } + return context.registers[16]; +} + +extern "C" std::uintptr_t UnwindCursorCaller(int mode) { + return UnwindCursorProbe(mode) + 1; +} + +int main(int argc, char** argv) { +#ifdef _WIN32 + _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); +#else + std::signal(SIGABRT, [](int) { std::_Exit(3); }); +#endif + const std::string mode = argc > 1 ? argv[1] : ""; + if (mode == "get-register") UnwindCursorCaller(GetUnknownRegister); + else if (mode == "set-register") UnwindCursorCaller(SetUnknownRegister); + else if (mode == "get-float-register") UnwindCursorCaller(GetFloat); + else if (mode == "set-float-register") UnwindCursorCaller(SetFloat); + else { + Require(mode.empty(), "unknown mode"); + UnwindCursorCaller(Checks); + CheckJumpTo(); + std::puts("unwind cursor tests passed"); + return 0; + } + std::fprintf(stderr, "the cursor returned from %s\n", mode.c_str()); + return 1; +} diff --git a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp index 480d0aebbd11..e06bfa790fd6 100644 --- a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp @@ -700,6 +700,11 @@ std::vector ResourceMaterializer::RuntimeImageModes(const ImageRe volume.dimension = RdnaImageDimension::Dim3D; modes.push_back(volume); } + if (image.dimension == RdnaImageDimension::Dim2DMsaa && image.fmaskCompatible && image.cubeFaceCompatible && !storage && !depth && conversion == IrBufferFormat::Invalid && packed == IrBufferFormat::Invalid && image.byElements == 0u) { + auto plane = mode; + plane.dimension = RdnaImageDimension::Dim2D; + modes.push_back(plane); + } if (image.dimension == RdnaImageDimension::Dim3D && image.flatVolumeCompatible && !storage && !depth && conversion == IrBufferFormat::Invalid && packed == IrBufferFormat::Invalid) { for (const auto dimension : {RdnaImageDimension::Dim2D, RdnaImageDimension::Dim2DArray}) { auto plane = mode; @@ -717,7 +722,8 @@ std::vector ResourceMaterializer::RuntimeImageModes(const ImageRe line.dimension = RdnaImageDimension::Dim1D; modes.push_back(line); } - if (image.dimension == RdnaImageDimension::Dim2D && image.cubeFaceCompatible && !storage && !depth && conversion == IrBufferFormat::Invalid && packed == IrBufferFormat::Invalid) { + const bool cubeFaceStorage = image.written && !image.read && !image.atomic && image.mipMode != ImageMipMode::DynamicStorage; + if (image.dimension == RdnaImageDimension::Dim2D && image.cubeFaceCompatible && (!storage || cubeFaceStorage) && !depth && conversion == IrBufferFormat::Invalid && packed == IrBufferFormat::Invalid) { auto face = mode; face.dimension = RdnaImageDimension::Dim2DArray; face.cube = true; diff --git a/core/shader/recompiler/Optimization/src/ResourceTracker.cpp b/core/shader/recompiler/Optimization/src/ResourceTracker.cpp index 800a3a45eea7..67109a46b9e1 100644 --- a/core/shader/recompiler/Optimization/src/ResourceTracker.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceTracker.cpp @@ -1291,7 +1291,8 @@ class Tracker { image.constantSwizzleCompatible = image.constantSwizzleCompatible && (op == IrOpcode::ImageSampleRaw || op == IrOpcode::ImageGatherRaw); const bool cubeFaceLoad = op == IrOpcode::ImageRead && !memory.imageHasMip && memory.imageSampleFlags == 0u; const bool cubeFaceSample = op == IrOpcode::ImageSampleRaw && (memory.imageSampleFlags == 0u || memory.imageSampleFlags == RdnaImageSampleFlagLevelZero || memory.imageSampleFlags == RdnaImageSampleFlagLod); - image.cubeFaceCompatible = image.cubeFaceCompatible && (cubeFaceLoad || cubeFaceSample) && !memory.imagePacked && memory.imageByElements == 0u; + const bool cubeFaceStore = op == IrOpcode::ImageWrite && !memory.imageHasMip; + image.cubeFaceCompatible = image.cubeFaceCompatible && (cubeFaceLoad || cubeFaceSample || cubeFaceStore) && !memory.imagePacked && memory.imageByElements == 0u; const bool flatVolumeLoad = op == IrOpcode::ImageRead && memory.imageSampleFlags == 0u; const bool flatVolumeSample = op == IrOpcode::ImageSampleRaw && memory.imageSampleFlags == RdnaImageSampleFlagLevelZero; const bool flatVolumeQuery = op == IrOpcode::ImageQueryDimensions; diff --git a/docs/dev/debt/libSceAgc.md b/docs/dev/debt/libSceAgc.md index 7d871117ed65..985c36c8bb1e 100644 --- a/docs/dev/debt/libSceAgc.md +++ b/docs/dev/debt/libSceAgc.md @@ -2,6 +2,8 @@ ### Unknown function info +- [GS shader-half fusion](../../../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - `SPI_SHADER_PGM_RSRC2_GS.OC_LDS_EN` is preserved if either half requests it, while user SGPR allocation comes from the front half and other state stays with the back half. Bit 18 is `OC_LDS_EN` in AMD's [GFX10 register definitions](https://github.com/torvalds/linux/blob/master/drivers/gpu/drm/amd/include/asic_reg/gc/gc_10_1_0_sh_mask.h). This is inferred from retaining both halves' requirements; console fusion output has not been measured. Cocoon (PPSA08766) requests front `0x70004` and back `0xab0020`. Other unmatched hardware state still throws. + - PPSA01341 imports declared without parameters, signatures unknown: [sceAgcSetSemaphoreMemory](../../../core/libs/prx/libSceAgc/Unimplemented.cpp). Name from the shadPS4 aerolib NID list - [sceAgcWaitRegMemPatchMask](../../../core/libs/prx/libSceAgc/Patch/src/WaitRegMem.cpp) (libSceAgc) - follows `sceAgcWaitRegMemPatchReference`: the mask is taken as a 32-bit value and written to the low mask word of a 32- or 64-bit wait, as [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/main/src/game_compat.c) does; [sharpemu](https://github.com/sharpemu/sharpemu/blob/main/src/SharpEmu.Libs/Agc/AgcExports.PacketPatching.cs) writes both words of a 64-bit wait's mask. A mask above 32 bits throws - [sceAgcDcbSetCfRegisterDirect, sceAgcDcbSetCfRegisterRangeDirect](../../../core/libs/prx/libSceAgc/DcbState/src/Registers.cpp) (libSceAgc) - signatures follow [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/main/src/game_compat.c) and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Agc/SceAgc.cs), which agree; the SET_CONFIG_REG (0x68) packet is OpenAGC's. The driver does not execute SET_CONFIG_REG, so a submitted packet throws diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index beb400f75e4d..b011fd51ad8c 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -6,6 +6,8 @@ ### Unknown function info +- [Initial point context](../../../core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp) - new queues and CLEAR_STATE seed PA_SU_POINT_SIZE 0x00080008 and PA_SU_POINT_MINMAX 0xffff0000, matching every public context table in libSceAgc/Command/include/RegisterDefaultsData.hpp. Cocoon (PPSA08766) reaches geometry point output without writing these registers in the observed startup. Applying the SDK context defaults to initial/CLEAR_STATE driver context is inferred; GPU power-on reset state is not measured and is not the source of these values. Explicit writes and pushed/restored contexts retain their own values. + - [Optional BMP frame dumps](../../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) use checked host stdio. An open, header, pixel or close failure prints one diagnostic and disables subsequent BMP dumps for the process; queued frames drain without more file I/O. Pixel validation and allocation failures retain the existing fatal path. The CPU fixture checks identical BMP bytes and real pipe failures. VideoOut flips on an RTX 3060 continue after a first dump that cannot be opened; header, pixel and close failures during presentation are unmeasured. - [Inactive vertex point size](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) - `PA_CL_VS_OUT_CNTL.USE_VTX_POINT_SIZE` is accepted for lines, triangles and rectangles with `GS_EN` and `HS_EN` clear, when polygon mode is disabled or both faces are filled. Points, patches and absent routing or rasterization registers still throw. Bit 16 selects the misc export's X component as PointSize ([AMD CIK register reference](https://www.amd.com/content/dam/amd/en/documents/radeon-tech-docs/programmer-references/CIK_3D_registers_v2.pdf), PA_CL_VS_OUT_CNTL); ignoring it for non-point rasterization follows [Vulkan PointSize](https://docs.vulkan.org/refpages/latest/refpages/source/PointSize.html). This is inferred, not measured on console hardware. XashPS5 (PPSA19111 v1.3) sets `0x01210000` on a filled triangle draw. diff --git a/docs/dev/debt/libSceHmd.md b/docs/dev/debt/libSceHmd.md index 1206760ae424..2f261089361d 100644 --- a/docs/dev/debt/libSceHmd.md +++ b/docs/dev/debt/libSceHmd.md @@ -3,3 +3,7 @@ ### Silent stubs - [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. + +### Unknown function info + +- [libSceHmd distortion and field of view](../../../core/libs/prx/libSceHmd/Export.cpp) (libSceHmd) - `sceHmdDistortionInitialize`, `sceHmdDistortionGet2dVrCommand`, `sceHmdDistortionGetCorrectionCommand`, `sceHmdDistortionGetWorkMemorySize`, `sceHmdDistortionGetWorkMemoryAlign`, `sceHmdFillDistortionBuffer`, `sceHmdGet2DEyeOffset`, `sceHmdGet2dDistortionMap`, `sceHmdGet2dVrCommand`, `sceHmdGetDistortionCorrectionCommand`, `sceHmdGetDistortionMap`, `sceHmdGetDistortionParams`, `sceHmdGetDistortionWorkMemoryAlign`, `sceHmdGetDistortionWorkMemorySize`, `sceHmdGetEyeStatus`, `sceHmdGetFieldOfView` are declared without parameters from the firmware export database (aerolib.csv); their guest signatures are unverified on hardware and every call throws diff --git a/docs/dev/debt/libSceVoice.md b/docs/dev/debt/libSceVoice.md index 8d57d43d4a29..5983d70f3922 100644 --- a/docs/dev/debt/libSceVoice.md +++ b/docs/dev/debt/libSceVoice.md @@ -8,3 +8,4 @@ - [sceVoiceResetPort](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - the `(uint32_t portId)` signature is the PS Vita one in [VitaSDK](https://github.com/vitasdk/vita-headers/blob/ae5808f3105c42fa3373ba4ab64ab54e2e3ca848/include/psp2/voice.h#L503-L514) and the PS3 `cellVoiceResetPort` in [RPCS3](https://github.com/RPCS3/rpcs3/blob/be69c054eeac57e2e671697a463473d37fe5a7b2/rpcs3/Emu/Cell/Modules/cellVoice.cpp#L493-L510); that the PS5 library takes the same argument is assumed. A reset clears a port's buffered data, and ports here buffer nothing (`sceVoiceReadFromOPort` returns 0 bytes and `sceVoiceWriteToIPort` throws), so it only checks that the library is initialized and the port exists, as RPCS3 does, and keeps the port's connections, volume and mute flag. An uninitialized library or an unknown port throws like the other port calls; the console's error codes are not measured. PPSA23566 imports it - [sceVoiceGetResourceInfo](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - the `(VoiceResourceInfo* info)` signature and port limits (7 voice inputs, 7 voice outputs, 1 device input, 1 device output, 64 total ports) are the PS Vita ones in [VitaSDK](https://github.com/vitasdk/vita-headers/blob/ae5808f3105c42fa3373ba4ab64ab54e2e3ca848/include/psp2/voice.h#L150-L157); that the PS5 library uses the same layout and limits is assumed. An uninitialized library or a null output pointer throws like the other Voice calls; console limits and error codes are not measured. PPSA23566 imports it +- [sceVoiceEnableChat, sceVoiceDisableChat](../../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) take no arguments and return 0, following [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Audio/AudioOut2.cs#L460-L464) and the `sceAudioOut2EnableChat` model. The host mixes no chat audio, so the calls only record whether chat audio is enabled and fail when the library is not initialized. Console error codes and chat session requirements are unverified. PPSA23566 imports them diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index f312e93f6f83..415d4375ab7b 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -22,6 +22,7 @@ - [atanl, sinl, cosl, tanl, sinhl, coshl, tanhl, expl, exp2l, logl, log10l and powl](../../../core/libs/prx/libc/src/Math.cpp) (libc) - computed by the host's math library, as `acosl` is, not by the console's FreeBSD msun ld80 code: results can differ from the console's in the last bit (glibc's `log10l(1000)` is not exactly 3), and on Linux glibc sets `errno` to `EDOM` or `ERANGE` where msun only raises the floating-point exception. `ldexpl` and `truncl` are exact operations and give the console's result; `logb` and `remainder` give the console's value too, but on Linux glibc sets `errno` (`ERANGE` for `logb(0)`, `EDOM` for `remainder` by zero or of an infinity) where msun only raises the floating-point exception - [std_execute_once](../../../core/libs/prx/libc/Export.cpp) (libc) - assumed to be the plain-named twin of `std::_Execute_once` (`_ZSt13_Execute_onceRSt9once_flagPFiPvS1_PS1_ES1_`, the callee of Dinkumware `call_once`): it delegates there and gets the [thread.once.callonce] semantics already verified for that symbol (one invocation per flag, concurrent callers block until it completes, and a failing or throwing invocation leaves the flag unready so the next caller runs it). The name equivalence is not confirmed on a title, and the callback convention (called with (null, arg, null), non-zero return meaning success) is the twin's, unverified for this symbol - [Dinkumware mutexes and conditions](../../../core/libs/prx/libc/src/Threads.cpp) (libc) use pointer-to-handle arguments, matching the console aerolib signatures reported in [#3079](https://github.com/boykopovar/AnyPS5/pull/3079). The title's `_Mtx_lock` maps `0` to `_Thrd_success` (0), `0x8002000b` to `_Thrd_busy` (3), and other failures to `_Thrd_error` (4); its internal mutex init recognizes recursive flag `0x100`, while every other type uses default attributes. The observed relock failure for a plain mutex suggests those defaults are error-checking, but that has not been confirmed on hardware. The title's `_Mtx_unlock` and `_Cnd_wait` return success regardless of their internal result. The host implementation treats invalid ownership preconditions as no-ops, and throws for invalid handles, host wait errors and destroying a locked mutex as debug guards; those cases are not confirmed against the console. +- [Floating-point compiler-rt builtins](../../../core/libs/prx/libc/src/FloatBuiltins.cpp) (libc) follow the `libc.prx` bundled with PPSA08427 (disassembly, and that binary run side by side on random inputs) where it differs from current compiler-rt: the soft-float `__add`, `__sub`, `__mul` and `__div` (`sf3`, `df3`) round to nearest, raise no exception flags and ignore MXCSR, here done with SSE under a default MXCSR that is restored afterwards; division flushes a quotient below the smallest normal to a signed zero; `__extendsfdf2` and `__truncdfsf2` copy NaN payloads unshifted; float-to-integer conversions do not saturate but shift the significand by a count masked to the shift width. `__mulsc3`, `__muldc3`, `__divsc3` and `__divdc3` fuse the same products as the console with `fma` and scale through the x87 like its `scalbn`, so their values match, but NaN payloads and exception flags can differ, as the host `fma` and `logb` are not the console's VEX FMA code and libm - [random, srandom, initstate, setstate](../../../core/libs/prx/libc/src/Random.cpp) (libc) follow FreeBSD 11 [random.c](https://github.com/freebsd/freebsd-src/blob/4ab2e064d7950be84256d671a7ae93f87cc6aa36/lib/libc/stdlib/random.c): the additive feedback generator on a default 128-byte `TYPE_3` table, `good_rand` seeding, state sizes rounded down to 8, 32, 64, 128 or 256 bytes, and `setstate` returning the previous state or null for a rear position past the degree. `initstate` below 8 bytes returns null without setting errno, as that file and its manual do (glibc sets `EINVAL`). FreeBSD does not lock the generator; here every call is serialised by one lock, so concurrent callers draw the serial sequence between them. A null state throws, where FreeBSD faults. That the console's `libSceLibcInternal` uses this FreeBSD code and not another generator is unverified - [qsort](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) follows FreeBSD 11 [qsort.c](https://github.com/freebsd/freebsd-src/blob/5ed7eb0d97ba4436218e810f61bd059acba984c2/lib/libc/stdlib/qsort.c): Bentley and McIlroy's quicksort, with insertion sort below 7 elements and after a partition pass that swaps nothing, so elements that compare equal end in FreeBSD's order and the comparator is called in the same sequence. That the console's libc uses this FreeBSD code is unverified - [memcpy_s and memmove_s](../../../core/libs/prx/libc/src/Strings.cpp) (libc) use the C11 K.3.7.1.1 and K.3.7.1.2 size constraints, with RSIZE_MAX taken as SIZE_MAX >> 1 as in memset_s. An oversized destination size returns ERANGE (34) without touching the destination; the console's RSIZE_MAX and exact error value are unverified. Constraint violations return an error without invoking the registered constraint handler, as before. @@ -65,7 +66,7 @@ - [sceKernelSync](../../../core/libs/prx/libc/src/General.cpp) (libkernel) flushes only the host files and directories recorded through `RecordWrittenPath_nid_no_patch` (the libc, libkernel, save data and app content file functions) and their changed parent directories. A host write in a library that does not record its path is not flushed by `sceKernelSync` - [sceKernelSync](../../../core/libs/prx/libc/src/General.cpp) (libkernel) on Windows skips a recorded file it cannot open for writing (read-only, or held open by a handle that denies write sharing), since `FlushFileBuffers` needs a writable handle. On Linux, it skips a recorded file it cannot open for reading. Such a file's data may not reach the disk before a host crash; the guest reads the same data either way - [`fopen` streams](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) on Windows share delete access, like the descriptors [`sceKernelOpen`](../../../core/libs/prx/libkernel/File/src/Open.cpp) opens, so a file the guest holds open can be removed or renamed. A stream reopened with `freopen` still goes through `_wfreopen`, which does not share delete access. On Windows, opening a directory in a read mode fails with `EACCES`, where FreeBSD opens it; directory streams are not emulated -- The [`_Unwind_Context`](../../../core/libs/prx/libc/include/exceptions/Unwind.hpp) that `_Unwind_Backtrace` passes to its callback starts with a table laid out like LLVM libunwind's `AbstractUnwindCursor` virtual table, because homebrew built with the ps5-payload-dev SDK links libunwind statically and reads the frames with its own `_Unwind_GetIP` (`__unw_get_reg`), which calls through that table. Only register reads and writes (`validReg`, `getReg`, `setReg`, with `UNW_REG_IP` and `UNW_REG_SP`), `validFloatReg` (always false) and `isSignalFrame` are answered; the other slots throw. The layout follows libunwind's `UnwindCursor.hpp`, not the console's own `libSceLibcInternal`, which is not checked +- The [`_Unwind_Context`](../../../core/libs/prx/libc/include/exceptions/Unwind.hpp) that `_Unwind_Backtrace` passes to its callback starts with a table laid out like LLVM libunwind's `AbstractUnwindCursor` virtual table, because homebrew built with the ps5-payload-dev SDK links libunwind statically and reads the frames with its own `_Unwind_GetIP` (`__unw_get_reg`), which calls through that table. Its [slots](../../../core/libs/prx/libc/src/exception/Unwind.cpp) answer as `UnwindCursor` of [LLVM 18.1.8](https://github.com/llvm/llvm-project/blob/llvmorg-18.1.8/libunwind/src/UnwindCursor.hpp) does with DWARF unwind information: `getReg` and `setReg` of a register number outside -2 (`UNW_REG_SP`) to 16 (`rip`), `getFloatReg` and `setFloatReg` print libunwind's message and abort, as `_LIBUNWIND_ABORT` does; the destructors do nothing; `getInfo` gives the function's range, LSDA and personality from its FDE, `DW_CFA_GNU_args_size` as `gp`, `UNWIND_X86_64_MODE_DWARF` as `format`, the FDE as `unwind_info` and the start of the ELF segment or PE section holding the IP as `extra`, and all zero when no FDE covers the IP. Differences: `step` walks through Windows host frames that have no FDE with `RtlVirtualUnwind`, as `_Unwind_Backtrace` does, and `getInfo` reports them with no range, as `_Unwind_GetRegionStart` does; `step` looks a signal frame up at its IP instead of the IP minus one and ends at a frame whose rules leave `rsp` and `rip` unchanged, where libunwind would return that frame again; `getFunctionName` asks `dladdr` on Linux and macOS but takes the nearest export at or below the IP in the image holding it on Windows, since PE exports carry no size; `jumpto` restores the registers as `_Unwind_RaiseException` installs a landing pad, without `r10` and `r11`. The layout follows libunwind's `UnwindCursor.hpp`, not the console's own `libSceLibcInternal`, which is not checked - The Windows libc [exception unwinder](../../../core/libs/prx/libc/src/exception/Unwind.cpp) steps through frames of modules without the project's `.ehmeta` and `.ehfram` tables (system DLLs, and WinLibs GCC 15.2's `libstdc++-6.dll` and `libwinpthread-1.dll`, whose `.eh_frame` sections are empty) with their PE unwind data through `RtlVirtualUnwind`, but does not run Windows exception or termination handlers: it stops at a frame whose unwind data has one, such as a C++ cleanup through `__gxx_personality_seh0` in `libstdc++-6.dll` or a system DLL function like `qsort` in `ucrtbase.dll` (Windows 10 22H2), and the exception is then reported as unhandled - 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. - [getifaddrs](../../../core/libs/prx/libc/src/Interfaces.cpp) (libc) lists the host's IPv4 and IPv6 addresses in FreeBSD's `ifaddrs` and sockaddr layouts. FreeBSD's per-interface AF_LINK entries (MAC, index, MTU) and their `if_data` are not produced, so interfaces without an address are omitted. Names are the host's (Linux `lo`, `eth0`; Windows NDIS names such as `ethernet_32769`, or `if` when one does not fit 15 bytes), not the console's. A Linux point-to-point address without a peer loses `IFF_POINTOPOINT`, and an IPv4 broadcast address the host omits is computed from the netmask. On Windows the flags are only `IFF_UP`, `IFF_RUNNING`, `IFF_LOOPBACK`, `IFF_BROADCAST` and `IFF_MULTICAST`, derived from the adapter: PPP and tunnel adapters get neither `IFF_BROADCAST` nor `IFF_POINTOPOINT` because their peer address is not reported, and an address whose prefix length the host reports as unspecified gets a host mask. diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index c95047f0dd50..687760e5f6b6 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -205,7 +205,7 @@ - [pthread_cond_reltimedwait_np](../../../core/libs/prx/libkernel/Pthread/Posix/Cond.cpp) (libkernel) takes `(cond, mutex, usec)`, a relative timeout in microseconds, and is `scePthreadCondTimedwait` with POSIX error numbers. FreeBSD 11.4 has no such function; the signature comes only from shadPS4, which registers one function for this NID and for `scePthreadCondTimedwait` ([`condvar.cpp`](https://github.com/shadps4-emu/shadPS4/blob/3a9f57265d28975658f1ca941daa5c879c7a74bd/src/core/libraries/kernel/threads/condvar.cpp#L229-L234)). shadPS4 reads a 64-bit count; here it is the 32-bit `SceKernelUseconds` of the SCE twin, as for `sem_reltimedwait_np`. Unverified on the console - [sysctl kern.arandom](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) serves FreeBSD 11's MIB {1, 37} (CTL_KERN, KERN_ARANDOM) and the "kern.arandom" sysctlbyname lookup through std::random_device, returning min(*oldLength, 256) random bytes. Following FreeBSD's sysctl_kern_arnd, a null buffer reports min(*oldLength, 256) without writing, a null length returns 0 without writing, and requests with a new value return EPERM (1) as the node is read-only. Cryptographic quality depends on the host's random device implementation. - [pthread_atfork](../../../core/libs/prx/libkernel/Pthread/Posix/Atfork.cpp) (libkernel) appends the three handlers to a process-wide list and returns 0, or `ENOMEM` when the entry cannot be allocated, as FreeBSD 11.4's [`_pthread_atfork`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libthr/thread/thr_fork.c#L78-L100) does; null handlers are accepted. The handlers never run: `fork` throws and `vfork` is absent, so no child process is created. Handlers of a module that is unloaded stay in the list, where FreeBSD's `__pthread_cxa_finalize` removes them. PPSA97358 has a source-level call from the constructor `rcu_init` ([rcu.c](https://github.com/ZiZc3/XPSemu/blob/8eec07b4e31f66dce8e0b05c1ca1281af303e933/util/rcu.c#L488-L495)), but current-main runtime reachability is unverified; XPSemu currently stops first at the unresolved `mlockall` import (#4386) -- [sceKernelGetPageTableStats](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) reports page-table entries over the recorded protection ranges (committed mappings; reservations are not recorded): each range charges one entry per 2 MiB granule it spans to the cpu pool when it has CPU access bits (0x7) and to the gpu pool when it has GPU access bits (0x30), so a range with both counts in both and two ranges sharing one granule charge it once each, as KytyPS5 sums per committed range. Mappings with no access bits, such as PROT_NONE or ACP-only protections, charge neither pool. The 2048-entry budget per side, its constants, and whether the console counts reservations or guest image segments are unverified. Resonance: A Plague Tale Legacy (PPSA30601) reaches this call after the other missing exports of its start-up are provided +- [sceKernelGetPageTableStats](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) reports 8192 entries on each side and counts them as a PS5 (firmware 13.60) counted them for a payload process: one cpu entry for each 2 MiB granule that holds a committed mapping, whatever its protection (none included), and one gpu entry for each 1 GiB region that holds a GPU-accessible mapping, plus one for each 2 MiB granule of such a mapping that is not a whole granule of direct memory at a 2 MiB-aligned physical address. A granule or region shared by several mappings counts once, and reservations do not count. On the console the first GPU mapping of a process also takes three gpu entries and one cpu entry that stay after it is unmapped, which is not modelled, and only the ranges whose protection is recorded here are counted. A null pointer returns `SCE_KERNEL_ERROR_EFAULT`, where the console faults. Resonance: A Plague Tale Legacy (PPSA30601) calls it at start-up (#4067) - [clock_settime](../../../core/libs/prx/libkernel/Time/Time.cpp) (libkernel) validates like FreeBSD 11.4's [`kern_clock_settime`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_time.c#L396-L412) for a root caller: `EFAULT` for a null time, `EINVAL` for any clock but `CLOCK_REALTIME`, a negative second count or a nanosecond count outside 0 to 999999999. A valid `CLOCK_REALTIME` request throws, naming the requested time, since the host clock is never set and no guest clock offset is kept. FreeBSD checks `PRIV_CLOCK_SETTIME` first, which a jailed process fails with `EPERM`; whether a title holds it on the console is unverified. PS5MC (PPSA99420) imports it; no call site is known - [chflags](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) follows FreeBSD 11.4's [`kern_chflagsat`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_syscalls.c#L2366-L2383) and UFS's [`ufs_setattr`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/ufs/ufs/ufs_vnops.c#L509-L556) for a root caller: the path lookup reports `EFAULT`, `ENOENT` and `ENOTDIR`, a flag outside the 17 UFS stores (`VNOVAL` included) `EOPNOTSUPP`, and `SF_SNAPSHOT` `EPERM`. Neither host keeps BSD file flags, so every file is taken to have none (`stat` reports `st_flags` 0): flags 0 succeed without touching the file, and any other flag throws, naming the flags. `UF_READONLY`, `UF_HIDDEN`, `UF_SYSTEM` and `UF_ARCHIVE` are FreeBSD's names for the Windows attributes but are not mapped to them: UFS stores them without enforcing them, `FILE_ATTRIBUTE_READONLY` already carries the owner write bit of `chmod` on Windows, and Linux has no equivalent a process can set without `CAP_LINUX_IMMUTABLE`. The console's filesystem types, `EROFS` on read-only mounts and the change-time update are not modelled - [getpriority](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) follows FreeBSD 11.4's [`sys_getpriority`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_resource.c#L93-L165) for the only guest process, which is taken to run at nice 0: `PRIO_PROCESS` and `PRIO_PGRP` with 0 or its own pid (the guest is taken to lead its own process group, see `kill`) and `PRIO_USER` with 0 or its user id 0 return 0, any other id fails with `ESRCH` and any other `which` with `EINVAL`. As in FreeBSD's libc, where `getpriority` is the bare system call, errno is left unchanged on success. The nice value of a title process on the console is unverified, and the host nice of the process or of the threads `APS5_THREAD_PRIORITY=high` raises is not reported. `setpriority` accepts the same target ids (any other id fails with `ESRCH`, any other `which` with `EINVAL`), clamps the value to FreeBSD's `PRIO_MIN`-`PRIO_MAX` (-20 to 20) and stores it as the guest process's nice value, which `getpriority` returns; the host scheduling class is not changed, and whether a title process may lower the value without privilege is unverified diff --git a/docs/dev/debt/shader.md b/docs/dev/debt/shader.md index cf62583aa404..7a5f66b60562 100644 --- a/docs/dev/debt/shader.md +++ b/docs/dev/debt/shader.md @@ -8,6 +8,8 @@ ### Unknown function info +- [MSAA-address image loads on 2D textures](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) - 32-bit `image_load` with DIM 2D_MSAA reads a non-multisampled 2D T# as the same DIM 2D load, ignoring the sample index. Measured on RX 6650 XT (gfx1032), wave32 and wave64, for R32_UINT, R32_FLOAT, RGBA8_UNORM and RGBA8_SRGB, sample indices 0/1/2/7/31/0xffffffff and in-range/out-of-range coordinates; IEEE 0, DX10 clamp 1, denorm32 0, denorm16 3, round32/16 0, fp16 overflow 0. Pinned in ImageMsaaAddressOnPlane.cpp. Not measured on gfx1013. Stores, atomics, mip-bearing or A16 addresses, packed accesses, converted formats, depth-bit views, array-MSAA addresses and image-table accesses are not enabled by this compatibility mode. + - [MUBUF/MTBUF descriptors ending in VCC](../../../core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp): the four dword descriptor at s104 is s104, s105, vcc_lo and vcc_hi, as LLVM's [VCCTuple/SReg_128](https://github.com/llvm/llvm-project/blob/d8219b47422f7322db252ad29ca8ea87993fb433/llvm/lib/Target/AMDGPU/SIRegisterInfo.td#L422) and [buffer resource operands](https://github.com/llvm/llvm-project/blob/d8219b47422f7322db252ad29ca8ea87993fb433/llvm/lib/Target/AMDGPU/BUFInstructions.td) describe. Existing translation maps scalar codes106/107 to the raw VCC words. Recompiled loads/stores, both words, bounds, formats and wave32/wave64 execute on NVIDIA and AMD Vulkan; raw gfx10/gfx1013 hardware is not measured. Higher descriptor bases remain refused; the normal SGPR bound is unchanged. - [Packed pixel ancillary word](../../../core/shader/recompiler/Optimization/src/ConstantFolder.cpp) (shader recompiler) - the sample index at bits 8-11 and the render target index at bits 16-28 follow #295. The sample index is the SampleId builtin, an equality inferred, not measured. The render target index is the Layer builtin when the draw exports it (a layered draw, `PA_CL_VS_OUT_CNTL` bit 18; the pipeline specialization constant `LayerExported`) and 0 otherwise, the first layer Vulkan renders when no pre-rasterization stage writes Layer. Reading the unwritten Layer builtin instead makes AMD's Windows driver drop the fragment's writes (#4800); with the constant at 0 the load is folded away. Mesa reads both fields from those bits, with the layer 14 bits wide only from GFX12 on ([ac_nir_lower_intrinsics_to_args.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/bffd91947efc1932da99aca69fc540d6d4201b0c/src/amd/common/nir/ac_nir_lower_intrinsics_to_args.c#L342-L368)). A value passed through a select, move or phi whose every read is an extract inside one of those fields is rebuilt from the two fields alone. Any other read the masked-select pass cannot drop gets the whole word: the primitive class at bits 0-1 (0 point, 1 line, 2 triangle, 3 rectangle), which the draw sets as a pipeline specialization constant ([RasterPrimitiveOf](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp)) in the shaders that read it, plus the sample field, with every other bit zero. That layout comes from the RDNA 3 and 3.5 ISA guides (prim type 1:0, VRS rates 3:2 and 5:4, eye id 7, sample 11:8, render target index 28:16). The GCN register guides give the same prim type and sample fields and put the render target index at 26:16, so the two sources agree for layers below 2048. AMD's LLPC reads the primitive class from bits 1:0 with the same encoding ([LowerGlCompatibility.cpp](https://github.com/GPUOpen-Drivers/llpc/blob/40cb8d95ad8d6f7f1652e3fd47d39667594cce08/llpc/lowering/LowerGlCompatibility.cpp#L830-L836)) and without a GFX-level check ([getPrimType](https://github.com/GPUOpen-Drivers/llpc/blob/40cb8d95ad8d6f7f1652e3fd47d39667594cce08/lgc/lowering/LowerInOut.cpp#L5307-L5315)). Nobody has checked it on gfx1013. In the whole word only bits 0-1 and 8-11 have a source. Bits 2-7, 12-15 and 29-31 are assumed zero, not measured: the VRS rates (2-5) and the eye id (7) because the driver supports neither variable-rate shading nor multiview, so a title that enables either would read other bits on the console, and bits 6, 12-15 and 29-31 because no source gives them a meaning. The sample field reads SampleId, which is always 0 here because the driver rejects MSAA and per-sample shading (`PA_SC_AA_CONFIG`, `PS_ITER_SAMPLE`). The whole word has the same 0 at bits 16-28. A change that makes draws export a layer must restore the Layer read in both places - [Front-face input with `FRONT_FACE_ALL_BITS`](../../../core/shader/recompiler/Translation/src/InstructionTranslator.cpp) (shader recompiler) - with `SPI_BARYC_CNTL.FRONT_FACE_ALL_BITS` set, a back face reads 0 and a front face 0xffffffff. Mesa 22.0 RADV sets the bit ([radv_pipeline.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-22.0.0/src/amd/vulkan/radv_pipeline.c#L5953)) and ACO tests the input with `v_cmp_lg_u32 0` ([aco_instruction_selection.cpp](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-22.0.0/src/amd/compiler/aco_instruction_selection.cpp#L8123-L8128)), so only back = 0 and front ≠ 0 are sourced; PPSA05684's two-sided cloth shaders test it with `v_cmp_lt_u32 0`. The exact front value is not measured. Without the bit the input is ±1.0, as current Mesa reads it (`fgt 0`, `fsign`; [ac_nir_lower_intrinsics_to_args.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/bffd91947efc1932da99aca69fc540d6d4201b0c/src/amd/common/nir/ac_nir_lower_intrinsics_to_args.c)) @@ -55,7 +57,7 @@ - [2D stores to 1D storage images](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp): an `image_store` with DIM 2D through a 1D T# writes texel x and ignores y, as measured on RDNA2 (gfx1035) for y from 0 to 4 and 0xffffffff, single- and four-component formats and a 1x1 8_8_8_8_UNORM T#: the T# type decides the addressing, DIM only how many address VGPRs are read. Storage loads, atomics and `image_store_mip` through such a pair still throw - [MIMG PCK2/PCK4 loads](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) (`image_load[_mip]_pck2/pck4`, opcodes 0x70, 0x71, 0x73 and 0x74) were measured on RDNA2 (gfx1035) with the [hardware oracle](../HW_ORACLE.md) for 2D images with identity swizzle. With data mask 0x1, they write one dword: the raw bits of the N texels (PCK2: 2, PCK4: 4) from `x & ~(N - 1)` on row y, concatenated (element k at bit `k * texel bits`) and zero-extended. The dword is zero when the mip level is past the last level, `x + N - 1` is outside the level's width (unaligned x, unsigned), or y is outside its height. Data only comes back when N texels fit in 32 bits: R8, R16 or RG8 for PCK2, R8 for PCK4. Wider texels (R32, RG16, RGBA8) and masks other than 0x1 write zeros on hardware. Those masks throw at decode, and formats outside UNORM/UINT/SINT R8, R16 and RG8 throw. #1314's earlier measurement saw only zeros because it used masks 0x3 and 0xf. The stores (0x76, 0x77, 0x79 and 0x7a) decode but throw at translation. They are not measured, and opcode 0x76 on 32_32_32_32_FLOAT hung the GPU (#1314). - [3D image accesses bound to a 2D or 2D-array T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) follow CouchRiv's [hardware oracle](../HW_ORACLE.md) measurement on RDNA2 (gfx1036, #2023) and adriadam10's (gfx1035, #2140), not the PS5's gfx1013. On a 2D T#, `image_sample_lz` ignores r and reads the (u,v) texel, `image_load` and `image_load_mip` read the (x,y) texel when z is 0 and return zero otherwise, and `image_get_resinfo` returns (width, height, 1, levels). On a 2D-array T#, these accesses behave like dim 2D-array: r or z is the layer, and `image_get_resinfo` returns the layer count as its third component. Every other access of such an image, including samples with an LOD, bias, derivatives, offset or comparison, gathers, `image_get_lod` and stores, keeps throwing because it is not measured. -- [2D accesses through a cube T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) (#4139) read the T#'s `BASE_ARRAY` face as a cube access with face coordinate 0 does, measured on RDNA1 (gfx1010, RX 5700 XT) with the [hardware oracle](../HW_ORACLE.md) template run through the Windows driver's comgr and HIP runtime on 8x8 and 32x32 32_FLOAT cubes (`DEPTH` 5 and 11, `BASE_ARRAY` 0 and 6): `image_sample_lz`, plain `image_sample` (one level, and six levels in 2x2 compute quads) and `image_sample_l` (the third address VGPR is the LOD, integer and fractional) with DIM 2D equal DIM CUBE with face 0 on every row, with point and bilinear filters, wrap, clamp and mirror addressing and `DISABLE_CUBE_WRAP` 0 and 1, and the VGPR after the two coordinates is not read; `image_load` with DIM 2D reads texel (x, y) of the `BASE_ARRAY` face. So the face coordinates are cube coordinates in [1, 2]: the texel is `floor((u - 1) * width)`. They are translated through the cube mode DIM CUBE accesses use, which samples the face of the 2D-array view at (u - 1, v - 1) with the S# addressing. That matches the hardware inside the face, except within half a texel of its edges with a bilinear filter and `DISABLE_CUBE_WRAP` 0, where the hardware blends texels of the neighbouring faces (also measured on RDNA2, gfx1035, 8x8 cubes with `BASE_ARRAY` 0 and 6). Outside it the hardware clamps a coordinate below 1 to the first texel under every filter, wrap mode and `DISABLE_CUBE_WRAP` setting measured, clamps a coordinate of 2 or more to the last texel when `DISABLE_CUBE_WRAP` is 0 (point filter) and applies the S# addressing to it when it is 1; the translation does none of this, as for DIM CUBE. Only `image_sample_lz`, plain `image_sample`, `image_sample_l` and mip-less `image_load` take this mode; other DIM 2D accesses through a cube T# (gathers, bias, comparison, offsets, derivatives, storage, mip loads) still throw. +- [2D accesses through a cube T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) (#4139) read the T#'s `BASE_ARRAY` face as a cube access with face coordinate 0 does, measured on RDNA1 (gfx1010, RX 5700 XT) with the [hardware oracle](../HW_ORACLE.md) template run through the Windows driver's comgr and HIP runtime on 8x8 and 32x32 32_FLOAT cubes (`DEPTH` 5 and 11, `BASE_ARRAY` 0 and 6): `image_sample_lz`, plain `image_sample` (one level, and six levels in 2x2 compute quads) and `image_sample_l` (the third address VGPR is the LOD, integer and fractional) with DIM 2D equal DIM CUBE with face 0 on every row, with point and bilinear filters, wrap, clamp and mirror addressing and `DISABLE_CUBE_WRAP` 0 and 1, and the VGPR after the two coordinates is not read; `image_load` with DIM 2D reads texel (x, y) of the `BASE_ARRAY` face. So the face coordinates are cube coordinates in [1, 2]: the texel is `floor((u - 1) * width)`. They are translated through the cube mode DIM CUBE accesses use, which samples the face of the 2D-array view at (u - 1, v - 1) with the S# addressing. That matches the hardware inside the face, except within half a texel of its edges with a bilinear filter and `DISABLE_CUBE_WRAP` 0, where the hardware blends texels of the neighbouring faces (also measured on RDNA2, gfx1035, 8x8 cubes with `BASE_ARRAY` 0 and 6). Outside it the hardware clamps a coordinate below 1 to the first texel under every filter, wrap mode and `DISABLE_CUBE_WRAP` setting measured, clamps a coordinate of 2 or more to the last texel when `DISABLE_CUBE_WRAP` is 0 (point filter) and applies the S# addressing to it when it is 1; the translation does none of this, as for DIM CUBE. Only `image_sample_lz`, plain `image_sample`, `image_sample_l`, mip-less `image_load`, and mip-less `image_store` to an image the program only stores to take this mode. Such a store writes texel (x, y) of the `BASE_ARRAY` face, measured on RDNA2 (gfx1036, [by simonbbby](https://github.com/boykopovar/AnyPS5/pull/6799#issuecomment-6217609589), not the PS5's gfx1013) with the [hardware oracle](../HW_ORACLE.md) on 8x8 mip-less cube arrays (`DEPTH` 11) of 32_UINT, 32_FLOAT and 16_16_16_16_FLOAT ([the last also by simonbbby](https://github.com/boykopovar/AnyPS5/pull/6799#issuecomment-6224023050), with exactly representable half values) with `BASE_ARRAY` 0 and 6, wave32 and wave64: the 64 stored texels land on that face and every other byte of the 64 KiB backing is unchanged. Other formats, and Crimson Desert's own 256x256 tiled descriptor, are not measured. Other DIM 2D accesses through a cube T# (gathers, bias, comparison, offsets, derivatives, atomics, stores to an image the program also reads, stores of formats written through a conversion such as 32_SINT, mip loads and stores) still throw. - [Unnormalized S#s](../../../core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp) are accepted only when direct and used only by explicit-LOD `image_sample` without comparison, derivatives or `_a`, on a single-level, single-layer 1D or 2D view of mip 0, even when the image is also sampled through a normalized S#. - [Unnormalized texel offsets](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) are added to the f32 coordinates: exact on RDNA2 (gfx1035), which applies the offset before the clamp ([measured by adriadam10](https://github.com/boykopovar/AnyPS5/pull/822#issuecomment-6033192725)). - [Cube image gradients](../../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) (shader recompiler) - `image_sample_d` and `image_sample_cd` on a cube pass their gradients to the host sample without the -1.0 that moves the face coordinates from [1, 2] to [0, 1], as Mesa's AMD lowering builds them (`src/amd/common/nir/ac_nir_lower_tex_coords.c`, `prepare_cube_coords`: face-space derivatives with no offset, coordinates offset by 1.5). Measured on RDNA2 (gfx1035) with the [hardware oracle](../HW_ORACLE.md): on a 16x16 32_FLOAT cube with five levels, point mip filter, `image_sample_d` reads the level of `round(log2(16 * max(|ddx|, |ddy|)))` for gradients of 2^(level ± 0.3) texels on each of the four components, both signs, mixed components, every face and two coordinates (1032 rows). `image_sample_cd` takes one level per quad of lanes, from the gradients of its first lane; the host sample computes it per lane.