From 940f28d1024bf31b0a01b4cfb660eb102bf5c218 Mon Sep 17 00:00:00 2001 From: OverkillLabs <10800190+OverkillLabs@users.noreply.github.com> Date: Mon, 28 Sep 2026 13:18:07 +0300 Subject: [PATCH] libc: wide-char formatting and precise sub-tick waits - Add FormattingWide.cpp implementing vswprintf_nid_postfix and wprintf_nid_postfix, the only wide-char printf-family functions upstream had left as unimplemented stubs (Formatting.cpp's checked and unchecked narrow printf/scanf families are unaffected). Removes the dead vswprintf_nid_postfix stub from Export.cpp. - Add PreciseWait.hpp: std::condition_variable::wait_for cannot return in under about one system tick on this MinGW toolchain regardless of the requested budget, even with NtSetTimerResolution; PreciseSleepUs uses a high-resolution waitable timer instead (~0.5ms vs ~15.6ms). Header-only for now: its guest-side callers (Equeue/EventFlag/ Semaphore/AgcDriver wait loops) are out of scope for this change, so it is not wired into them here. --- core/libs/prx/libc/CMakeLists.txt | 1 + core/libs/prx/libc/Export.cpp | 5 +- core/libs/prx/libc/include/PreciseWait.hpp | 51 +++++ core/libs/prx/libc/src/FormattingWide.cpp | 224 +++++++++++++++++++++ 4 files changed, 277 insertions(+), 4 deletions(-) create mode 100644 core/libs/prx/libc/include/PreciseWait.hpp create mode 100644 core/libs/prx/libc/src/FormattingWide.cpp diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index ba4ae9c93..e6301e575 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -20,6 +20,7 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/ApplicationProcessParameters.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/JumpBuffer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Formatting.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/FormattingWide.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RuntimeSupport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/LocaleSupport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Process.cpp diff --git a/core/libs/prx/libc/Export.cpp b/core/libs/prx/libc/Export.cpp index 625b27ff9..84b1b05f4 100644 --- a/core/libs/prx/libc/Export.cpp +++ b/core/libs/prx/libc/Export.cpp @@ -81,10 +81,7 @@ int APS5_VABI fscanf_nid_postfix() { return 0; } -int APS5_VABI vswprintf_nid_postfix() { - NotImplemented_nid_no_patch(__func__); - return 0; -} +// vswprintf_nid_postfix is implemented in src/FormattingWide.cpp. APS5_EXPORT("Pu0Ecyk-7FU", libcUnknown_Pu0Ecyk_M7FU); int APS5_VABI libcUnknown_Pu0Ecyk_M7FU() { diff --git a/core/libs/prx/libc/include/PreciseWait.hpp b/core/libs/prx/libc/include/PreciseWait.hpp new file mode 100644 index 000000000..fe1132248 --- /dev/null +++ b/core/libs/prx/libc/include/PreciseWait.hpp @@ -0,0 +1,51 @@ +#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_PRECISEWAIT_HPP +#define CORE_LIBS_PRX_LIBC_INCLUDE_PRECISEWAIT_HPP + +#include +#include + +#ifdef _WIN32 +#include +#endif + +// std::condition_variable::wait_for on this MinGW toolchain cannot return in under about one system tick, +// whatever the budget says: measured 15.6 ms average for a 0 us wait and for a 1 us wait alike, and raising +// the process timer resolution to 0.5 ms with NtSetTimerResolution does not change it. A high-resolution +// waitable timer honours the same request in about 0.5 ms instead. +// +// This matters for any guest-side poll that requests a sub-tick wait budget: without this helper, such a +// poll runs at the OS tick rate rather than at the requested budget, which can turn a tight wait loop into +// a multi-millisecond stall per iteration. +// +// Callers must hold no lock that the awaited event's producer needs: the point of the helper is to wait +// precisely and cheaply, so the caller re-tests its own condition afterwards. +inline constexpr unsigned long long kSubTickWaitUs = 2000; + +inline void PreciseSleepUs(unsigned long long micros) { + if (micros == 0) { + return; + } +#ifdef _WIN32 + struct Timer { + HANDLE handle = CreateWaitableTimerExW(nullptr, nullptr, + CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); + ~Timer() { + if (handle != nullptr) { + CloseHandle(handle); + } + } + }; + thread_local Timer timer; + if (timer.handle != nullptr) { + LARGE_INTEGER due{}; + due.QuadPart = -static_cast(micros * 10ULL); // relative, 100 ns units + if (SetWaitableTimerEx(timer.handle, &due, 0, nullptr, nullptr, nullptr, 0) != FALSE) { + WaitForSingleObject(timer.handle, INFINITE); + return; + } + } +#endif + std::this_thread::sleep_for(std::chrono::microseconds(micros)); +} + +#endif diff --git a/core/libs/prx/libc/src/FormattingWide.cpp b/core/libs/prx/libc/src/FormattingWide.cpp new file mode 100644 index 000000000..5c44a994e --- /dev/null +++ b/core/libs/prx/libc/src/FormattingWide.cpp @@ -0,0 +1,224 @@ +// Wide-character (UTF-16 guest wchar_t) printf-family support. Upstream's Formatting.cpp implements the +// narrow checked/unchecked printf and scanf families but has no real wide-char formatter; vswprintf and +// wprintf are otherwise left as unimplemented stubs. +#include +#include +#include +#include +#include +#include +#include +#include + +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libc/include/VarArgsAbi.hpp" +#include "prx/libc/include/WindowsFormatting.hpp" + +#ifdef _WIN32 +#define APS5_VA_BEGIN(last) __builtin_sysv_va_list args; __builtin_sysv_va_start(args, last) +#define APS5_VA_END() __builtin_sysv_va_end(args) +#else +#define APS5_VA_BEGIN(last) std::va_list args; va_start(args, last) +#define APS5_VA_END() va_end(args) +#endif + +namespace { + +bool In(char16_t character, const char* set) { + return character != 0 && character < 128 && std::strchr(set, static_cast(character)) != nullptr; +} + +void AppendUtf16(std::u16string& out, const char* text, std::size_t limit) { + for (std::size_t index = 0; index < limit && text[index] != '\0';) { + const unsigned char lead = static_cast(text[index]); + std::size_t extra = lead >= 0xf0 ? 3 : lead >= 0xe0 ? 2 : lead >= 0xc0 ? 1 : 0; + std::uint32_t code = extra == 3 ? lead & 0x07 : extra == 2 ? lead & 0x0f : extra == 1 ? lead & 0x1f : lead; + std::size_t used = 1; + for (; used <= extra; ++used) { + const unsigned char next = static_cast(text[index + used]); + if ((next & 0xc0) != 0x80) { extra = 0; code = lead; used = 1; break; } + code = (code << 6) | (next & 0x3f); + } + if (extra != 0) used = extra + 1; + index += used; + if (code >= 0x10000) { + code -= 0x10000; + out.push_back(static_cast(0xd800 + (code >> 10))); + out.push_back(static_cast(0xdc00 + (code & 0x3ff))); + } else out.push_back(static_cast(code)); + } +} + +std::string ToUtf8(const std::u16string& text) { + std::string out; + for (std::size_t index = 0; index < text.size(); ++index) { + std::uint32_t code = text[index]; + if (code >= 0xd800 && code < 0xdc00 && index + 1 < text.size() && text[index + 1] >= 0xdc00 && text[index + 1] < 0xe000) + code = 0x10000 + ((code - 0xd800) << 10) + (text[++index] - 0xdc00); + if (code < 0x80) out.push_back(static_cast(code)); + else if (code < 0x800) { + out.push_back(static_cast(0xc0 | (code >> 6))); + out.push_back(static_cast(0x80 | (code & 0x3f))); + } else if (code < 0x10000) { + out.push_back(static_cast(0xe0 | (code >> 12))); + out.push_back(static_cast(0x80 | ((code >> 6) & 0x3f))); + out.push_back(static_cast(0x80 | (code & 0x3f))); + } else { + out.push_back(static_cast(0xf0 | (code >> 18))); + out.push_back(static_cast(0x80 | ((code >> 12) & 0x3f))); + out.push_back(static_cast(0x80 | ((code >> 6) & 0x3f))); + out.push_back(static_cast(0x80 | (code & 0x3f))); + } + } + return out; +} + +template void AppendNumber(std::u16string& out, const std::string& spec, T value) { + const int size = std::snprintf(nullptr, 0, spec.c_str(), value); + if (size < 0) throw std::runtime_error("Formatting conversion failed"); + std::string text(static_cast(size) + 1, '\0'); + std::snprintf(&text[0], text.size(), spec.c_str(), value); + out.append(text.begin(), text.begin() + size); +} + +void AppendPadded(std::u16string& out, const std::u16string& text, bool left, int width) { + const std::size_t pad = width > 0 && static_cast(width) > text.size() ? static_cast(width) - text.size() : 0; + if (!left) out.append(pad, u' '); + out += text; + if (left) out.append(pad, u' '); +} + +// Wide printf: %s is a narrow string and %ls a wide one; numeric conversions reuse the host formatter. +std::u16string FormatWide(const char16_t* format, VaList* source) { + if (format == nullptr || source == nullptr) throw std::invalid_argument("Null formatting argument"); + LibcDetail::FormatArguments args(source); + std::u16string out; + while (*format) { + if (*format != u'%') { out.push_back(*format++); continue; } + ++format; + if (*format == u'%') { out.push_back(*format++); continue; } + std::string spec = "%"; + bool left = false; + int width = 0; + int precision = -1; + while (In(*format, "-+ #0")) { + if (*format == u'-') left = true; + spec += static_cast(*format++); + } + if (*format == u'*') { + ++format; + width = args.Next(); + if (width < 0) { left = true; spec += '-'; width = -width; } + spec += std::to_string(width); + } else { + while (*format >= u'0' && *format <= u'9') { width = width * 10 + (*format - u'0'); spec += static_cast(*format++); } + } + if (*format == u'.') { + ++format; + precision = 0; + if (*format == u'*') { + ++format; + precision = args.Next(); + if (precision >= 0) spec += "." + std::to_string(precision); + } else { + spec += '.'; + while (*format >= u'0' && *format <= u'9') { precision = precision * 10 + (*format - u'0'); spec += static_cast(*format++); } + } + } + std::string length; + if (In(*format, "hljztL")) { + length += static_cast(*format++); + if ((length == "h" && *format == u'h') || (length == "l" && *format == u'l')) length += static_cast(*format++); + } + const char16_t conversion = *format; + if (!conversion) throw std::invalid_argument("Incomplete format conversion"); + ++format; + const bool integerLength = length.empty() || length == "h" || length == "hh" || length == "l" || length == "ll" || + length == "j" || length == "z" || length == "t"; + if (conversion == u'd' || conversion == u'i') { + if (!integerLength) throw std::invalid_argument("Invalid integer length"); + long long value; + if (length.empty() || length == "h" || length == "hh") { + value = args.Next(); + if (length == "h") value = static_cast(value); + if (length == "hh") value = static_cast(value); + } else value = args.Next(); + AppendNumber(out, spec + "ll" + static_cast(conversion), value); + } else if (In(conversion, "ouxX")) { + if (!integerLength) throw std::invalid_argument("Invalid integer length"); + unsigned long long value; + if (length.empty() || length == "h" || length == "hh") { + value = args.Next(); + if (length == "h") value = static_cast(value); + if (length == "hh") value = static_cast(value); + } else value = args.Next(); + AppendNumber(out, spec + "ll" + static_cast(conversion), value); + } else if (In(conversion, "aAeEfFgG")) { + if (length == "L") AppendNumber(out, spec + "L" + static_cast(conversion), args.Next()); + else { + if (!length.empty() && length != "l") throw std::invalid_argument("Invalid floating length"); + AppendNumber(out, spec + static_cast(conversion), args.Next()); + } + } else if (conversion == u'p' && length.empty()) { + AppendNumber(out, spec + 'p', args.Next()); + } else if (conversion == u'c' || conversion == u'C') { + const int value = args.Next(); + std::u16string text; + if (conversion == u'C' || length == "l") text.push_back(static_cast(value)); + else text.push_back(static_cast(static_cast(value))); + AppendPadded(out, text, left, width); + } else if (conversion == u's' || conversion == u'S') { + std::u16string text; + const std::size_t limit = precision < 0 ? SIZE_MAX : static_cast(precision); + if (conversion == u'S' || length == "l") { + const char16_t* value = args.Next(); + if (value == nullptr) value = u"(null)"; + for (std::size_t index = 0; index < limit && value[index] != 0; ++index) text.push_back(value[index]); + } else { + const char* value = args.Next(); + AppendUtf16(text, value != nullptr ? value : "(null)", limit); + } + AppendPadded(out, text, left, width); + } else { + throw std::invalid_argument("Unsupported format conversion"); + } + } + return out; +} + +} // namespace + +extern "C" { + +// Returns the character count, or -1 when the buffer is too small (the output is still terminated). +int APS5_VABI vswprintf_nid_postfix(char16_t* buffer, std::size_t size, const char16_t* format, VaList* args) { + if (buffer == nullptr || size == 0) { errno = 22; return -1; } + try { + const std::u16string text = FormatWide(format, args); + const std::size_t copied = text.size() < size - 1 ? text.size() : size - 1; + std::memcpy(buffer, text.data(), copied * sizeof(char16_t)); + buffer[copied] = 0; + return copied == text.size() ? static_cast(copied) : -1; + } catch (const std::exception&) { + buffer[0] = 0; + errno = 22; + return -1; + } +} + +int APS5_VABI wprintf_nid_postfix(const char16_t* format, ...) { + APS5_VA_BEGIN(format); + int result = -1; + try { + const std::u16string text = FormatWide(format, reinterpret_cast(args)); + const std::string bytes = ToUtf8(text); + if (std::fwrite(bytes.data(), 1, bytes.size(), stdout) == bytes.size()) result = static_cast(text.size()); + } catch (const std::exception&) { + errno = 22; + } + APS5_VA_END(); + return result; +} + +} // extern "C"