Merge pull request #5037 from Feuris/feat/libc-cnd-timedwait
feat(libc): implement _Cnd_timedwait
This commit is contained in:
commit
c7632f036f
|
|
@ -639,6 +639,18 @@ if(BUILD_TESTING)
|
|||
endif()
|
||||
endif()
|
||||
|
||||
add_test_executable(guest_libc_cnd_timed_wait_tests EXCLUDE_FROM_ALL ${LIBS_INCLUDE_DIR}/tests/GuestLibcCndTimedWait.cpp)
|
||||
target_include_directories(guest_libc_cnd_timed_wait_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_libc_cnd_timed_wait_tests PRIVATE libc)
|
||||
configure_windows_unwind(guest_libc_cnd_timed_wait_tests)
|
||||
if(BUILD_TESTING)
|
||||
set_target_properties(guest_libc_cnd_timed_wait_tests PROPERTIES EXCLUDE_FROM_ALL FALSE)
|
||||
add_test(NAME guest_libc_cnd_timed_wait COMMAND guest_libc_cnd_timed_wait_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_libc_cnd_timed_wait PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(MINGW)
|
||||
if(BUILD_TESTING)
|
||||
enable_language(ASM)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
|
@ -24,18 +25,18 @@ struct Xtime {
|
|||
constexpr std::int64_t nanosPerSecond = 1000000000;
|
||||
constexpr std::int64_t longestSleepSeconds = 86400;
|
||||
|
||||
timespec XtimeDeadline(const Xtime& time) {
|
||||
timespec XtimeDeadline(const Xtime& time, const char* function) {
|
||||
constexpr auto largest = std::numeric_limits<std::int64_t>::max();
|
||||
constexpr auto smallest = std::numeric_limits<std::int64_t>::min();
|
||||
const auto carry = time.nsec / nanosPerSecond;
|
||||
auto nanos = time.nsec % nanosPerSecond;
|
||||
auto seconds = time.sec;
|
||||
if ((carry > 0 && seconds > largest - carry) || (carry < 0 && seconds < smallest - carry))
|
||||
throw std::overflow_error("_Thrd_sleep: xtime seconds overflow");
|
||||
throw std::overflow_error(std::string(function) + ": xtime seconds overflow");
|
||||
seconds += carry;
|
||||
if (nanos < 0) {
|
||||
if (seconds == smallest)
|
||||
throw std::overflow_error("_Thrd_sleep: xtime seconds overflow");
|
||||
throw std::overflow_error(std::string(function) + ": xtime seconds overflow");
|
||||
nanos += nanosPerSecond;
|
||||
--seconds;
|
||||
}
|
||||
|
|
@ -44,6 +45,7 @@ timespec XtimeDeadline(const Xtime& time) {
|
|||
|
||||
constexpr int threadSuccess = 0;
|
||||
constexpr int threadNomem = 1;
|
||||
constexpr int threadTimedout = 2;
|
||||
constexpr int threadBusy = 3;
|
||||
constexpr int threadError = 4;
|
||||
|
||||
|
|
@ -220,6 +222,44 @@ int APS5_VABI _Cnd_wait_nid_postfix(void** conditionHandle, void** mutexHandle)
|
|||
return threadSuccess;
|
||||
}
|
||||
|
||||
int APS5_VABI _Cnd_timedwait_nid_postfix(void** conditionHandle, void** mutexHandle, const Xtime* target) {
|
||||
if (!target)
|
||||
APS5_INVALID_ARG_EX;
|
||||
const auto deadline = XtimeDeadline(*target, "_Cnd_timedwait");
|
||||
constexpr auto maxDuration = std::chrono::system_clock::duration::max();
|
||||
constexpr auto minDuration = std::chrono::system_clock::duration::min();
|
||||
constexpr auto maxSeconds = std::chrono::duration_cast<std::chrono::seconds>(maxDuration).count();
|
||||
constexpr auto minSeconds = std::chrono::duration_cast<std::chrono::seconds>(minDuration).count();
|
||||
constexpr auto maxNanos = std::chrono::duration_cast<std::chrono::nanoseconds>(maxDuration - std::chrono::seconds(maxSeconds)).count();
|
||||
if (deadline.tv_sec > maxSeconds || (deadline.tv_sec == maxSeconds && deadline.tv_nsec > maxNanos) ||
|
||||
deadline.tv_sec < minSeconds)
|
||||
throw std::overflow_error("_Cnd_timedwait: xtime seconds overflow");
|
||||
auto* condition = ResolveCondition(conditionHandle, "_Cnd_timedwait");
|
||||
auto* mutex = ResolveMutex(mutexHandle, "_Cnd_timedwait");
|
||||
if (!IsOwnedByCaller(mutex))
|
||||
return threadSuccess;
|
||||
if (mutex->count != 1)
|
||||
return threadSuccess;
|
||||
mutex->count = 0;
|
||||
mutex->owner.store(std::thread::id{}, std::memory_order_release);
|
||||
std::unique_lock lock(mutex->native, std::adopt_lock);
|
||||
bool timeout = false;
|
||||
try {
|
||||
const auto deadlineTime = std::chrono::system_clock::time_point(
|
||||
std::chrono::seconds(deadline.tv_sec) + std::chrono::nanoseconds(deadline.tv_nsec));
|
||||
timeout = (condition->native.wait_until(lock, deadlineTime) == std::cv_status::timeout);
|
||||
} catch (const std::system_error&) {
|
||||
lock.release();
|
||||
mutex->owner.store(std::this_thread::get_id(), std::memory_order_release);
|
||||
mutex->count = 1;
|
||||
throw;
|
||||
}
|
||||
lock.release();
|
||||
mutex->owner.store(std::this_thread::get_id(), std::memory_order_release);
|
||||
mutex->count = 1;
|
||||
return timeout ? threadTimedout : threadSuccess;
|
||||
}
|
||||
|
||||
int APS5_VABI _Cnd_broadcast_nid_postfix(void** handle) {
|
||||
ResolveCondition(handle, "_Cnd_broadcast")->native.notify_all();
|
||||
return threadSuccess;
|
||||
|
|
@ -233,7 +273,7 @@ int SignalCondition_nid_no_patch(void** handle) {
|
|||
int APS5_VABI _Thrd_sleep_nid_postfix(const Xtime* target, Xtime*) {
|
||||
if (!target)
|
||||
APS5_INVALID_ARG_EX;
|
||||
const auto deadline = XtimeDeadline(*target);
|
||||
const auto deadline = XtimeDeadline(*target, "_Thrd_sleep");
|
||||
for (;;) {
|
||||
timespec now{};
|
||||
if (clock_gettime(CLOCK_REALTIME, &now) != 0)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
|
||||
struct Xtime {
|
||||
std::int64_t sec;
|
||||
std::int64_t nsec;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
int APS5_VABI _Mtx_init_nid_postfix(void** handle, int type);
|
||||
void APS5_VABI _Mtx_destroy_nid_postfix(void** handle);
|
||||
int APS5_VABI _Mtx_lock_nid_postfix(void** handle);
|
||||
int APS5_VABI _Mtx_unlock_nid_postfix(void** handle);
|
||||
int APS5_VABI _Cnd_init_nid_postfix(void** handle);
|
||||
void APS5_VABI _Cnd_destroy_nid_postfix(void** handle);
|
||||
int APS5_VABI _Cnd_broadcast_nid_postfix(void** handle);
|
||||
int APS5_VABI _Cnd_timedwait_nid_postfix(void** condition, void** mutex, const Xtime* target);
|
||||
}
|
||||
|
||||
static void Require(bool value) { if (!value) std::abort(); }
|
||||
|
||||
template<typename TException, typename TCall>
|
||||
static void RequireThrows(TCall call) {
|
||||
try {
|
||||
call();
|
||||
} catch (const TException&) {
|
||||
return;
|
||||
}
|
||||
std::abort();
|
||||
}
|
||||
|
||||
constexpr int success = 0;
|
||||
constexpr int timedout = 2;
|
||||
constexpr int plain = 0x01;
|
||||
|
||||
int main() {
|
||||
void* mutex = nullptr;
|
||||
void* condition = nullptr;
|
||||
Require(_Mtx_init_nid_postfix(&mutex, plain) == success);
|
||||
Require(_Cnd_init_nid_postfix(&condition) == success);
|
||||
|
||||
Require(_Mtx_lock_nid_postfix(&mutex) == success);
|
||||
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
const auto nowSec = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
|
||||
const auto nowNsec = std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count() % 1000000000;
|
||||
|
||||
Xtime pastTime{nowSec - 1, nowNsec};
|
||||
Require(_Cnd_timedwait_nid_postfix(&condition, &mutex, &pastTime) == timedout);
|
||||
|
||||
Xtime unnormalizedPastTime{nowSec - 2, nowNsec + 1000000000};
|
||||
Require(_Cnd_timedwait_nid_postfix(&condition, &mutex, &unnormalizedPastTime) == timedout);
|
||||
|
||||
Xtime negativePastTime{nowSec, nowNsec - 2000000000};
|
||||
Require(_Cnd_timedwait_nid_postfix(&condition, &mutex, &negativePastTime) == timedout);
|
||||
|
||||
const auto timeoutDeadline = std::chrono::system_clock::now() + std::chrono::milliseconds(20);
|
||||
const auto timeoutSec = std::chrono::duration_cast<std::chrono::seconds>(timeoutDeadline.time_since_epoch()).count();
|
||||
const auto timeoutNsec = std::chrono::duration_cast<std::chrono::nanoseconds>(timeoutDeadline.time_since_epoch()).count() % 1000000000;
|
||||
const Xtime timeoutTime{timeoutSec, timeoutNsec};
|
||||
while (true) {
|
||||
const int result = _Cnd_timedwait_nid_postfix(&condition, &mutex, &timeoutTime);
|
||||
if (result == timedout) {
|
||||
Require(std::chrono::system_clock::now() >= timeoutDeadline);
|
||||
break;
|
||||
}
|
||||
Require(result == success);
|
||||
}
|
||||
|
||||
const auto unnormalizedDeadline = std::chrono::system_clock::now() + std::chrono::milliseconds(20);
|
||||
const auto unnormalizedSec = std::chrono::duration_cast<std::chrono::seconds>(unnormalizedDeadline.time_since_epoch()).count();
|
||||
const auto unnormalizedNsec = std::chrono::duration_cast<std::chrono::nanoseconds>(unnormalizedDeadline.time_since_epoch()).count() % 1000000000;
|
||||
const Xtime unnormalizedTimeoutTime{unnormalizedSec - 1, unnormalizedNsec + 1000000000};
|
||||
while (true) {
|
||||
const int result = _Cnd_timedwait_nid_postfix(&condition, &mutex, &unnormalizedTimeoutTime);
|
||||
if (result == timedout) {
|
||||
Require(std::chrono::system_clock::now() >= unnormalizedDeadline);
|
||||
break;
|
||||
}
|
||||
Require(result == success);
|
||||
}
|
||||
|
||||
Xtime futureTime{nowSec + 5, nowNsec};
|
||||
bool ready = false;
|
||||
std::thread signaler([&] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
Require(_Mtx_lock_nid_postfix(&mutex) == success);
|
||||
ready = true;
|
||||
Require(_Cnd_broadcast_nid_postfix(&condition) == success);
|
||||
Require(_Mtx_unlock_nid_postfix(&mutex) == success);
|
||||
});
|
||||
while (!ready) {
|
||||
Require(_Cnd_timedwait_nid_postfix(&condition, &mutex, &futureTime) == success);
|
||||
}
|
||||
signaler.join();
|
||||
|
||||
Require(_Mtx_unlock_nid_postfix(&mutex) == success);
|
||||
RequireThrows<std::invalid_argument>([&] { _Cnd_timedwait_nid_postfix(&condition, &mutex, nullptr); });
|
||||
const Xtime overflowCarryTime{std::numeric_limits<std::int64_t>::max(), 1000000000};
|
||||
RequireThrows<std::overflow_error>([&] { _Cnd_timedwait_nid_postfix(&condition, &mutex, &overflowCarryTime); });
|
||||
const Xtime overflowSecTime{std::numeric_limits<std::int64_t>::max(), 0};
|
||||
RequireThrows<std::overflow_error>([&] { _Cnd_timedwait_nid_postfix(&condition, &mutex, &overflowSecTime); });
|
||||
_Cnd_destroy_nid_postfix(&condition);
|
||||
_Mtx_destroy_nid_postfix(&mutex);
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
- [nearbyintf](../../../core/libs/prx/libc/src/Math.cpp) (libc, NID `c+4r-T-tEIc`) uses the host rounding mode, not the console's; console rounding mode state remains unverified.
|
||||
- [_Thrd_sleep](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `jfRI3snge3o`) follows the Dinkumware threads library shipped with [Visual C++ 2013](https://github.com/adityaramesh/concurrency_runtime/tree/master/VS2013_SP2/crt/src/thr) (`xtime.c`) and C11 Annex K, without console verification. It reads a 16-byte xtime (64-bit seconds and nanoseconds) on the guest `CLOCK_REALTIME`, normalizes the nanoseconds and sleeps through `nanosleep`, retrying on `EINTR`, so it is a cancellation point; a null xtime or one whose normalization overflows throws. The console's `_Thrd_sleep` (disassembled from a title's `libc.prx`) also takes an optional `xtime*` of remaining time and returns `int`; here the sleep is never cut short, so it returns 0 and leaves the remaining time untouched.
|
||||
- [_Mtx_init_with_name](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `tgioGpKtmbE`) forwards to `_Mtx_init` and ignores the mutex name parameter.
|
||||
- [_Cnd_timedwait](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `McaImWKXong`) follows the console's `_Cnd_timedwait` (disassembled from a title's `libc.prx`), where xtime is an absolute deadline converted against the current time: it returns `_Thrd_timedout` (2) on expiration and `_Thrd_success` (0) on notification. The console returns 4 for other internal pthread errors while this implementation throws.
|
||||
- [Guest arena](../../../core/libs/prx/libc/src/GuestArena.cpp) (libc) - the application map area is assumed to end at 0xFC_0000_0000, as `SCE_KERNEL_APP_MAP_AREA_END_ADDR` does on the PS4. On Windows, host allocations made in the arena before libc loads stay, and a fixed guest mapping over one throws
|
||||
- [sceLibcInternalBacktraceForGame](../../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - `(const char* heapName)` and its return follow the `libc.prx` ASTRO BOT (PPSA21564 01.007.000) bundles: its four calls (0x6801, 0x7F46, 0xE75E, 0xEE02) are in the `malloc_stats` and `sceLibcMspaceMallocStats` paths, each after a `[%s] ...` line printed with the same heap name buffer, and the caller ignores the result and continues. The console's backtrace output is not reproduced: the heap name and the guest return address are written to the host standard error
|
||||
- [sceLibcInternalHeapErrorReportForGame](../../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - `(void* heap, void* block, uint32 error)` follows the same `libc.prx` (call at 0xF634): it passes the heap, the block and the nonzero result of its block check, and then calls a function that never returns, so the report only has to return. The meaning of the error values is unknown; the three values and the guest return address are written to the host standard error
|
||||
|
|
|
|||
Loading…
Reference in New Issue