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.
This commit is contained in:
OverkillLabs 2026-09-28 13:18:07 +03:00
parent cdc95d9a70
commit 940f28d102
4 changed files with 277 additions and 4 deletions

View File

@ -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

View File

@ -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() {

View File

@ -0,0 +1,51 @@
#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_PRECISEWAIT_HPP
#define CORE_LIBS_PRX_LIBC_INCLUDE_PRECISEWAIT_HPP
#include <chrono>
#include <thread>
#ifdef _WIN32
#include <windows.h>
#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<LONGLONG>(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

View File

@ -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 <cerrno>
#include <cstdint>
#include <cstddef>
#include <cstdio>
#include <cstdarg>
#include <cstring>
#include <stdexcept>
#include <string>
#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<char>(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<unsigned char>(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<unsigned char>(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<char16_t>(0xd800 + (code >> 10)));
out.push_back(static_cast<char16_t>(0xdc00 + (code & 0x3ff)));
} else out.push_back(static_cast<char16_t>(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<char>(code));
else if (code < 0x800) {
out.push_back(static_cast<char>(0xc0 | (code >> 6)));
out.push_back(static_cast<char>(0x80 | (code & 0x3f)));
} else if (code < 0x10000) {
out.push_back(static_cast<char>(0xe0 | (code >> 12)));
out.push_back(static_cast<char>(0x80 | ((code >> 6) & 0x3f)));
out.push_back(static_cast<char>(0x80 | (code & 0x3f)));
} else {
out.push_back(static_cast<char>(0xf0 | (code >> 18)));
out.push_back(static_cast<char>(0x80 | ((code >> 12) & 0x3f)));
out.push_back(static_cast<char>(0x80 | ((code >> 6) & 0x3f)));
out.push_back(static_cast<char>(0x80 | (code & 0x3f)));
}
}
return out;
}
template<class T> 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<std::size_t>(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<std::size_t>(width) > text.size() ? static_cast<std::size_t>(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<char>(*format++);
}
if (*format == u'*') {
++format;
width = args.Next<int>();
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<char>(*format++); }
}
if (*format == u'.') {
++format;
precision = 0;
if (*format == u'*') {
++format;
precision = args.Next<int>();
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<char>(*format++); }
}
}
std::string length;
if (In(*format, "hljztL")) {
length += static_cast<char>(*format++);
if ((length == "h" && *format == u'h') || (length == "l" && *format == u'l')) length += static_cast<char>(*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<int>();
if (length == "h") value = static_cast<short>(value);
if (length == "hh") value = static_cast<signed char>(value);
} else value = args.Next<long long>();
AppendNumber(out, spec + "ll" + static_cast<char>(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<unsigned int>();
if (length == "h") value = static_cast<unsigned short>(value);
if (length == "hh") value = static_cast<unsigned char>(value);
} else value = args.Next<unsigned long long>();
AppendNumber(out, spec + "ll" + static_cast<char>(conversion), value);
} else if (In(conversion, "aAeEfFgG")) {
if (length == "L") AppendNumber(out, spec + "L" + static_cast<char>(conversion), args.Next<long double>());
else {
if (!length.empty() && length != "l") throw std::invalid_argument("Invalid floating length");
AppendNumber(out, spec + static_cast<char>(conversion), args.Next<double>());
}
} else if (conversion == u'p' && length.empty()) {
AppendNumber(out, spec + 'p', args.Next<void*>());
} else if (conversion == u'c' || conversion == u'C') {
const int value = args.Next<int>();
std::u16string text;
if (conversion == u'C' || length == "l") text.push_back(static_cast<char16_t>(value));
else text.push_back(static_cast<char16_t>(static_cast<unsigned char>(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<std::size_t>(precision);
if (conversion == u'S' || length == "l") {
const char16_t* value = args.Next<const char16_t*>();
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<const char*>();
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<int>(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<VaList*>(args));
const std::string bytes = ToUtf8(text);
if (std::fwrite(bytes.data(), 1, bytes.size(), stdout) == bytes.size()) result = static_cast<int>(text.size());
} catch (const std::exception&) {
errno = 22;
}
APS5_VA_END();
return result;
}
} // extern "C"