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:
parent
d677f970b2
commit
af3c2024ed
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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, ¤t);
|
||||
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));
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
Loading…
Reference in New Issue