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:
parent
cdc95d9a70
commit
940f28d102
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
Loading…
Reference in New Issue