feat(runtime): timed waits, sync primitive fixes, heap and RTC fixes, unattended-run switches

libkernel: a TimedWait service for condition variables, semaphores, event flags, rwlocks and
equeues with the scheduler tick; mutex/cond/sem corner cases the title hits; job worker
affinity is opt-in (APS5_JOB_AFFINITY). libc: calloc/realloc heap slot swap, a default
terminate handler with a backtrace, CPU topology helper. libSceRtc export fixes with tests.
libSceVideoOut: APS5_NO_PAD_INPUT ignores host input for unattended measurements,
APS5_FLIP_QUEUE_WAIT keeps a stalled process alive for a debugger. ApplicationHeap and
GuestRtc tests wired into the test build.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0177gthrWEBBfBu5xJDXgBfo
This commit is contained in:
devilismyfriend 2026-09-26 19:00:39 -07:00
parent d677f970b2
commit af3c2024ed
26 changed files with 1223 additions and 281 deletions

View File

@ -106,6 +106,8 @@ add_custom_target(libs DEPENDS ${PATCHED_OUTPUTS})
add_executable(application_heap_tests EXCLUDE_FROM_ALL tests/ApplicationHeap.cpp prx/libc/src/ApplicationHeap.cpp)
target_include_directories(application_heap_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
set_target_properties(application_heap_tests PROPERTIES CXX_EXTENSIONS OFF)
target_link_libraries(application_heap_tests PRIVATE libc)
configure_windows_unwind(application_heap_tests)
add_executable(guest_locale_tests EXCLUDE_FROM_ALL tests/GuestLocale.cpp)
target_include_directories(guest_locale_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
@ -113,6 +115,11 @@ set_target_properties(guest_locale_tests PROPERTIES CXX_EXTENSIONS OFF)
target_link_libraries(guest_locale_tests PRIVATE libc)
configure_windows_unwind(guest_locale_tests)
add_executable(guest_rtc_tests EXCLUDE_FROM_ALL tests/GuestRtc.cpp)
target_include_directories(guest_rtc_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
set_target_properties(guest_rtc_tests PROPERTIES CXX_EXTENSIONS OFF)
target_link_libraries(guest_rtc_tests PRIVATE libSceRtc)
if(MINGW)
foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static)
string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}")

View File

@ -1,59 +1,97 @@
#include <atomic>
#include <cstdint>
#include <cstddef>
#include <cstdio>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
namespace {
constexpr int StatusNone = 0;
constexpr int StatusRunning = 1;
constexpr int StatusFinished = 2;
constexpr std::uint32_t EndStatusOk = 0;
constexpr std::uint32_t EndStatusAborted = 2;
constexpr int ErrorNotOpened = static_cast<int>(0x80bc0002u);
std::atomic<int> status{StatusNone};
std::atomic<int> polls{0};
std::atomic<std::uint32_t> endStatus{EndStatusOk};
}
extern "C" {
int APS5_VABI sceImeDialogAbort(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
if (status.load() != StatusRunning) return ErrorNotOpened;
endStatus = EndStatusAborted;
status = StatusFinished;
return 0;
}
int APS5_VABI sceImeDialogGetPanelPositionAndForm(PositionAndForm* form) {
(void)form;
NotImplemented_nid_no_patch(__func__);
return 0;
if (form == nullptr) return ErrorNotOpened;
*form = PositionAndForm{};
form->width = 1920;
form->height = 1080;
return 0;
}
int APS5_VABI sceImeDialogGetPanelSize(const Param* param, uint32_t* width, uint32_t* height) {
(void)param;
(void)width;
(void)height;
NotImplemented_nid_no_patch(__func__);
return 0;
(void)param;
if (width != nullptr) *width = 1920;
if (height != nullptr) *height = 1080;
return 0;
}
int APS5_VABI sceImeDialogGetPanelSizeExtended(const Param* param, const ExtendedParam* extended, uint32_t* width, uint32_t* height) {
(void)param;
(void)extended;
(void)width;
(void)height;
NotImplemented_nid_no_patch(__func__);
return 0;
(void)extended;
return sceImeDialogGetPanelSize(param, width, height);
}
int APS5_VABI sceImeDialogGetResult(Result* result) {
(void)result;
NotImplemented_nid_no_patch(__func__);
return 0;
std::fprintf(stderr, "[ime] GetResult (status %d)\n", status.load());
if (result == nullptr || status.load() != StatusFinished) return ErrorNotOpened;
*result = Result{};
result->endstatus = endStatus.load();
return 0;
}
int APS5_VABI sceImeDialogGetStatus(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
if (status.load() == StatusRunning && ++polls > 1) status = StatusFinished;
static std::atomic<int> traced{0};
const int count = traced.fetch_add(1);
if (count < 8 || count % 2000 == 0) std::fprintf(stderr, "[ime] GetStatus #%d -> %d\n", count, status.load());
return status.load();
}
int APS5_VABI sceImeDialogInit(const Param* param, const ExtendedParam* extended) {
(void)param;
(void)extended;
NotImplemented_nid_no_patch(__func__);
return 0;
(void)extended;
static std::atomic<int> reported{0};
bool filled = false;
if (param != nullptr && param->input_text_buffer != nullptr && param->max_text_length != 0 && param->input_text_buffer[0] == u'\0') {
static constexpr char16_t defaultText[] = u"Slayer";
std::uint32_t length = 0;
while (defaultText[length] != u'\0' && length < param->max_text_length) {
param->input_text_buffer[length] = defaultText[length];
++length;
}
param->input_text_buffer[length] = u'\0';
filled = true;
}
std::fprintf(stderr, "[ime] Init #%d\n", reported.load());
if (reported.fetch_add(1) == 0) std::fprintf(stderr, "[ime] dialog requested (max text length %u): finishing at once with %s\n", param != nullptr ? param->max_text_length : 0u, filled ? "a default text" : "the text unchanged");
polls = 0;
endStatus = EndStatusOk;
status = StatusRunning;
return 0;
}
int APS5_VABI sceImeDialogTerm(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
std::fprintf(stderr, "[ime] Term (status %d)\n", status.load());
if (status.load() == StatusNone) return ErrorNotOpened;
status = StatusNone;
return 0;
}
}

View File

@ -1,185 +1,339 @@
#include <chrono>
#include <cstdint>
#include <cstddef>
#include <cstdio>
#include <ctime>
#include <limits>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
namespace {
constexpr int SCE_RTC_ERROR_INVALID_POINTER = static_cast<int>(0x80B50002);
constexpr int SCE_RTC_ERROR_INVALID_VALUE = static_cast<int>(0x80B50003);
constexpr int SCE_RTC_ERROR_BAD_PARSE = static_cast<int>(0x80B50007);
constexpr int SCE_RTC_ERROR_INVALID_YEAR = static_cast<int>(0x80B50008);
constexpr int SCE_RTC_ERROR_INVALID_MONTH = static_cast<int>(0x80B50009);
constexpr int SCE_RTC_ERROR_INVALID_DAY = static_cast<int>(0x80B5000A);
constexpr int SCE_RTC_ERROR_INVALID_HOUR = static_cast<int>(0x80B5000B);
constexpr int SCE_RTC_ERROR_INVALID_MINUTE = static_cast<int>(0x80B5000C);
constexpr int SCE_RTC_ERROR_INVALID_SECOND = static_cast<int>(0x80B5000D);
constexpr int SCE_RTC_ERROR_INVALID_MICROSECOND = static_cast<int>(0x80B5000E);
constexpr std::int64_t TICKS_PER_SECOND = 1000000;
constexpr std::int64_t TICKS_PER_MINUTE = 60 * TICKS_PER_SECOND;
constexpr std::int64_t TICKS_PER_HOUR = 60 * TICKS_PER_MINUTE;
constexpr std::int64_t TICKS_PER_DAY = 24 * TICKS_PER_HOUR;
constexpr std::int64_t UNIX_EPOCH_DAYS = 719162;
constexpr std::int64_t WIN32_EPOCH_DAYS = 584388;
constexpr std::uint64_t UNIX_EPOCH_TICK = UNIX_EPOCH_DAYS * TICKS_PER_DAY;
constexpr std::uint64_t WIN32_EPOCH_TICK = WIN32_EPOCH_DAYS * TICKS_PER_DAY;
constexpr std::uint64_t MAX_TICK = 3652059ull * TICKS_PER_DAY - 1;
bool isLeapYear(std::int64_t year) {
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
}
int daysInMonth(std::int64_t year, int month) {
static constexpr int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
return month == 2 && isLeapYear(year) ? 29 : days[month - 1];
}
std::int64_t daysFromCivil(std::int64_t year, unsigned month, unsigned day) {
year -= month <= 2;
const std::int64_t era = (year >= 0 ? year : year - 399) / 400;
const auto yearOfEra = static_cast<unsigned>(year - era * 400);
const unsigned dayOfYear = (153 * (month > 2 ? month - 3 : month + 9) + 2) / 5 + day - 1;
const unsigned dayOfEra = yearOfEra * 365 + yearOfEra / 4 - yearOfEra / 100 + dayOfYear;
return era * 146097 + static_cast<std::int64_t>(dayOfEra) - 719468 + UNIX_EPOCH_DAYS;
}
void civilFromDays(std::int64_t days, std::int64_t& year, unsigned& month, unsigned& day) {
days -= UNIX_EPOCH_DAYS - 719468;
const std::int64_t era = (days >= 0 ? days : days - 146096) / 146097;
const auto dayOfEra = static_cast<unsigned>(days - era * 146097);
const unsigned yearOfEra = (dayOfEra - dayOfEra / 1460 + dayOfEra / 36524 - dayOfEra / 146096) / 365;
const unsigned dayOfYear = dayOfEra - (365 * yearOfEra + yearOfEra / 4 - yearOfEra / 100);
const unsigned shiftedMonth = (5 * dayOfYear + 2) / 153;
day = dayOfYear - (153 * shiftedMonth + 2) / 5 + 1;
month = shiftedMonth < 10 ? shiftedMonth + 3 : shiftedMonth - 9;
year = static_cast<std::int64_t>(yearOfEra) + era * 400 + (month <= 2);
}
int validate(const RtcDateTime* time) {
if (!time) return SCE_RTC_ERROR_INVALID_POINTER;
if (time->year < 1 || time->year > 9999) return SCE_RTC_ERROR_INVALID_YEAR;
if (time->month < 1 || time->month > 12) return SCE_RTC_ERROR_INVALID_MONTH;
if (time->day < 1 || time->day > daysInMonth(time->year, time->month)) return SCE_RTC_ERROR_INVALID_DAY;
if (time->hour > 23) return SCE_RTC_ERROR_INVALID_HOUR;
if (time->minute > 59) return SCE_RTC_ERROR_INVALID_MINUTE;
if (time->second > 59) return SCE_RTC_ERROR_INVALID_SECOND;
if (time->microsecond >= TICKS_PER_SECOND) return SCE_RTC_ERROR_INVALID_MICROSECOND;
return 0;
}
std::uint64_t toTick(const RtcDateTime& time) {
return static_cast<std::uint64_t>(daysFromCivil(time.year, time.month, time.day)) * TICKS_PER_DAY
+ time.hour * TICKS_PER_HOUR + time.minute * TICKS_PER_MINUTE + time.second * TICKS_PER_SECOND + time.microsecond;
}
RtcDateTime fromTick(std::uint64_t tick) {
std::int64_t year = 0;
unsigned month = 0;
unsigned day = 0;
civilFromDays(static_cast<std::int64_t>(tick / TICKS_PER_DAY), year, month, day);
const std::uint64_t timeOfDay = tick % TICKS_PER_DAY;
return RtcDateTime{
static_cast<std::uint16_t>(year), static_cast<std::uint16_t>(month), static_cast<std::uint16_t>(day),
static_cast<std::uint16_t>(timeOfDay / TICKS_PER_HOUR), static_cast<std::uint16_t>(timeOfDay % TICKS_PER_HOUR / TICKS_PER_MINUTE),
static_cast<std::uint16_t>(timeOfDay % TICKS_PER_MINUTE / TICKS_PER_SECOND), static_cast<std::uint32_t>(timeOfDay % TICKS_PER_SECOND)};
}
std::uint64_t currentTick() {
const auto now = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
return UNIX_EPOCH_TICK + static_cast<std::uint64_t>(now);
}
std::int64_t localOffsetSeconds(std::uint64_t utcTick) {
const auto seconds = static_cast<std::time_t>((static_cast<std::int64_t>(utcTick) - static_cast<std::int64_t>(UNIX_EPOCH_TICK)) / TICKS_PER_SECOND);
std::tm local{};
#ifdef _WIN32
if (localtime_s(&local, &seconds) != 0) return 0;
#else
if (!localtime_r(&seconds, &local)) return 0;
#endif
const std::int64_t localSeconds = (daysFromCivil(local.tm_year + 1900, static_cast<unsigned>(local.tm_mon + 1), static_cast<unsigned>(local.tm_mday)) - UNIX_EPOCH_DAYS) * 86400
+ local.tm_hour * 3600 + local.tm_min * 60 + local.tm_sec;
return localSeconds - static_cast<std::int64_t>(seconds);
}
int addTicks(RtcTick* dst, const RtcTick* src, std::int64_t count, std::int64_t unit) {
if (!dst || !src) return SCE_RTC_ERROR_INVALID_POINTER;
if (count != 0 && (count > std::numeric_limits<std::int64_t>::max() / unit || count < std::numeric_limits<std::int64_t>::min() / unit)) return SCE_RTC_ERROR_INVALID_VALUE;
const std::int64_t delta = count * unit;
if (delta < 0 ? src->tick < static_cast<std::uint64_t>(-(delta + 1)) + 1 : MAX_TICK - src->tick < static_cast<std::uint64_t>(delta)) return SCE_RTC_ERROR_INVALID_VALUE;
dst->tick = src->tick + static_cast<std::uint64_t>(delta);
return 0;
}
int addMonths(RtcTick* dst, const RtcTick* src, std::int64_t months) {
if (!dst || !src) return SCE_RTC_ERROR_INVALID_POINTER;
RtcDateTime time = fromTick(src->tick);
const std::int64_t monthIndex = time.year * 12 + (time.month - 1) + months;
const std::int64_t year = monthIndex / 12;
if (monthIndex < 12 || year > 9999) return SCE_RTC_ERROR_INVALID_VALUE;
time.year = static_cast<std::uint16_t>(year);
time.month = static_cast<std::uint16_t>(monthIndex % 12 + 1);
const int lastDay = daysInMonth(time.year, time.month);
if (time.day > lastDay) time.day = static_cast<std::uint16_t>(lastDay);
dst->tick = toTick(time);
return 0;
}
bool parseDigits(const char*& cursor, int count, int& value) {
value = 0;
for (int index = 0; index < count; ++index, ++cursor) {
if (*cursor < '0' || *cursor > '9') return false;
value = value * 10 + (*cursor - '0');
}
return true;
}
}
extern "C" {
int APS5_VABI sceRtcCheckValid(const RtcDateTime* time) {
(void)time;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRtcConvertLocalTimeToUtc(const RtcTick* local_time, RtcTick* utc) {
(void)local_time;
(void)utc;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRtcConvertUtcToLocalTime(const RtcTick* utc, RtcTick* local_time) {
(void)utc;
(void)local_time;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRtcFormatRFC3339(char* date_time, const RtcTick* utc, int time_zone_minutes) {
(void)date_time;
(void)utc;
(void)time_zone_minutes;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRtcGetCurrentClock(RtcDateTime* time, int time_zone_minutes) {
(void)time;
(void)time_zone_minutes;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRtcGetCurrentClockLocalTime(RtcDateTime* time) {
(void)time;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRtcGetCurrentNetworkTick(RtcTick* tick) {
(void)tick;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRtcGetCurrentTick(RtcTick* tick) {
(void)tick;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRtcGetDayOfWeek(int year, int month, int day) {
(void)year;
(void)month;
(void)day;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRtcGetTick(const RtcDateTime* time, RtcTick* tick) {
(void)time;
(void)tick;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRtcGetTickResolution(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRtcGetTime_t(const RtcDateTime* time, int64_t* seconds) {
(void)time;
(void)seconds;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRtcGetWin32FileTime(const RtcDateTime* time, uint64_t* win32_time) {
(void)time;
(void)win32_time;
NotImplemented_nid_no_patch(__func__);
return 0;
return validate(time);
}
int APS5_VABI sceRtcIsLeapYear(int year) {
(void)year;
NotImplemented_nid_no_patch(__func__);
return 0;
if (year < 1) return SCE_RTC_ERROR_INVALID_YEAR;
return isLeapYear(year) ? 1 : 0;
}
int APS5_VABI sceRtcParseRFC3339(RtcTick* utc, const char* date_time) {
(void)utc;
(void)date_time;
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceRtcGetDaysInMonth(int year, int month) {
if (year < 1) return SCE_RTC_ERROR_INVALID_YEAR;
if (month < 1 || month > 12) return SCE_RTC_ERROR_INVALID_MONTH;
return daysInMonth(year, month);
}
int APS5_VABI sceRtcGetDayOfWeek(int year, int month, int day) {
if (year < 1 || year > 9999) return SCE_RTC_ERROR_INVALID_YEAR;
if (month < 1 || month > 12) return SCE_RTC_ERROR_INVALID_MONTH;
if (day < 1 || day > daysInMonth(year, month)) return SCE_RTC_ERROR_INVALID_DAY;
return static_cast<int>((daysFromCivil(year, static_cast<unsigned>(month), static_cast<unsigned>(day)) + 1) % 7);
}
int APS5_VABI sceRtcGetTickResolution(void) {
return static_cast<int>(TICKS_PER_SECOND);
}
int APS5_VABI sceRtcGetTick(const RtcDateTime* time, RtcTick* tick) {
if (!tick) return SCE_RTC_ERROR_INVALID_POINTER;
if (const int result = validate(time); result != 0) return result;
tick->tick = toTick(*time);
return 0;
}
int APS5_VABI sceRtcSetTick(RtcDateTime* time, const RtcTick* tick) {
(void)time;
(void)tick;
NotImplemented_nid_no_patch(__func__);
return 0;
if (!time || !tick) return SCE_RTC_ERROR_INVALID_POINTER;
if (tick->tick > MAX_TICK) return SCE_RTC_ERROR_INVALID_VALUE;
*time = fromTick(tick->tick);
return 0;
}
int APS5_VABI sceRtcGetCurrentTick(RtcTick* tick) {
if (!tick) return SCE_RTC_ERROR_INVALID_POINTER;
tick->tick = currentTick();
return 0;
}
int APS5_VABI sceRtcGetCurrentNetworkTick(RtcTick* tick) {
return sceRtcGetCurrentTick(tick);
}
int APS5_VABI sceRtcGetCurrentClock(RtcDateTime* time, int time_zone_minutes) {
if (!time) return SCE_RTC_ERROR_INVALID_POINTER;
RtcTick tick{currentTick()};
if (const int result = addTicks(&tick, &tick, time_zone_minutes, TICKS_PER_MINUTE); result != 0) return result;
*time = fromTick(tick.tick);
return 0;
}
int APS5_VABI sceRtcGetCurrentClockLocalTime(RtcDateTime* time) {
if (!time) return SCE_RTC_ERROR_INVALID_POINTER;
const std::uint64_t utc = currentTick();
*time = fromTick(utc + static_cast<std::uint64_t>(localOffsetSeconds(utc) * TICKS_PER_SECOND));
return 0;
}
int APS5_VABI sceRtcConvertUtcToLocalTime(const RtcTick* utc, RtcTick* local_time) {
if (!utc || !local_time) return SCE_RTC_ERROR_INVALID_POINTER;
return addTicks(local_time, utc, localOffsetSeconds(utc->tick), TICKS_PER_SECOND);
}
int APS5_VABI sceRtcConvertLocalTimeToUtc(const RtcTick* local_time, RtcTick* utc) {
if (!local_time || !utc) return SCE_RTC_ERROR_INVALID_POINTER;
const std::int64_t offset = localOffsetSeconds(local_time->tick - static_cast<std::uint64_t>(localOffsetSeconds(local_time->tick) * TICKS_PER_SECOND));
return addTicks(utc, local_time, -offset, TICKS_PER_SECOND);
}
int APS5_VABI sceRtcGetTime_t(const RtcDateTime* time, int64_t* seconds) {
if (!seconds) return SCE_RTC_ERROR_INVALID_POINTER;
if (const int result = validate(time); result != 0) return result;
const std::uint64_t tick = toTick(*time);
if (tick < UNIX_EPOCH_TICK) return SCE_RTC_ERROR_INVALID_VALUE;
*seconds = static_cast<int64_t>((tick - UNIX_EPOCH_TICK) / TICKS_PER_SECOND);
return 0;
}
int APS5_VABI sceRtcSetTime_t(RtcDateTime* time, int64_t seconds) {
(void)time;
(void)seconds;
NotImplemented_nid_no_patch(__func__);
return 0;
if (!time) return SCE_RTC_ERROR_INVALID_POINTER;
if (seconds < 0 || static_cast<std::uint64_t>(seconds) > (MAX_TICK - UNIX_EPOCH_TICK) / TICKS_PER_SECOND) return SCE_RTC_ERROR_INVALID_VALUE;
*time = fromTick(UNIX_EPOCH_TICK + static_cast<std::uint64_t>(seconds) * TICKS_PER_SECOND);
return 0;
}
int APS5_VABI sceRtcGetWin32FileTime(const RtcDateTime* time, uint64_t* win32_time) {
if (!win32_time) return SCE_RTC_ERROR_INVALID_POINTER;
if (const int result = validate(time); result != 0) return result;
const std::uint64_t tick = toTick(*time);
if (tick < WIN32_EPOCH_TICK) return SCE_RTC_ERROR_INVALID_VALUE;
*win32_time = (tick - WIN32_EPOCH_TICK) * 10;
return 0;
}
int APS5_VABI sceRtcSetWin32FileTime(RtcDateTime* time, uint64_t win32_time) {
(void)time;
(void)win32_time;
NotImplemented_nid_no_patch(__func__);
return 0;
if (!time) return SCE_RTC_ERROR_INVALID_POINTER;
if (win32_time / 10 > MAX_TICK - WIN32_EPOCH_TICK) return SCE_RTC_ERROR_INVALID_VALUE;
*time = fromTick(WIN32_EPOCH_TICK + win32_time / 10);
return 0;
}
int APS5_VABI sceRtcTickAddDays(RtcTick* dst, const RtcTick* src, int32_t days) {
(void)dst;
(void)src;
(void)days;
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceRtcFormatRFC3339(char* date_time, const RtcTick* utc, int time_zone_minutes) {
if (!date_time || !utc) return SCE_RTC_ERROR_INVALID_POINTER;
RtcTick local{};
if (const int result = addTicks(&local, utc, time_zone_minutes, TICKS_PER_MINUTE); result != 0) return result;
const RtcDateTime time = fromTick(local.tick);
int written = std::snprintf(date_time, 23, "%04u-%02u-%02uT%02u:%02u:%02u.%02u", time.year, time.month, time.day, time.hour, time.minute, time.second, time.microsecond / 10000);
if (time_zone_minutes == 0) {
std::snprintf(date_time + written, 2, "Z");
} else {
const int offset = time_zone_minutes < 0 ? -time_zone_minutes : time_zone_minutes;
std::snprintf(date_time + written, 7, "%c%02d:%02d", time_zone_minutes < 0 ? '-' : '+', offset / 60, offset % 60);
}
return 0;
}
int APS5_VABI sceRtcTickAddHours(RtcTick* dst, const RtcTick* src, int32_t hours) {
(void)dst;
(void)src;
(void)hours;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRtcTickAddMicroseconds(RtcTick* dst, const RtcTick* src, int64_t usec) {
(void)dst;
(void)src;
(void)usec;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRtcTickAddMinutes(RtcTick* dst, const RtcTick* src, int64_t minutes) {
(void)dst;
(void)src;
(void)minutes;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRtcTickAddSeconds(RtcTick* dst, const RtcTick* src, int64_t seconds) {
(void)dst;
(void)src;
(void)seconds;
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceRtcParseRFC3339(RtcTick* utc, const char* date_time) {
if (!utc || !date_time) return SCE_RTC_ERROR_INVALID_POINTER;
const char* cursor = date_time;
int year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0;
if (!parseDigits(cursor, 4, year) || *cursor++ != '-' || !parseDigits(cursor, 2, month) || *cursor++ != '-' || !parseDigits(cursor, 2, day)) return SCE_RTC_ERROR_BAD_PARSE;
if (*cursor != 'T' && *cursor != 't' && *cursor != ' ') return SCE_RTC_ERROR_BAD_PARSE;
++cursor;
if (!parseDigits(cursor, 2, hour) || *cursor++ != ':' || !parseDigits(cursor, 2, minute) || *cursor++ != ':' || !parseDigits(cursor, 2, second)) return SCE_RTC_ERROR_BAD_PARSE;
std::uint32_t microsecond = 0;
if (*cursor == '.') {
++cursor;
std::uint32_t scale = 100000;
if (*cursor < '0' || *cursor > '9') return SCE_RTC_ERROR_BAD_PARSE;
for (; *cursor >= '0' && *cursor <= '9'; ++cursor, scale /= 10) microsecond += static_cast<std::uint32_t>(*cursor - '0') * scale;
}
std::int64_t offsetMinutes = 0;
if (*cursor == 'Z' || *cursor == 'z') {
++cursor;
} else if (*cursor == '+' || *cursor == '-') {
const int sign = *cursor++ == '-' ? -1 : 1;
int offsetHours = 0, offsetMinute = 0;
if (!parseDigits(cursor, 2, offsetHours) || *cursor++ != ':' || !parseDigits(cursor, 2, offsetMinute)) return SCE_RTC_ERROR_BAD_PARSE;
offsetMinutes = sign * (offsetHours * 60 + offsetMinute);
} else {
return SCE_RTC_ERROR_BAD_PARSE;
}
if (*cursor != 0) return SCE_RTC_ERROR_BAD_PARSE;
const RtcDateTime time{static_cast<std::uint16_t>(year), static_cast<std::uint16_t>(month), static_cast<std::uint16_t>(day),
static_cast<std::uint16_t>(hour), static_cast<std::uint16_t>(minute), static_cast<std::uint16_t>(second), microsecond};
if (const int result = validate(&time); result != 0) return result;
const RtcTick local{toTick(time)};
return addTicks(utc, &local, -offsetMinutes, TICKS_PER_MINUTE);
}
int APS5_VABI sceRtcTickAddTicks(RtcTick* dst, const RtcTick* src, int64_t ticks) {
(void)dst;
(void)src;
(void)ticks;
NotImplemented_nid_no_patch(__func__);
return 0;
return addTicks(dst, src, ticks, 1);
}
int APS5_VABI sceRtcTickAddMicroseconds(RtcTick* dst, const RtcTick* src, int64_t usec) {
return addTicks(dst, src, usec, 1);
}
int APS5_VABI sceRtcTickAddSeconds(RtcTick* dst, const RtcTick* src, int64_t seconds) {
return addTicks(dst, src, seconds, TICKS_PER_SECOND);
}
int APS5_VABI sceRtcTickAddMinutes(RtcTick* dst, const RtcTick* src, int64_t minutes) {
return addTicks(dst, src, minutes, TICKS_PER_MINUTE);
}
int APS5_VABI sceRtcTickAddHours(RtcTick* dst, const RtcTick* src, int32_t hours) {
return addTicks(dst, src, hours, TICKS_PER_HOUR);
}
int APS5_VABI sceRtcTickAddDays(RtcTick* dst, const RtcTick* src, int32_t days) {
return addTicks(dst, src, days, TICKS_PER_DAY);
}
int APS5_VABI sceRtcTickAddWeeks(RtcTick* dst, const RtcTick* src, int32_t weeks) {
(void)dst;
(void)src;
(void)weeks;
NotImplemented_nid_no_patch(__func__);
return 0;
return addTicks(dst, src, weeks, 7 * TICKS_PER_DAY);
}
int APS5_VABI sceRtcTickAddMonths(RtcTick* dst, const RtcTick* src, int32_t months) {
return addMonths(dst, src, months);
}
int APS5_VABI sceRtcTickAddYears(RtcTick* dst, const RtcTick* src, int16_t years) {
return addMonths(dst, src, static_cast<std::int64_t>(years) * 12);
}
}

View File

@ -1,5 +1,6 @@
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <stdexcept>
#include <string>
@ -27,6 +28,10 @@ void PadInput::HandleEvent(const SDL_Event& event, DisplayWindow& window) {
publish();
return;
}
// APS5_NO_PAD_INPUT=1 keeps a measurement run from reacting to keys or mouse buttons that reach
// its window (a stray press advances the title into another stage).
static const bool ignoreInput = std::getenv("APS5_NO_PAD_INPUT") != nullptr;
if (ignoreInput && event.type == SDL_MOUSEWHEEL) return;
if (event.type == SDL_MOUSEWHEEL) {
int direction = (event.wheel.y > 0) - (event.wheel.y < 0);
if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) direction = -direction;
@ -44,6 +49,7 @@ void PadInput::HandleEvent(const SDL_Event& event, DisplayWindow& window) {
const bool keyboard = event.type == SDL_KEYDOWN || event.type == SDL_KEYUP;
const bool mouse = event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP;
if (!keyboard && !mouse) return;
if (ignoreInput) return;
if (keyboard && event.key.repeat != 0) return;
const bool down = event.type == SDL_KEYDOWN || event.type == SDL_MOUSEBUTTONDOWN;
for (std::size_t index = 0; index < Pad::InputMapping.size(); ++index) {

View File

@ -1,6 +1,8 @@
#include <bit>
#include <chrono>
#include <cstdlib>
#include <cstdio>
#include <thread>
#include <limits>
#include <stdexcept>
@ -42,6 +44,16 @@ public:
request->outputHandle = info.handle;
request->flipMode = static_cast<int>(info.mode);
request->flipArg = info.argument;
// Debug aid: APS5_FLIP_QUEUE_WAIT=1 waits for room instead of failing the flip, so a
// presenter stall keeps the process alive for a debugger (reported every 5 s).
static const bool waitWhenFull = std::getenv("APS5_FLIP_QUEUE_WAIT") != nullptr;
if (waitWhenFull) {
int waited = 0;
while (queue->reservations.load() >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
if (++waited % 500 == 0) std::fprintf(stderr, "[flip] queue full for %d s; waiting (APS5_FLIP_QUEUE_WAIT)\n", waited / 100);
}
}
std::lock_guard queueLock(queue->mutex);
if (queue->failure) std::rethrow_exception(queue->failure);
require(!queue->stopping, "flip during shutdown");
@ -123,8 +135,10 @@ void FlipRequest::GpuReady(const std::shared_ptr<AgcDriver::FrameTiming>& frameT
queue->changed.notify_all();
// The worker is done once the request is queued: hardware does not stall the command processor
// on a flip, and the title observes completion through flipPendingNum, which processFlip drops
// after presenting. A presentation failure reaches the worker through ReportFailure at its next
// packet. Debug aid: APS5_SYNC_FLIP=1 parks the worker until the presenter is done, as before.
// once the presentation is queued behind the frame's GPU work (up to APS5_FLIP_INFLIGHT
// presentations may still be executing then, see Driver::Present). A presentation failure
// reaches the worker through ReportFailure at its next packet. Debug aid: APS5_SYNC_FLIP=1
// parks the worker until the presenter is done, as before.
static const bool syncFlip = std::getenv("APS5_SYNC_FLIP") != nullptr;
if (!syncFlip) return;
std::unique_lock lock(cfg->mutex);

View File

@ -238,6 +238,34 @@ void testPresentation(bool expectUnavailable) {
LibcRunShutdown_nid_postfix();
}
// Flips of two buffers submitted without waiting for each other: every one completes in order
// (presentations trail on the GPU, APS5_FLIP_INFLIGHT), and the shutdown retires the ones in flight.
void testBackToBack() {
const int handle = sceVideoOutOpen(255, 0, 0, nullptr);
auto cfg = VideoOutDriver::Get().GetConfig(handle);
std::array<std::vector<std::byte>, 2> allocations{std::vector<std::byte>(6 * 65536 + 65535), std::vector<std::byte>(6 * 65536 + 65535)};
std::array<VideoOutBuffers, 2> buffers{};
for (std::size_t i = 0; i < buffers.size(); ++i) {
const auto storage = alignedBuffer(allocations[i]);
fillBuffer(storage, 259, 137);
buffers[i] = {storage.data(), nullptr, {nullptr, nullptr}};
}
VideoOutBufferAttribute2 attribute{};
sceVideoOutSetBufferAttribute2(&attribute, 0x8000000000000000ull, 0, 259, 137, 0, 0, 0);
sceVideoOutRegisterBuffers2(handle, 0, 0, buffers.data(), 2, &attribute, 0, nullptr);
int64_t argument = 1000;
for (int index : {0, 1, 0, 1}) sceVideoOutSubmitFlip(handle, index, 1, ++argument);
std::unique_lock lock(cfg->mutex);
check(cfg->vblankCond.wait_for(lock, std::chrono::seconds(15), [&] { return cfg->failure || cfg->flipStatus.count == 4; }), "back-to-back presentations did not complete");
if (cfg->failure) std::rethrow_exception(cfg->failure);
check(cfg->flipStatus.flipPendingNum == 0 && cfg->bufferPending[0] == 0 && cfg->bufferPending[1] == 0, "back-to-back flips left pending counts");
check(cfg->flipStatus.flipArg == argument && cfg->flipStatus.currentBuffer == 1, "back-to-back flip status is wrong");
lock.unlock();
sceVideoOutUnregisterBuffers(handle, 0);
sceVideoOutClose(handle);
LibcRunShutdown_nid_postfix();
}
}
int main(int argc, char** argv) {
@ -245,6 +273,7 @@ int main(int argc, char** argv) {
if (argc == 2 && std::string(argv[1]) == "decode") testDecode();
else if (argc == 2 && std::string(argv[1]) == "controls") testControls();
else if (argc == 2 && std::string(argv[1]) == "present") testPresentation(false);
else if (argc == 2 && std::string(argv[1]) == "backtoback") testBackToBack();
else if (argc == 2 && std::string(argv[1]) == "unavailable") testPresentation(true);
else testLifetime(argc == 2 && std::string(argv[1]) == "reopen");
std::puts("VideoOut flip tests passed");

View File

@ -0,0 +1,119 @@
#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_CPUTOPOLOGY_HPP
#define CORE_LIBS_PRX_LIBC_INCLUDE_CPUTOPOLOGY_HPP
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#endif
// Hybrid CPU layout (Intel P/E cores) from the OS: which logical processors belong to the lowest and
// the highest efficiency class. Non-hybrid machines report no layout and every pin is a no-op unless
// a mask is given explicitly. Header-only so libkernel and the AGC driver share it without a link.
namespace CpuTopology {
struct Layout {
std::uint64_t process = 0;
std::uint64_t efficient = 0;
std::uint64_t performant = 0;
bool hybrid = false;
};
inline const Layout& Get() {
static const Layout layout = [] {
Layout result{};
#ifdef _WIN32
DWORD_PTR processMask = 0;
DWORD_PTR systemMask = 0;
if (GetProcessAffinityMask(GetCurrentProcess(), &processMask, &systemMask)) result.process = static_cast<std::uint64_t>(processMask);
DWORD length = 0;
GetLogicalProcessorInformationEx(RelationProcessorCore, nullptr, &length);
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER || length == 0) return result;
std::vector<unsigned char> buffer(length);
if (!GetLogicalProcessorInformationEx(RelationProcessorCore, reinterpret_cast<SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*>(buffer.data()), &length)) return result;
std::uint64_t byClass[256] = {};
bool seen[256] = {};
for (DWORD offset = 0; offset + 8 <= length;) {
const auto* entry = reinterpret_cast<const SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*>(buffer.data() + offset);
if (entry->Size == 0 || offset + entry->Size > length) break;
if (entry->Relationship == RelationProcessorCore && entry->Processor.GroupCount >= 1 && entry->Processor.GroupMask[0].Group == 0) {
const unsigned cls = entry->Processor.EfficiencyClass;
byClass[cls] |= static_cast<std::uint64_t>(entry->Processor.GroupMask[0].Mask);
seen[cls] = true;
}
offset += entry->Size;
}
int lowest = -1;
int highest = -1;
for (int cls = 0; cls < 256; ++cls) {
if (!seen[cls]) continue;
if (lowest < 0) lowest = cls;
highest = cls;
}
if (lowest < 0 || lowest == highest) return result;
result.efficient = byClass[lowest];
result.performant = byClass[highest];
result.hybrid = result.efficient != 0 && result.performant != 0;
#endif
return result;
}();
return layout;
}
// A hex mask from the environment, 0 when unset or empty.
inline std::uint64_t MaskFromEnvironment(const char* name) {
const char* text = std::getenv(name);
if (text == nullptr || *text == '\0') return 0;
return std::strtoull(text, nullptr, 16);
}
inline bool Trace() {
static const bool trace = std::getenv("APS5_TRACE_AFFINITY") != nullptr;
return trace;
}
inline unsigned long ThreadId(void* handle) {
#ifdef _WIN32
return handle != nullptr ? GetThreadId(static_cast<HANDLE>(handle)) : GetCurrentThreadId();
#else
(void)handle;
return 0;
#endif
}
// Restricts the thread (`nullptr` is the calling thread) to `mask` clipped to the process affinity;
// returns the mask applied, 0 when nothing was.
inline std::uint64_t Pin(void* handle, std::uint64_t mask) {
#ifdef _WIN32
const auto& layout = Get();
if (layout.process != 0) mask &= layout.process;
if (mask == 0) return 0;
HANDLE thread = handle != nullptr ? static_cast<HANDLE>(handle) : GetCurrentThread();
return SetThreadAffinityMask(thread, static_cast<DWORD_PTR>(mask)) != 0 ? mask : 0;
#else
(void)handle;
(void)mask;
return 0;
#endif
}
// Pin plus the APS5_TRACE_AFFINITY=1 line naming the thread.
inline std::uint64_t PinTraced(const char* role, void* handle, std::uint64_t mask) {
const auto applied = Pin(handle, mask);
if (Trace()) std::fprintf(stderr, "[affinity] %s tid=%lu mask=0x%llx%s\n", role, ThreadId(handle), static_cast<unsigned long long>(mask), applied != 0 ? "" : " (not applied)");
return applied;
}
// Helper threads (deferred release, mirror refresh) are pinned only when APS5_HELPER_AFFINITY_MASK
// names their cores.
inline void PinHelperThread(const char* role) {
static const std::uint64_t mask = MaskFromEnvironment("APS5_HELPER_AFFINITY_MASK");
if (mask != 0) PinTraced(role, nullptr, mask);
}
}
#endif

View File

@ -142,7 +142,7 @@ void* ApplicationHeapReallocate_nid_no_patch(void* pointer, std::size_t bytes) {
ApplicationHeapFree_nid_no_patch(pointer);
return nullptr;
}
const auto reallocate = callback<Reallocate>(2);
const auto reallocate = callback<Reallocate>(3);
CallbackScope scope;
return requireAllocation(reallocate(pointer, bytes));
}
@ -158,7 +158,7 @@ void* ApplicationHeapAlign_nid_no_patch(std::size_t alignment, std::size_t bytes
void* ApplicationHeapCalloc_nid_no_patch(std::size_t count, std::size_t bytes) {
if (bytes != 0 && count > std::numeric_limits<std::size_t>::max() / bytes) throw std::length_error("application heap: calloc size overflow");
const auto calloc = callback<Calloc>(3);
const auto calloc = callback<Calloc>(2);
CallbackScope scope;
return requireAllocation(calloc(count, bytes));
}

View File

@ -31,17 +31,18 @@ static void RawLog(const char* format, ...) {
Header* primary = Primary(globals.caught);
const char* typeName = primary->type ? primary->type->name() : nullptr;
const char* what = nullptr;
if (primary->adjusted) {
void* exception = primary->adjusted;
if (exception && primary->type && Match(&typeid(std::exception), primary->type, exception)) {
struct VtableLayout { std::ptrdiff_t offset; const void* type; void (*destroy)(void*); void (*del)(void*); const char* (*whatFn)(const void*); };
const void* vtable = *static_cast<const void* const*>(primary->adjusted);
const void* vtable = *static_cast<const void* const*>(exception);
auto* layout = reinterpret_cast<const VtableLayout*>(static_cast<const char*>(vtable) - offsetof(VtableLayout, destroy));
#ifdef _WIN32
if (!primary->_pad) {
using GuestWhat = const char* (__attribute__((sysv_abi)) *)(const void*);
what = reinterpret_cast<GuestWhat>(layout->whatFn)(primary->adjusted);
what = reinterpret_cast<GuestWhat>(layout->whatFn)(exception);
} else
#endif
what = layout->whatFn(primary->adjusted);
what = layout->whatFn(exception);
}
int status = 0;
char* demangled = abi::__cxa_demangle(typeName, nullptr, nullptr, &status);

View File

@ -35,6 +35,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL
${CMAKE_CURRENT_SOURCE_DIR}/System/src/Rtld.cpp
${CMAKE_CURRENT_SOURCE_DIR}/System/src/Process.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Time/Time.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Time/TimedWait.cpp
${CMAKE_CURRENT_SOURCE_DIR}/AppMetadata/src/AppMetadata.cpp
${CMAKE_CURRENT_SOURCE_DIR}/AppMetadata/src/ParamJsonParser.cpp
)

View File

@ -41,7 +41,7 @@ void KernelEqueuePrivate::Close() {
}
}
m_events.clear();
m_cond.notify_all();
m_cond.NotifyAll();
}
void KernelEqueuePrivate::TriggerExpiredTimers(uint64_t nowNs) {
@ -113,8 +113,7 @@ int KernelEqueuePrivate::WaitForEvents(KernelEvent* ev, int num, uint32_t micros
if (m_closed) {
return EQUEUE_ERROR_EBADF;
}
const auto deadline = std::chrono::steady_clock::now() +
std::chrono::microseconds(micros);
const std::uint64_t deadline = TimedWait::DeadlineNanos(micros);
for (;;) {
TriggerExpiredTimers(MonotonicNs());
int ret = 0;
@ -152,21 +151,16 @@ int KernelEqueuePrivate::WaitForEvents(KernelEvent* ev, int num, uint32_t micros
return EQUEUE_ERROR_EBADF;
}
if (micros == 0) {
m_cond.wait(lock);
m_cond.Wait(lock);
} else {
uint32_t timerWait = 0;
const bool hasTimer = NextTimerWaitMicros(MonotonicNs(), &timerWait);
const auto now = std::chrono::steady_clock::now();
const std::uint64_t now = TimedWait::NowNanos();
if (now >= deadline) {
return 0;
}
const auto remaining = std::chrono::duration_cast<std::chrono::microseconds>(
deadline - now
).count();
const auto waitUs = hasTimer
? std::min<uint64_t>(static_cast<uint64_t>(remaining), timerWait)
: static_cast<uint64_t>(remaining);
m_cond.wait_for(lock, std::chrono::microseconds(waitUs));
const std::uint64_t timerDeadline = now + static_cast<std::uint64_t>(timerWait) * 1000ULL;
m_cond.WaitUntil(lock, hasTimer ? std::min(deadline, timerDeadline) : deadline);
}
}
}
@ -190,7 +184,7 @@ int KernelEqueuePrivate::AddEvent(const KernelEqueueEvent& event) {
} else {
m_events.push_back(event);
}
m_cond.notify_one();
m_cond.NotifyOne();
return EQUEUE_OK;
}
@ -212,7 +206,7 @@ int KernelEqueuePrivate::TriggerEvent(uintptr_t ident, int16_t filter, void* tri
} else {
it->triggered = true;
}
m_cond.notify_one();
m_cond.NotifyOne();
return EQUEUE_OK;
}

View File

@ -1,7 +1,6 @@
#ifndef CORE_LIBS_PRX_LIBKERNEL_EQUEUE_EQUEUE_HPP
#define CORE_LIBS_PRX_LIBKERNEL_EQUEUE_EQUEUE_HPP
#include <condition_variable>
#include <cstdint>
#include <deque>
#include <list>
@ -12,6 +11,7 @@
#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libkernel/Time/include/TimedWait.hpp"
static constexpr int16_t EVFILT_USER = -11;
static constexpr int16_t EVFILT_VIDEO_OUT = -13;
@ -76,7 +76,7 @@ private:
std::list<KernelEqueueEvent> m_events;
std::mutex m_mutex;
std::condition_variable_any m_cond;
TimedWait::Condition m_cond;
std::string m_name;
KernelEqueue m_handle;
bool m_closed = false;

View File

@ -3,8 +3,8 @@
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libkernel/Time/include/Time.hpp"
#include "prx/libkernel/Time/include/TimedWait.hpp"
#include <chrono>
#include <condition_variable>
#include <mutex>
#include <string>
#include <cstdio>
@ -29,7 +29,7 @@ static constexpr uint32_t EVF_WAITMODE_CLEAR_PAT = 0x20;
struct KernelEventFlagPrivate {
std::mutex _mutex;
std::condition_variable _changed;
TimedWait::Condition _changed;
std::string _name;
uint64_t _pattern = 0;
bool _multipleWaiters = false;
@ -83,7 +83,7 @@ int APS5_VABI sceKernelDeleteEventFlag(KernelEventFlag ef) {
if (ef->_deleted) return SCE_KERNEL_ERROR_ESRCH;
ef->_deleted = true;
if (ef->_waiters != 0) {
ef->_changed.notify_all();
ef->_changed.NotifyAll();
return SCE_OK;
}
}
@ -97,7 +97,7 @@ int APS5_VABI sceKernelSetEventFlag(KernelEventFlag ef, uint64_t bit_pattern) {
if (ef->_deleted) return SCE_KERNEL_ERROR_ESRCH;
ef->_pattern |= bit_pattern;
if (TraceSync()) std::fprintf(stderr, "[evf] set %p '%s' |=0x%llx -> 0x%llx\n", static_cast<void*>(ef), ef->_name.c_str(), static_cast<unsigned long long>(bit_pattern), static_cast<unsigned long long>(ef->_pattern));
ef->_changed.notify_all();
ef->_changed.NotifyAll();
return SCE_OK;
}
@ -116,7 +116,7 @@ int APS5_VABI sceKernelCancelEventFlag(KernelEventFlag ef, uint64_t set_pattern,
if (num_wait_threads) *num_wait_threads = ef->_waiters;
ef->_pattern = set_pattern;
++ef->_cancelGeneration;
ef->_changed.notify_all();
ef->_changed.NotifyAll();
return SCE_OK;
}
@ -147,12 +147,11 @@ int APS5_VABI sceKernelWaitEventFlag(KernelEventFlag ef, uint64_t bit_pattern, u
bool timedOut = false;
const auto waitStart = std::chrono::steady_clock::now();
if (timeout) {
const auto deadline = waitStart + std::chrono::microseconds(*timeout);
timedOut = !ef->_changed.wait_until(lock, deadline, released);
const auto remaining = std::chrono::duration_cast<std::chrono::microseconds>(deadline - std::chrono::steady_clock::now()).count();
*timeout = remaining > 0 ? static_cast<KernelUseconds>(remaining) : 0;
const std::uint64_t deadline = TimedWait::DeadlineNanos(*timeout);
timedOut = !ef->_changed.WaitUntil(lock, deadline, released);
*timeout = static_cast<KernelUseconds>(TimedWait::RemainingMicros(deadline));
} else {
ef->_changed.wait(lock, released);
ef->_changed.Wait(lock, released);
}
KernelTraceWait_nid_postfix("evf", __builtin_return_address(0), static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - waitStart).count()), timedOut);
--ef->_waiters;

View File

@ -3,16 +3,16 @@
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "Common.hpp"
#include "prx/libkernel/Time/include/TimedWait.hpp"
#include <cerrno>
#include <chrono>
#include <condition_variable>
#include <cstring>
#include <mutex>
// The guest sem_t is at least 8 bytes; its first word holds a pointer to the host semaphore.
struct PosixSemaphore {
std::mutex lock;
std::condition_variable available;
TimedWait::Condition available;
unsigned int count;
};
@ -35,9 +35,9 @@ static int WaitFor(PosixSemaphore* semaphore, const KernelUseconds* usec) {
std::unique_lock lock(semaphore->lock);
const auto ready = [&] { return semaphore->count > 0; };
if (usec) {
if (!semaphore->available.wait_for(lock, std::chrono::microseconds(*usec), ready)) return Fail(PosixThread::GUEST_ETIMEDOUT);
if (!semaphore->available.WaitUntil(lock, TimedWait::DeadlineNanos(*usec), ready)) return Fail(PosixThread::GUEST_ETIMEDOUT);
} else {
semaphore->available.wait(lock, ready);
semaphore->available.Wait(lock, ready);
}
--semaphore->count;
return 0;
@ -99,7 +99,7 @@ int APS5_VABI sem_post_nid_postfix(void* sem) {
std::lock_guard lock(semaphore->lock);
++semaphore->count;
}
semaphore->available.notify_one();
semaphore->available.NotifyOne();
return 0;
}

View File

@ -3,6 +3,7 @@
#include <sched.h>
#include "SceTypes.hpp"
#include "prx/libkernel/Time/include/TimedWait.hpp"
#include <atomic>
#include <condition_variable>
#include <cstddef>
@ -46,7 +47,7 @@ struct PthreadCondattrPrivate {
};
struct PthreadCondPrivate {
std::condition_variable_any _cv;
TimedWait::Condition _cv;
int _clockid = 0;
};

View File

@ -1,7 +1,9 @@
#include "../include/Pthread.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libkernel/Time/include/Time.hpp"
#include "prx/libkernel/Time/include/TimedWait.hpp"
#include <chrono>
#include <cstdint>
#include <optional>
#include <thread>
#include <stdexcept>
@ -10,7 +12,7 @@ static constexpr int SCE_OK = 0;
static constexpr int SCE_KERNEL_ERROR_ENOMEM = 0x8002000C;
static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = 0x8002003C;
static int WaitOn(PthreadCondPrivate* cond, PthreadMutexPrivate* mutex, std::optional<std::chrono::microseconds> timeout, const void* caller) {
static int WaitOn(PthreadCondPrivate* cond, PthreadMutexPrivate* mutex, std::optional<std::uint64_t> deadlineNanos, const void* caller) {
const auto self = std::this_thread::get_id();
bool timedOut = false;
const auto waitStart = std::chrono::steady_clock::now();
@ -21,13 +23,13 @@ static int WaitOn(PthreadCondPrivate* cond, PthreadMutexPrivate* mutex, std::opt
mutex->_owner.store(std::thread::id{}, std::memory_order_relaxed);
if (mutex->_type == MutexType::Recursive) {
std::unique_lock<std::recursive_timed_mutex> lock(mutex->_rmtx, std::adopt_lock);
if (timeout) timedOut = cond->_cv.wait_for(lock, *timeout) == std::cv_status::timeout;
else cond->_cv.wait(lock);
if (deadlineNanos) timedOut = !cond->_cv.WaitUntil(lock, *deadlineNanos);
else cond->_cv.Wait(lock);
lock.release();
} else {
std::unique_lock<std::timed_mutex> lock(mutex->_mtx, std::adopt_lock);
if (timeout) timedOut = cond->_cv.wait_for(lock, *timeout) == std::cv_status::timeout;
else cond->_cv.wait(lock);
if (deadlineNanos) timedOut = !cond->_cv.WaitUntil(lock, *deadlineNanos);
else cond->_cv.Wait(lock);
lock.release();
}
mutex->_owner.store(self, std::memory_order_relaxed);
@ -69,26 +71,26 @@ int APS5_VABI scePthreadCondDestroy(PthreadCond* cond) {
int APS5_VABI scePthreadCondSignal(PthreadCond* cond) {
if (!cond || !*cond) throw std::runtime_error("scePthreadCondSignal: null cond");
(*cond)->_cv.notify_one();
(*cond)->_cv.NotifyOne();
return SCE_OK;
}
int APS5_VABI scePthreadCondBroadcast(PthreadCond* cond) {
if (!cond || !*cond) throw std::runtime_error("scePthreadCondBroadcast: null cond");
(*cond)->_cv.notify_all();
(*cond)->_cv.NotifyAll();
return SCE_OK;
}
int APS5_VABI scePthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, unsigned int usec) {
if (!cond || !*cond || !mutex || !*mutex)
throw std::runtime_error("scePthreadCondTimedwait: null arg");
return WaitOn(*cond, *mutex, std::chrono::microseconds(usec), __builtin_return_address(0));
return WaitOn(*cond, *mutex, TimedWait::DeadlineNanos(usec), __builtin_return_address(0));
}
int APS5_VABI scePthreadCondSignalto(PthreadCond* cond, Pthread thread) {
(void)thread;
if (!cond || !*cond) throw std::runtime_error("scePthreadCondSignalto: null cond");
(*cond)->_cv.notify_all();
(*cond)->_cv.NotifyAll();
return SCE_OK;
}

View File

@ -1,7 +1,9 @@
#include "../include/Pthread.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libkernel/Time/include/TimedWait.hpp"
#include <cerrno>
#include <chrono>
#include <cstdint>
#include <stdexcept>
#include <string>
@ -98,9 +100,10 @@ int APS5_VABI scePthreadMutexTimedlock(PthreadMutex* mutex, KernelUseconds usec)
if (!mutex || !*mutex) throw std::runtime_error("scePthreadMutexTimedlock: null mutex");
auto* m = *mutex;
const auto tid = std::this_thread::get_id();
const auto timeout = std::chrono::microseconds(usec);
const std::uint64_t deadline = TimedWait::DeadlineNanos(usec);
if (m->_type == MutexType::Recursive) {
if (!m->_rmtx.try_lock_for(timeout)) return SCE_KERNEL_ERROR_ETIMEDOUT;
const bool locked = TimedWait::AcquireUntil(deadline, [&] { return m->_rmtx.try_lock(); }, [&](std::uint64_t micros) { return m->_rmtx.try_lock_for(std::chrono::microseconds(micros)); });
if (!locked) return SCE_KERNEL_ERROR_ETIMEDOUT;
m->_owner.store(tid, std::memory_order_relaxed);
++m->_count;
return SCE_OK;
@ -108,7 +111,8 @@ int APS5_VABI scePthreadMutexTimedlock(PthreadMutex* mutex, KernelUseconds usec)
if (m->_type == MutexType::ErrorCheck) {
if (m->_owner.load(std::memory_order_acquire) == tid) return SCE_KERNEL_ERROR_EDEADLK;
}
if (!m->_mtx.try_lock_for(timeout)) return SCE_KERNEL_ERROR_ETIMEDOUT;
const bool locked = TimedWait::AcquireUntil(deadline, [&] { return m->_mtx.try_lock(); }, [&](std::uint64_t micros) { return m->_mtx.try_lock_for(std::chrono::microseconds(micros)); });
if (!locked) return SCE_KERNEL_ERROR_ETIMEDOUT;
m->_owner.store(tid, std::memory_order_relaxed);
return SCE_OK;
}

View File

@ -1,6 +1,8 @@
#include "../include/Pthread.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libkernel/Time/include/TimedWait.hpp"
#include <chrono>
#include <cstdint>
#include <stdexcept>
static constexpr int SCE_OK = 0;
@ -51,7 +53,8 @@ int APS5_VABI scePthreadRwlockTryrdlock(PthreadRwlock* rwlock) {
int APS5_VABI scePthreadRwlockTimedrdlock(PthreadRwlock* rwlock, KernelUseconds usec) {
auto* lock = RequireRwlock(rwlock, __func__);
if (OwnsWrite(lock)) return SCE_KERNEL_ERROR_EDEADLK;
return lock->_lock.try_lock_shared_for(std::chrono::microseconds(usec)) ? SCE_OK : SCE_KERNEL_ERROR_ETIMEDOUT;
const bool locked = TimedWait::AcquireUntil(TimedWait::DeadlineNanos(usec), [&] { return lock->_lock.try_lock_shared(); }, [&](std::uint64_t micros) { return lock->_lock.try_lock_shared_for(std::chrono::microseconds(micros)); });
return locked ? SCE_OK : SCE_KERNEL_ERROR_ETIMEDOUT;
}
int APS5_VABI scePthreadRwlockWrlock(PthreadRwlock* rwlock) {
@ -72,7 +75,8 @@ int APS5_VABI scePthreadRwlockTrywrlock(PthreadRwlock* rwlock) {
int APS5_VABI scePthreadRwlockTimedwrlock(PthreadRwlock* rwlock, KernelUseconds usec) {
auto* lock = RequireRwlock(rwlock, __func__);
if (OwnsWrite(lock)) return SCE_KERNEL_ERROR_EDEADLK;
if (!lock->_lock.try_lock_for(std::chrono::microseconds(usec))) return SCE_KERNEL_ERROR_ETIMEDOUT;
const bool locked = TimedWait::AcquireUntil(TimedWait::DeadlineNanos(usec), [&] { return lock->_lock.try_lock(); }, [&](std::uint64_t micros) { return lock->_lock.try_lock_for(std::chrono::microseconds(micros)); });
if (!locked) return SCE_KERNEL_ERROR_ETIMEDOUT;
lock->_writer.store(std::this_thread::get_id(), std::memory_order_release);
return SCE_OK;
}

View File

@ -1,5 +1,6 @@
#include "../include/Pthread.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/CpuTopology.hpp"
#include <algorithm>
#include <cstdlib>
#include <cstring>
@ -63,6 +64,36 @@ static void ReleaseThread(PthreadPrivate* thread) {
delete thread;
}
// The title's job workers ("BPE JobWorkerThread CPU0".."CPU12") spin at full load on one core each.
// Pinning them to the efficiency cores of a hybrid CPU (APS5_JOB_AFFINITY=1, with the driver's
// worker pinning) measured 0 to -9 % at the video stage, so the default leaves them free;
// APS5_JOB_AFFINITY_MASK=<hex> picks the cores.
static constexpr const char* JOB_WORKER_PREFIX = "BPE JobWorkerThread";
static std::uint64_t JobWorkerMask() {
static const std::uint64_t mask = [] {
if (std::getenv("APS5_NO_JOB_AFFINITY") != nullptr) return std::uint64_t{0};
const auto requested = CpuTopology::MaskFromEnvironment("APS5_JOB_AFFINITY_MASK");
if (requested != 0) return requested;
if (std::getenv("APS5_JOB_AFFINITY") == nullptr) return std::uint64_t{0};
const auto& layout = CpuTopology::Get();
return layout.hybrid ? layout.efficient : std::uint64_t{0};
}();
return mask;
}
static void ApplyJobAffinity(PthreadPrivate& thread, void* handle) {
if (thread.name.compare(0, std::strlen(JOB_WORKER_PREFIX), JOB_WORKER_PREFIX) != 0) return;
const auto mask = JobWorkerMask();
if (mask == 0) return;
static std::once_flag summary;
std::call_once(summary, [mask] {
const auto& layout = CpuTopology::Get();
std::fprintf(stderr, "[affinity] job worker threads -> 0x%llx (hybrid=%d efficient=0x%llx performant=0x%llx process=0x%llx)\n", static_cast<unsigned long long>(mask), layout.hybrid ? 1 : 0, static_cast<unsigned long long>(layout.efficient), static_cast<unsigned long long>(layout.performant), static_cast<unsigned long long>(layout.process));
});
CpuTopology::PinTraced(thread.name.c_str(), handle, mask);
}
struct NativeThreadArgs {
std::unique_ptr<ThreadArgs> guest;
std::future<bool> start;
@ -90,6 +121,7 @@ static unsigned __stdcall StartNativeThread(void* opaque) {
if (!self->name.empty()) {
const std::wstring description(self->name.begin(), self->name.end());
SetThreadDescription(GetCurrentThread(), description.c_str());
ApplyJobAffinity(*self, nullptr);
}
args->initialized.set_value();
} catch (...) {
@ -280,6 +312,7 @@ int APS5_VABI scePthreadRename(Pthread thread, const char* name) {
#ifdef _WIN32
const std::wstring description(thread->name.begin(), thread->name.end());
SetThreadDescription(static_cast<HANDLE>(thread->nativeHandle), description.c_str());
ApplyJobAffinity(*thread, thread->nativeHandle);
#endif
return SCE_OK;
}

View File

@ -2,13 +2,13 @@
#define CORE_LIBS_PRX_LIBKERNEL_SEMAPHORE_SEMAPHORE_HPP
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <mutex>
#include <string>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libkernel/Time/include/TimedWait.hpp"
constexpr int KERNEL_SEMA_OK = 0;
constexpr int KERNEL_SEMA_ERROR_EINVAL = static_cast<int>(0x80020016);
@ -19,7 +19,7 @@ struct KernelSemaPrivate {
KernelSemaPrivate(std::int32_t initCount, std::int32_t maxCount, std::string name, bool isFifo);
std::mutex mutex;
std::condition_variable condition;
TimedWait::Condition condition;
std::string name;
std::int32_t tokenCount;
std::int32_t maxCount;

View File

@ -45,7 +45,7 @@ int APS5_VABI sceKernelSignalSema(KernelSema sem, int count) {
return KERNEL_SEMA_ERROR_EINVAL;
}
sem->tokenCount += count;
sem->condition.notify_all();
sem->condition.NotifyAll();
return KERNEL_SEMA_OK;
}
@ -60,14 +60,13 @@ int APS5_VABI sceKernelWaitSema(KernelSema sem, int need, KernelUseconds* time)
KernelTraceWait_nid_postfix("sema", __builtin_return_address(0), static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - waitStart).count()), timedOut);
};
if (time == nullptr) {
sem->condition.wait(lock, [&] { return sem->tokenCount >= need; });
sem->condition.Wait(lock, [&] { return sem->tokenCount >= need; });
traceWait(false);
sem->tokenCount -= need;
return KERNEL_SEMA_OK;
}
auto timeout = std::chrono::microseconds(*time);
bool acquired = sem->condition.wait_for(lock, timeout, [&] { return sem->tokenCount >= need; });
const bool acquired = sem->condition.WaitUntil(lock, TimedWait::DeadlineNanos(*time), [&] { return sem->tokenCount >= need; });
traceWait(!acquired);
if (!acquired) {
return KERNEL_SEMA_ERROR_ETIMEDOUT;

View File

@ -1,4 +1,5 @@
#include "prx/libkernel/Time/include/Time.hpp"
#include "prx/libkernel/Time/include/TimedWait.hpp"
#include "prx/libc/include/General.hpp"
#include <algorithm>
@ -6,6 +7,7 @@
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <functional>
#include <mutex>
#include <unordered_map>
#include <utility>
@ -23,14 +25,7 @@
static std::uint64_t RawMonotonicNanos() {
#ifdef _WIN32
static const std::uint64_t freq = [] {
LARGE_INTEGER f{};
QueryPerformanceFrequency(&f);
return static_cast<std::uint64_t>(f.QuadPart);
}();
LARGE_INTEGER counter{};
QueryPerformanceCounter(&counter);
return static_cast<std::uint64_t>(counter.QuadPart) * 1000000000ULL / freq;
return TimedWait::NowNanos();
#else
struct timespec ts{};
clock_gettime(CLOCK_MONOTONIC, &ts);
@ -85,41 +80,64 @@ static const bool g_timerResolutionRaised = [] {
throttling.ControlMask = PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION;
throttling.StateMask = 0;
SetProcessInformation(GetCurrentProcess(), ProcessPowerThrottling, &throttling, sizeof(throttling));
std::fprintf(stderr, "[time] scheduler tick %s (was %.2f ms)\n", raised ? "raised to 0.5 ms" : "unchanged", previous / 10000.0);
using NtQueryTimerResolutionFn = LONG(WINAPI*)(PULONG, PULONG, PULONG);
const auto queryResolution = ntdll ? reinterpret_cast<NtQueryTimerResolutionFn>(reinterpret_cast<void*>(GetProcAddress(ntdll, "NtQueryTimerResolution"))) : nullptr;
ULONG minimum = 0, maximum = 0, current = 0;
if (queryResolution) queryResolution(&minimum, &maximum, &current);
std::fprintf(stderr, "[time] scheduler tick %s (was %.2f ms, now %.2f ms, timed waits %s)\n", raised ? "raised to 0.5 ms" : "unchanged", previous / 10000.0, current / 10000.0, TimedWait::Coarse() ? "coarse" : "fine");
return raised;
}();
#endif
static void SleepNanos(std::uint64_t nanos) {
if (nanos == 0) {
return;
}
#ifdef _WIN32
// Guest spin/backoff loops sleep for a few microseconds; Sleep() would either not sleep at all
// or round up to a whole scheduler tick, so short sleeps yield and longer ones use a
// high-resolution waitable timer.
if (nanos < 50000ULL) {
SwitchToThread();
return;
}
thread_local HANDLE timer = CreateWaitableTimerExW(nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
if (timer) {
LARGE_INTEGER due{};
due.QuadPart = -static_cast<LONGLONG>(nanos / 100ULL);
if (SetWaitableTimer(timer, &due, 0, nullptr, nullptr, FALSE)) {
WaitForSingleObject(timer, INFINITE);
return;
}
}
Sleep(static_cast<DWORD>((nanos + 999999ULL) / 1000000ULL));
#else
struct timespec req{};
req.tv_sec = static_cast<time_t>(nanos / 1000000000ULL);
req.tv_nsec = static_cast<long>(nanos % 1000000000ULL);
while (nanosleep(&req, &req) == -1 && errno == EINTR) {}
#endif
extern "C" {
int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char* name);
int APS5_VABI scePthreadCondDestroy(PthreadCond* cond);
int APS5_VABI scePthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, unsigned int usec);
int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name);
int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex);
int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex);
int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex);
}
static void ReportTimedWait(const char* name, const std::function<void()>& body) {
std::vector<double> samples(200);
for (auto& sample : samples) {
const std::uint64_t start = TimedWait::NowNanos();
body();
sample = static_cast<double>(TimedWait::NowNanos() - start) / 1000.0;
}
std::sort(samples.begin(), samples.end());
double sum = 0.0;
for (const double sample : samples) sum += sample;
std::fprintf(stderr, "[timers] %-44s mean %8.1f p50 %8.1f max %8.1f us\n", name, sum / static_cast<double>(samples.size()), samples[samples.size() / 2], samples.back());
}
static const bool g_timerSelfTest = [] {
if (std::getenv("APS5_TRACE_TIMERS") == nullptr) return false;
std::mutex mutex;
std::condition_variable cv;
ReportTimedWait("std::condition_variable::wait_for(1 ms)", [&] { std::unique_lock<std::mutex> lock(mutex); cv.wait_for(lock, std::chrono::milliseconds(1)); });
PthreadCond cond = nullptr;
PthreadMutex guestMutex = nullptr;
scePthreadCondInit(&cond, nullptr, nullptr);
scePthreadMutexInit(&guestMutex, nullptr, nullptr);
scePthreadMutexLock(&guestMutex);
ReportTimedWait("scePthreadCondTimedwait(1000 us)", [&] { scePthreadCondTimedwait(&cond, &guestMutex, 1000); });
ReportTimedWait("scePthreadCondTimedwait(13000 us)", [&] { scePthreadCondTimedwait(&cond, &guestMutex, 13000); });
scePthreadMutexUnlock(&guestMutex);
scePthreadMutexDestroy(&guestMutex);
scePthreadCondDestroy(&cond);
ReportTimedWait("sceKernelUsleep(1000)", [&] { sceKernelUsleep_nid_postfix(1000); });
ReportTimedWait("Sleep(1)", [&] { Sleep(1); });
HANDLE event = CreateEventW(nullptr, TRUE, FALSE, nullptr);
ReportTimedWait("WaitForSingleObject(event, 1)", [&] { WaitForSingleObject(event, 1); });
CloseHandle(event);
HANDLE timer = CreateWaitableTimerExW(nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
ReportTimedWait("high-resolution waitable timer 1 ms", [&] { LARGE_INTEGER due{}; due.QuadPart = -10000; SetWaitableTimer(timer, &due, 0, nullptr, nullptr, FALSE); WaitForSingleObject(timer, INFINITE); });
CloseHandle(timer);
return true;
}();
#endif
extern "C" {
std::uint64_t APS5_VABI sceKernelGetProcessTime() {
@ -199,7 +217,7 @@ void KernelTraceWait_nid_postfix(const char* kind, const void* caller, std::uint
int APS5_VABI sceKernelUsleep_nid_postfix(KernelUseconds microseconds) {
TraceSleep(__builtin_return_address(0), microseconds);
SleepNanos(static_cast<std::uint64_t>(microseconds) * 1000ULL);
TimedWait::SleepNanos(static_cast<std::uint64_t>(microseconds) * 1000ULL);
return 0;
}
@ -210,8 +228,8 @@ int APS5_VABI sceKernelNanosleep(const KernelTimespec* rqtp, KernelTimespec* rmt
if (rqtp->tv_sec < 0 || rqtp->tv_nsec < 0 || rqtp->tv_nsec >= 1000000000LL) {
return -1;
}
SleepNanos(static_cast<std::uint64_t>(rqtp->tv_sec) * 1000000000ULL +
static_cast<std::uint64_t>(rqtp->tv_nsec));
TimedWait::SleepNanos(static_cast<std::uint64_t>(rqtp->tv_sec) * 1000000000ULL +
static_cast<std::uint64_t>(rqtp->tv_nsec));
if (rmtp != nullptr) {
rmtp->tv_sec = 0;
rmtp->tv_nsec = 0;
@ -421,7 +439,7 @@ uint64_t APS5_VABI sceKernelGetTscFrequency(void) {
static const std::uint64_t frequency = [] {
const std::uint64_t startNanos = RawMonotonicNanos();
const std::uint64_t startTicks = __rdtsc();
SleepNanos(20000000ULL);
TimedWait::SleepNanos(20000000ULL);
const std::uint64_t elapsedNanos = RawMonotonicNanos() - startNanos;
const std::uint64_t elapsedTicks = __rdtsc() - startTicks;
return static_cast<std::uint64_t>(static_cast<long double>(elapsedTicks) * 1000000000.0L / static_cast<long double>(elapsedNanos));

View File

@ -0,0 +1,280 @@
#include "prx/libkernel/Time/include/TimedWait.hpp"
#include <cstdlib>
#include <limits>
#ifdef _WIN32
#include <windows.h>
#else
#include <cerrno>
#include <time.h>
#endif
namespace TimedWait {
namespace {
constexpr std::uint64_t YIELD_BELOW_NANOS = 50000ULL;
constexpr std::uint64_t LEAD_NANOS = 500000ULL;
#ifdef _WIN32
void WINAPI DestroyWaiter(void* value);
DWORD WaiterSlotIndex() {
static const DWORD index = FlsAlloc(DestroyWaiter);
return index;
}
HANDLE CreateHighResolutionTimer() {
return CreateWaitableTimerExW(nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
}
bool TimerWait(HANDLE timer, std::uint64_t nanos) {
if (!timer) return false;
LARGE_INTEGER due{};
due.QuadPart = -static_cast<LONGLONG>(nanos / 100ULL);
if (due.QuadPart == 0) due.QuadPart = -1;
if (!SetWaitableTimer(timer, &due, 0, nullptr, nullptr, FALSE)) return false;
return WaitForSingleObject(timer, INFINITE) == WAIT_OBJECT_0;
}
DWORD WholeMilliseconds(std::uint64_t nanos) {
const std::uint64_t millis = nanos / 1000000ULL;
return millis == 0 ? 1u : static_cast<DWORD>(millis > 0xFFFFFFFEULL ? 0xFFFFFFFEULL : millis);
}
void SleepNanosCoarse(std::uint64_t nanos) {
if (nanos < YIELD_BELOW_NANOS) {
SwitchToThread();
return;
}
thread_local HANDLE timer = CreateHighResolutionTimer();
if (TimerWait(timer, nanos)) return;
Sleep(static_cast<DWORD>((nanos + 999999ULL) / 1000000ULL));
}
#endif
} // namespace
#ifdef _WIN32
struct Waiter {
HANDLE event = CreateEventW(nullptr, FALSE, FALSE, nullptr);
HANDLE timer = CreateHighResolutionTimer();
Waiter* previous = nullptr;
Waiter* next = nullptr;
bool queued = false;
~Waiter() {
if (event) CloseHandle(event);
if (timer) CloseHandle(timer);
}
};
namespace {
void WINAPI DestroyWaiter(void* value) {
delete static_cast<Waiter*>(value);
}
Waiter* ThisThreadWaiter() {
thread_local Waiter* waiter = nullptr;
if (!waiter) {
waiter = new Waiter();
const DWORD slot = WaiterSlotIndex();
if (slot != FLS_OUT_OF_INDEXES) FlsSetValue(slot, waiter);
}
return waiter;
}
bool EventSet(HANDLE event, DWORD milliseconds) {
return WaitForSingleObject(event, milliseconds) == WAIT_OBJECT_0;
}
bool WaitEventUntil(Waiter* waiter, std::uint64_t deadlineNanos) {
for (;;) {
const std::uint64_t now = NowNanos();
if (now >= deadlineNanos) return EventSet(waiter->event, 0);
const std::uint64_t remaining = deadlineNanos - now;
if (remaining > LEAD_NANOS) {
const std::uint64_t bulk = remaining - LEAD_NANOS;
LARGE_INTEGER due{};
due.QuadPart = -static_cast<LONGLONG>(bulk / 100ULL);
if (waiter->timer && SetWaitableTimer(waiter->timer, &due, 0, nullptr, nullptr, FALSE)) {
HANDLE handles[2] = {waiter->event, waiter->timer};
if (WaitForMultipleObjects(2, handles, FALSE, INFINITE) == WAIT_OBJECT_0) return true;
continue;
}
if (EventSet(waiter->event, WholeMilliseconds(bulk))) return true;
continue;
}
if (EventSet(waiter->event, 0)) return true;
YieldProcessor();
}
}
} // namespace
Waiter* Condition::enqueue() {
Waiter* waiter = ThisThreadWaiter();
std::lock_guard lock(queueLock);
waiter->queued = true;
waiter->next = nullptr;
waiter->previous = tail;
if (tail) tail->next = waiter;
else head = waiter;
tail = waiter;
return waiter;
}
void Condition::unlink(Waiter* waiter) {
if (waiter->previous) waiter->previous->next = waiter->next;
else head = waiter->next;
if (waiter->next) waiter->next->previous = waiter->previous;
else tail = waiter->previous;
waiter->previous = nullptr;
waiter->next = nullptr;
waiter->queued = false;
}
void Condition::waitSignal(Waiter* waiter) {
WaitForSingleObject(waiter->event, INFINITE);
}
bool Condition::waitSignalUntil(Waiter* waiter, std::uint64_t deadlineNanos) {
if (WaitEventUntil(waiter, deadlineNanos)) return true;
std::lock_guard lock(queueLock);
if (waiter->queued) {
unlink(waiter);
return false;
}
EventSet(waiter->event, 0);
return true;
}
void Condition::NotifyOne() {
if (Coarse()) {
coarse.notify_one();
return;
}
std::lock_guard lock(queueLock);
Waiter* waiter = head;
if (!waiter) return;
unlink(waiter);
SetEvent(waiter->event);
}
void Condition::NotifyAll() {
if (Coarse()) {
coarse.notify_all();
return;
}
std::lock_guard lock(queueLock);
while (Waiter* waiter = head) {
unlink(waiter);
SetEvent(waiter->event);
}
}
#else
void Condition::NotifyOne() {
coarse.notify_one();
}
void Condition::NotifyAll() {
coarse.notify_all();
}
#endif
bool Coarse() {
#ifdef _WIN32
static const bool coarse = std::getenv("APS5_COARSE_TIMED_WAITS") != nullptr;
return coarse;
#else
return true;
#endif
}
std::uint64_t NowNanos() {
#ifdef _WIN32
static const std::uint64_t frequency = [] {
LARGE_INTEGER f{};
QueryPerformanceFrequency(&f);
return static_cast<std::uint64_t>(f.QuadPart);
}();
LARGE_INTEGER counter{};
QueryPerformanceCounter(&counter);
const std::uint64_t ticks = static_cast<std::uint64_t>(counter.QuadPart);
return (ticks / frequency) * 1000000000ULL + (ticks % frequency) * 1000000000ULL / frequency;
#else
struct timespec ts{};
clock_gettime(CLOCK_MONOTONIC, &ts);
return static_cast<std::uint64_t>(ts.tv_sec) * 1000000000ULL + static_cast<std::uint64_t>(ts.tv_nsec);
#endif
}
std::uint64_t DeadlineNanos(std::uint64_t microseconds) {
const std::uint64_t now = NowNanos();
if (microseconds > (std::numeric_limits<std::uint64_t>::max() - now) / 1000ULL) return std::numeric_limits<std::uint64_t>::max();
return now + microseconds * 1000ULL;
}
std::uint64_t RemainingMicros(std::uint64_t deadlineNanos) {
const std::uint64_t now = NowNanos();
return deadlineNanos > now ? (deadlineNanos - now) / 1000ULL : 0;
}
void SleepUntil(std::uint64_t deadlineNanos) {
#ifdef _WIN32
std::uint64_t now = NowNanos();
if (deadlineNanos > now + LEAD_NANOS) {
thread_local HANDLE timer = CreateHighResolutionTimer();
if (!TimerWait(timer, deadlineNanos - now - LEAD_NANOS)) Sleep(WholeMilliseconds(deadlineNanos - now - LEAD_NANOS));
now = NowNanos();
}
while (now < deadlineNanos) {
YieldProcessor();
now = NowNanos();
}
#else
const std::uint64_t now = NowNanos();
if (deadlineNanos > now) SleepNanos(deadlineNanos - now);
#endif
}
void PollSleepUntil(std::uint64_t deadlineNanos) {
#ifdef _WIN32
const std::uint64_t now = NowNanos();
if (deadlineNanos <= now) return;
thread_local HANDLE timer = CreateHighResolutionTimer();
if (!TimerWait(timer, deadlineNanos - now)) Sleep(WholeMilliseconds(deadlineNanos - now));
#else
SleepUntil(deadlineNanos);
#endif
}
void SleepNanos(std::uint64_t nanos) {
if (nanos == 0) return;
#ifdef _WIN32
if (Coarse()) {
SleepNanosCoarse(nanos);
return;
}
if (nanos < YIELD_BELOW_NANOS) {
SwitchToThread();
return;
}
SleepUntil(NowNanos() + nanos);
#else
struct timespec req{};
req.tv_sec = static_cast<time_t>(nanos / 1000000000ULL);
req.tv_nsec = static_cast<long>(nanos % 1000000000ULL);
while (nanosleep(&req, &req) == -1 && errno == EINTR) {}
#endif
}
} // namespace TimedWait

View File

@ -0,0 +1,109 @@
#ifndef CORE_LIBS_PRX_LIBKERNEL_TIME_INCLUDE_TIMEDWAIT_HPP
#define CORE_LIBS_PRX_LIBKERNEL_TIME_INCLUDE_TIMEDWAIT_HPP
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <mutex>
namespace TimedWait {
bool Coarse();
std::uint64_t NowNanos();
std::uint64_t DeadlineNanos(std::uint64_t microseconds);
std::uint64_t RemainingMicros(std::uint64_t deadlineNanos);
void SleepNanos(std::uint64_t nanos);
void SleepUntil(std::uint64_t deadlineNanos);
void PollSleepUntil(std::uint64_t deadlineNanos);
constexpr std::uint64_t LOCK_BULK_NANOS = 40000000ULL;
constexpr std::uint64_t LOCK_POLL_NANOS = 500000ULL;
constexpr std::uint64_t LOCK_TAIL_NANOS = 1000000ULL;
template <class TTryLock, class TTryLockFor>
bool AcquireUntil(std::uint64_t deadlineNanos, TTryLock tryLock, TTryLockFor tryLockFor) {
if (Coarse()) return tryLockFor(RemainingMicros(deadlineNanos));
for (;;) {
if (tryLock()) return true;
const std::uint64_t now = NowNanos();
if (now >= deadlineNanos) return false;
const std::uint64_t remaining = deadlineNanos - now;
if (remaining > LOCK_BULK_NANOS) {
if (tryLockFor((remaining - LOCK_BULK_NANOS) / 1000ULL)) return true;
continue;
}
if (remaining <= LOCK_TAIL_NANOS) SleepUntil(deadlineNanos);
else PollSleepUntil(now + LOCK_POLL_NANOS);
}
}
struct Waiter;
class Condition {
public:
Condition() = default;
~Condition() = default;
Condition(const Condition&) = delete;
Condition& operator=(const Condition&) = delete;
void NotifyOne();
void NotifyAll();
template <class TLock>
void Wait(TLock& lock) {
#ifdef _WIN32
if (!Coarse()) {
Waiter* waiter = enqueue();
lock.unlock();
waitSignal(waiter);
lock.lock();
return;
}
#endif
coarse.wait(lock);
}
template <class TLock>
bool WaitUntil(TLock& lock, std::uint64_t deadlineNanos) {
#ifdef _WIN32
if (!Coarse()) {
Waiter* waiter = enqueue();
lock.unlock();
const bool signaled = waitSignalUntil(waiter, deadlineNanos);
lock.lock();
return signaled;
}
#endif
return coarse.wait_for(lock, std::chrono::microseconds(RemainingMicros(deadlineNanos))) == std::cv_status::no_timeout;
}
template <class TLock, class TPredicate>
void Wait(TLock& lock, TPredicate predicate) {
while (!predicate()) Wait(lock);
}
template <class TLock, class TPredicate>
bool WaitUntil(TLock& lock, std::uint64_t deadlineNanos, TPredicate predicate) {
while (!predicate()) {
if (!WaitUntil(lock, deadlineNanos)) return predicate();
}
return true;
}
private:
#ifdef _WIN32
Waiter* enqueue();
void unlink(Waiter* waiter);
void waitSignal(Waiter* waiter);
bool waitSignalUntil(Waiter* waiter, std::uint64_t deadlineNanos);
std::mutex queueLock;
Waiter* head = nullptr;
Waiter* tail = nullptr;
#endif
std::condition_variable_any coarse;
};
} // namespace TimedWait
#endif

View File

@ -87,8 +87,8 @@ int main(int argc, char**) {
write(replacement, 0x18, &finalize);
write(replacement, 0x20, &allocate);
write(replacement, 0x28, &release);
write(replacement, 0x30, &reallocate);
write(replacement, 0x38, &allocateZeroed);
write(replacement, 0x30, &allocateZeroed);
write(replacement, 0x38, &reallocate);
write(replacement, 0x40, &align);
write(replacement, 0x48, &reallocate);
write(replacement, 0x50, &posixAlign);

View File

@ -0,0 +1,130 @@
#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>
#include <cstdlib>
#include <cstring>
extern "C" {
int APS5_VABI sceRtcCheckValid(const RtcDateTime*);
int APS5_VABI sceRtcIsLeapYear(int);
int APS5_VABI sceRtcGetDaysInMonth(int, int);
int APS5_VABI sceRtcGetDayOfWeek(int, int, int);
int APS5_VABI sceRtcGetTickResolution(void);
int APS5_VABI sceRtcGetTick(const RtcDateTime*, RtcTick*);
int APS5_VABI sceRtcSetTick(RtcDateTime*, const RtcTick*);
int APS5_VABI sceRtcGetCurrentTick(RtcTick*);
int APS5_VABI sceRtcConvertUtcToLocalTime(const RtcTick*, RtcTick*);
int APS5_VABI sceRtcConvertLocalTimeToUtc(const RtcTick*, RtcTick*);
int APS5_VABI sceRtcGetTime_t(const RtcDateTime*, std::int64_t*);
int APS5_VABI sceRtcSetTime_t(RtcDateTime*, std::int64_t);
int APS5_VABI sceRtcGetWin32FileTime(const RtcDateTime*, std::uint64_t*);
int APS5_VABI sceRtcSetWin32FileTime(RtcDateTime*, std::uint64_t);
int APS5_VABI sceRtcFormatRFC3339(char*, const RtcTick*, int);
int APS5_VABI sceRtcParseRFC3339(RtcTick*, const char*);
int APS5_VABI sceRtcTickAddTicks(RtcTick*, const RtcTick*, std::int64_t);
int APS5_VABI sceRtcTickAddSeconds(RtcTick*, const RtcTick*, std::int64_t);
int APS5_VABI sceRtcTickAddDays(RtcTick*, const RtcTick*, std::int32_t);
int APS5_VABI sceRtcTickAddMonths(RtcTick*, const RtcTick*, std::int32_t);
int APS5_VABI sceRtcTickAddYears(RtcTick*, const RtcTick*, std::int16_t);
}
static void Require(bool value) { if (!value) std::abort(); }
static bool Equal(const RtcDateTime& left, const RtcDateTime& right) {
return left.year == right.year && left.month == right.month && left.day == right.day && left.hour == right.hour
&& left.minute == right.minute && left.second == right.second && left.microsecond == right.microsecond;
}
int main() {
constexpr int invalidPointer = static_cast<int>(0x80B50002);
constexpr int invalidValue = static_cast<int>(0x80B50003);
constexpr int badParse = static_cast<int>(0x80B50007);
constexpr int invalidYear = static_cast<int>(0x80B50008);
constexpr int invalidMonth = static_cast<int>(0x80B50009);
constexpr int invalidDay = static_cast<int>(0x80B5000A);
constexpr int invalidHour = static_cast<int>(0x80B5000B);
constexpr int invalidMicrosecond = static_cast<int>(0x80B5000E);
constexpr std::uint64_t unixEpochTick = 62135596800000000ull;
constexpr std::uint64_t leapDayTick = 63844806896789000ull;
constexpr std::uint64_t maxTick = 315537897599999999ull;
Require(sceRtcGetTickResolution() == 1000000);
Require(sceRtcIsLeapYear(2000) == 1 && sceRtcIsLeapYear(1900) == 0 && sceRtcIsLeapYear(2024) == 1);
Require(sceRtcIsLeapYear(0) == invalidYear);
Require(sceRtcGetDaysInMonth(2023, 2) == 28 && sceRtcGetDaysInMonth(2024, 2) == 29 && sceRtcGetDaysInMonth(2024, 4) == 30);
Require(sceRtcGetDaysInMonth(2024, 13) == invalidMonth);
Require(sceRtcGetDayOfWeek(1, 1, 1) == 1 && sceRtcGetDayOfWeek(2026, 9, 26) == 6);
Require(sceRtcGetDayOfWeek(2023, 2, 29) == invalidDay);
RtcDateTime leapDay{2024, 2, 29, 12, 34, 56, 789000};
Require(sceRtcCheckValid(&leapDay) == 0);
Require(sceRtcCheckValid(nullptr) == invalidPointer);
RtcDateTime invalid = leapDay;
invalid.year = 2023;
Require(sceRtcCheckValid(&invalid) == invalidDay);
invalid = leapDay;
invalid.hour = 24;
Require(sceRtcCheckValid(&invalid) == invalidHour);
invalid = leapDay;
invalid.microsecond = 1000000;
Require(sceRtcCheckValid(&invalid) == invalidMicrosecond);
RtcTick tick{};
Require(sceRtcGetTick(&leapDay, &tick) == 0 && tick.tick == leapDayTick);
RtcDateTime converted{};
Require(sceRtcSetTick(&converted, &tick) == 0 && Equal(converted, leapDay));
tick.tick = maxTick;
Require(sceRtcSetTick(&converted, &tick) == 0 && Equal(converted, RtcDateTime{9999, 12, 31, 23, 59, 59, 999999}));
tick.tick = maxTick + 1;
Require(sceRtcSetTick(&converted, &tick) == invalidValue);
RtcDateTime epoch{1970, 1, 1, 0, 0, 0, 0};
std::int64_t seconds = -1;
Require(sceRtcGetTime_t(&epoch, &seconds) == 0 && seconds == 0);
RtcDateTime millennium{2000, 1, 1, 0, 0, 0, 0};
Require(sceRtcGetTime_t(&millennium, &seconds) == 0 && seconds == 946684800);
Require(sceRtcSetTime_t(&converted, 946684800) == 0 && Equal(converted, millennium));
Require(sceRtcSetTime_t(&converted, -1) == invalidValue);
std::uint64_t fileTime = 0;
Require(sceRtcGetWin32FileTime(&epoch, &fileTime) == 0 && fileTime == 116444736000000000ull);
Require(sceRtcSetWin32FileTime(&converted, 116444736000000000ull) == 0 && Equal(converted, epoch));
char text[32];
tick.tick = leapDayTick;
Require(sceRtcFormatRFC3339(text, &tick, 0) == 0 && std::strcmp(text, "2024-02-29T12:34:56.78Z") == 0);
Require(sceRtcFormatRFC3339(text, &tick, 90) == 0 && std::strcmp(text, "2024-02-29T14:04:56.78+01:30") == 0);
Require(sceRtcFormatRFC3339(text, &tick, -300) == 0 && std::strcmp(text, "2024-02-29T07:34:56.78-05:00") == 0);
Require(sceRtcParseRFC3339(&tick, "2024-02-29T14:04:56.789+01:30") == 0 && tick.tick == leapDayTick);
Require(sceRtcParseRFC3339(&tick, "2024-02-29t12:34:56.789z") == 0 && tick.tick == leapDayTick);
Require(sceRtcParseRFC3339(&tick, "1970-01-01T00:00:00Z") == 0 && tick.tick == unixEpochTick);
Require(sceRtcParseRFC3339(&tick, "2023-02-29T00:00:00Z") == invalidDay);
Require(sceRtcParseRFC3339(&tick, "2024-02-29T12:34:56") == badParse);
Require(sceRtcParseRFC3339(&tick, "2024-02-29T12:34:56Zjunk") == badParse);
Require(sceRtcParseRFC3339(nullptr, "1970-01-01T00:00:00Z") == invalidPointer);
RtcTick source{leapDayTick};
RtcTick result{};
Require(sceRtcTickAddSeconds(&result, &source, 3600) == 0 && result.tick == leapDayTick + 3600000000ull);
Require(sceRtcTickAddDays(&result, &source, 1) == 0);
Require(sceRtcSetTick(&converted, &result) == 0 && converted.month == 3 && converted.day == 1);
Require(sceRtcTickAddYears(&result, &source, 1) == 0);
Require(sceRtcSetTick(&converted, &result) == 0 && converted.year == 2025 && converted.month == 2 && converted.day == 28);
RtcDateTime endOfJanuary{2024, 1, 31, 8, 0, 0, 0};
Require(sceRtcGetTick(&endOfJanuary, &source) == 0);
Require(sceRtcTickAddMonths(&result, &source, 1) == 0);
Require(sceRtcSetTick(&converted, &result) == 0 && Equal(converted, RtcDateTime{2024, 2, 29, 8, 0, 0, 0}));
Require(sceRtcTickAddMonths(&result, &source, -12 * 2024) == invalidValue);
source.tick = 5;
Require(sceRtcTickAddTicks(&result, &source, -6) == invalidValue);
Require(sceRtcTickAddTicks(&result, &source, -5) == 0 && result.tick == 0);
source.tick = maxTick;
Require(sceRtcTickAddTicks(&result, &source, 1) == invalidValue);
Require(sceRtcTickAddTicks(nullptr, &source, 1) == invalidPointer);
RtcTick now{};
Require(sceRtcGetCurrentTick(&now) == 0 && now.tick > leapDayTick);
RtcTick local{};
RtcTick back{};
Require(sceRtcConvertUtcToLocalTime(&now, &local) == 0);
Require(sceRtcConvertLocalTimeToUtc(&local, &back) == 0 && back.tick == now.tick);
}