feat(libc): add missing Unity IL2CPP runtime imports and Dinkumware helpers

Add missing libc.prx symbols required by Unity IL2CPP runtime:
- Implement _Dtest double classification in Math.cpp
- Implement _Inf double constant in Math.cpp
- Implement sceLibcBacktraceGetBufferSize and sceLibcBacktraceSelf in Backtrace.cpp
- Implement _Cnd_signal in Threads.cpp
- Implement _ZNSt8ios_base7_AddstdEPS_ in RuntimeSupport.cpp
- Implement __pointer_type_info and __function_type_info vtables in TypeInfo.cpp
- Remove duplicate stubs from libSceAmpr/Export.cpp
- Add unit tests in GuestMath.cpp and GuestLibcThreads.cpp
- Document unverified behaviors in docs/dev/debt/libc.md
This commit is contained in:
hamb3r 2026-10-13 00:03:15 +03:00
parent 0214069e33
commit 8652dbe35b
11 changed files with 99 additions and 22 deletions

View File

@ -898,15 +898,9 @@ int APS5_VABI sceAmprAmmWaitCommandBufferCompletion(std::uint32_t id) {
return AmmSubmitted_nid_no_patch(id) ? 0 : SCE_KERNEL_ERROR_ESRCH;
}
int APS5_VABI _ZNSt8ios_base7_AddstdEPS__nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
[[noreturn]] void APS5_VABI _ZSt16_Throw_Cpp_errori_nid_postfix(int code) {
ThrowCppError_nid_no_patch(code);
}
int APS5_VABI _Thrd_join_nid_postfix(Pthread thread, int* code) {
void* result = nullptr;
if (pthread_join_nid_postfix(thread, &result) != 0) return ThrdError;
@ -957,16 +951,11 @@ void APS5_VABI _ZNSt4_Pad7_LaunchEPP7pthread_nid_postfix(GuestPad* pad, Pthread*
if (created != 0) ThrowCppError_nid_no_patch(CppResourceUnavailableTryAgain);
while (!pad->started) RequireThrd(_Cnd_wait_nid_postfix(&pad->condition, &pad->mutex), "_Pad::_Launch: _Cnd_wait");
}
int APS5_VABI _ZNSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI _Cnd_signal_nid_postfix(void** handle) {
return SignalCondition_nid_no_patch(handle);
}
void APS5_VABI _Unlock_shared_ptr_spin_lock_nid_postfix(void) {
sharedPtrSpinLock.clear(std::memory_order_release);
}
@ -975,11 +964,6 @@ int APS5_VABI _Cnd_init_with_name_nid_postfix(void** handle, const char*) {
return _Cnd_init_nid_postfix(handle);
}
int APS5_VABI _ZTVN10__cxxabiv120__function_type_infoE_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI _ZTVSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@ -995,11 +979,6 @@ int APS5_VABI _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_
return 0;
}
int APS5_VABI _ZTVN10__cxxabiv119__pointer_type_infoE_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE7_GetcatEPPKNSt6locale5facetEPKS5__nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;

View File

@ -48,6 +48,7 @@ target_sources(libc PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src/RealPath.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/CxxAbiSupport.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Threads.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Backtrace.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/exception/Exports.cpp
)
@ -214,7 +215,7 @@ if(BUILD_TESTING)
endif()
add_test_executable(guest_libc_threads_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibcThreads.cpp)
target_include_directories(guest_libc_threads_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_libc_threads_tests PRIVATE libc)
target_link_libraries(guest_libc_threads_tests PRIVATE libc libkernel)
configure_windows_unwind(guest_libc_threads_tests)
add_test(NAME guest_libc_threads COMMAND guest_libc_threads_tests)
set_tests_properties(guest_libc_threads PROPERTIES TIMEOUT 20)

View File

@ -39,6 +39,8 @@ public:
class __pointer_type_info : public __pbase_type_info {};
class __function_type_info : public std::type_info {};
class __pointer_to_member_type_info : public __pbase_type_info {
public:
const __class_type_info* __context;

View File

@ -0,0 +1,16 @@
#include <cstddef>
#include "prx/libc/include/General.hpp"
extern "C" {
int APS5_VABI sceLibcBacktraceGetBufferSize_nid_postfix(int, int*, std::size_t*) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceLibcBacktraceSelf_nid_postfix(int, void*, std::size_t, int*) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -465,6 +465,19 @@ short APS5_VABI _FDtest_nid_postfix(const float* value) {
if (exponent == 0) return fraction != 0 ? Denormal : Zero;
return Finite;
}
short APS5_VABI _Dtest_nid_postfix(const double* value) {
constexpr short Denormal = -2, Finite = -1, Zero = 0, Infinite = 1, NotANumber = 2;
if (value == nullptr) throw std::invalid_argument("_Dtest: null value");
std::uint64_t bits;
std::memcpy(&bits, value, sizeof(bits));
const auto exponent = bits & 0x7ff0000000000000u;
const auto fraction = bits & 0x000fffffffffffffu;
if (exponent == 0x7ff0000000000000u) return fraction != 0 ? NotANumber : Infinite;
if (exponent == 0) return fraction != 0 ? Denormal : Zero;
return Finite;
}
int APS5_VABI __isnanf_nid_postfix(float x) { return std::isnan(x) ? 1 : 0; }
int APS5_VABI isnanf_nid_postfix(float x) { return __isnanf_nid_postfix(x); }
int APS5_VABI isnan_nid_postfix(double x) { return __isnan_nid_postfix(x); }
@ -526,4 +539,13 @@ int APS5_VABI _Fltrounds_nid_postfix(void) {
return SonyFltrounds();
}
union SonyDconst {
unsigned short _Word[8];
float _Float;
double _Double;
long double _Long_double;
};
static_assert(std::endian::native == std::endian::little);
SonyDconst _Inf_nid_postfix {{0, 0, 0, 0x7ff0, 0, 0, 0, 0}};
}

View File

@ -192,4 +192,8 @@ unsigned __int128 APS5_VABI __umodti3_nid_postfix(unsigned __int128 dividend, un
return SonyUmodti3(dividend, divisor);
}
void APS5_VABI _ZNSt8ios_base7_AddstdEPS__nid_postfix(void*) {
NotImplemented_nid_no_patch(__func__);
}
}

View File

@ -294,4 +294,10 @@ int APS5_VABI _Thrd_sleep_nid_postfix(const Xtime* target, Xtime*) {
}
}
int APS5_VABI _Cnd_signal_nid_postfix(void** handle) {
return SignalCondition_nid_no_patch(handle);
}
}

View File

@ -13,6 +13,8 @@ void APS5_VABI DeleteTypeInfo(std::type_info*) {
}
bool APS5_VABI IsPointerTypeInfo(const std::type_info*) { return false; }
bool APS5_VABI IsPointerTypeInfoTrue(const std::type_info*) { return true; }
bool APS5_VABI IsFunctionTypeInfoTrue(const std::type_info*) { return true; }
bool APS5_VABI CatchTypeInfo(const std::type_info* self, const std::type_info* thrown, void** object, unsigned) {
if (!self || !thrown || !object) throw std::invalid_argument("invalid RTTI catch arguments");
@ -65,4 +67,19 @@ extern "C" {
LibcException::TypeInfoVtable _ZTVN10__cxxabiv117__class_type_infoE_nid_postfix {0, &typeid(__cxxabiv1::__class_type_info)};
LibcException::TypeInfoVtable _ZTVN10__cxxabiv120__si_class_type_infoE_nid_postfix {0, &typeid(__cxxabiv1::__si_class_type_info)};
LibcException::TypeInfoVtable _ZTVN10__cxxabiv121__vmi_class_type_infoE_nid_postfix {0, &typeid(__cxxabiv1::__vmi_class_type_info)};
LibcException::TypeInfoVtable _ZTVN10__cxxabiv119__pointer_type_infoE_nid_postfix {
0,
&typeid(__cxxabiv1::__pointer_type_info),
LibcException::DestroyTypeInfo,
LibcException::DeleteTypeInfo,
LibcException::IsPointerTypeInfoTrue
};
LibcException::TypeInfoVtable _ZTVN10__cxxabiv120__function_type_infoE_nid_postfix {
0,
&typeid(__cxxabiv1::__function_type_info),
LibcException::DestroyTypeInfo,
LibcException::DeleteTypeInfo,
LibcException::IsPointerTypeInfo,
LibcException::IsFunctionTypeInfoTrue
};
}

View File

@ -16,6 +16,7 @@ int APS5_VABI _Cnd_init_nid_postfix(void** handle);
void APS5_VABI _Cnd_destroy_nid_postfix(void** handle);
int APS5_VABI _Cnd_wait_nid_postfix(void** condition, void** mutex);
int APS5_VABI _Cnd_broadcast_nid_postfix(void** handle);
int APS5_VABI _Cnd_signal_nid_postfix(void** handle);
}
static void Require(bool value) { if (!value) std::abort(); }
@ -175,9 +176,17 @@ static void ConditionBroadcast() {
_Mtx_destroy_nid_postfix(&mutex);
}
static void ConditionSignal() {
void* condition = nullptr;
Require(_Cnd_init_nid_postfix(&condition) == success);
Require(_Cnd_signal_nid_postfix(&condition) == success);
_Cnd_destroy_nid_postfix(&condition);
}
int main() {
MutexBasics();
RecursiveMutex();
MutualExclusion();
ConditionBroadcast();
ConditionSignal();
}

View File

@ -47,6 +47,7 @@ struct LibcFloatConstant { std::uint32_t bits[4]; };
extern LibcFloatConstant _FInf_nid_postfix;
extern LibcFloatConstant _FNan_nid_postfix;
short APS5_VABI _FDtest_nid_postfix(const float*);
short APS5_VABI _Dtest_nid_postfix(const double*);
int APS5_VABI __fpclassifyf_nid_postfix(float);
int APS5_VABI isnanf_nid_postfix(float);
int APS5_VABI __signbitl_nid_postfix(long double);
@ -696,6 +697,24 @@ static void CheckFloatMathExports() {
Require(isinf_nid_postfix(nan) == 0 && isinf_nid_postfix(1.0) == 0);
}
static void CheckDoubleClassification() {
const struct { std::uint64_t bits; short code; } cases[] = {
{0x0000000000000000ull, 0}, {0x8000000000000000ull, 0},
{0x0000000000000001ull, -2}, {0x800fffffffffffffull, -2},
{0x0010000000000000ull, -1}, {0xbff0000000000000ull, -1}, {0x7fefffffffffffffull, -1},
{0x7ff0000000000000ull, 1}, {0xfff0000000000000ull, 1},
{0x7ff0000000000001ull, 2}, {0x7ff8000000000000ull, 2}, {0xfff8000000000000ull, 2},
};
for (const auto& test : cases) {
double value;
std::memcpy(&value, &test.bits, sizeof(value));
if (_Dtest_nid_postfix(&value) != test.code) {
std::fprintf(stderr, "Guest _Dtest failed for %016llx\n", static_cast<unsigned long long>(test.bits));
std::abort();
}
}
}
static void CheckDoubleRounding(double value, std::int64_t expected, bool invalid) {
for (const bool useLlround : {false, true}) {
std::feclearexcept(FE_ALL_EXCEPT);
@ -956,4 +975,5 @@ int main() {
CheckAtanhf();
CheckHypotSpecialValue();
CheckFmodErrno();
CheckDoubleClassification();
}

View File

@ -66,3 +66,4 @@
- [flock](../../../core/libs/prx/libc/src/FileLock.cpp) (libkernel) on Windows shares one lock state between duplicated descriptors, following FreeBSD 11.4's [flock(2)](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libc/sys/flock.2). Independent opens have separate states; closing a descriptor or reopening a stream drops only its reference. Lock transitions on one open file description are serialized. The host's [LockFileEx](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex) locks also restrict ordinary reads and writes through other handles, unlike FreeBSD's advisory locks, and do not restrict mapped views. Console behavior, signal interruption and concurrent closure of a descriptor while its lock call is pending are unverified.
- [std::_Throw_Cpp_error](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libSceAmpr) follows Dinkumware's `thread0.cpp` as shipped with [Visual C++ 2013](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/thread0.cpp) and in [microsoft/STL](https://github.com/microsoft/STL/blob/da52dc0fcadbd83690cfd1ed32408d2847439493/stl/src/thread0.cpp), without console verification. `_Cpp_error` codes 0 to 6 throw a guest `std::system_error` whose `error_code` holds the FreeBSD errno of the `errc` value (EBUSY 16, EINVAL 22, ESRCH 3, ENOMEM 12, EPERM 1, EDEADLK 11, EAGAIN 35) and the generic category, and whose `what()` is the table message, `": "` and the generic category's message for that errno, selected by the first matching errno entry in VC12's [`syserror.cpp`](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/syserror.cpp); another code throws `std::out_of_range` on the host instead of reading past the table. The guest `system_error` is taken to be [VC12's](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/system_error): the 16-byte `runtime_error` followed by `error_code {int, const error_category*}`, 32 bytes, so its copy constructor and assignment copy the code too. The generic category is a static libc object with VC12's `error_category` virtual order (destructors, `name`, `message`, `default_error_condition`, the two `equivalent`), not the console's `_Error_objects<int>::_Generic_object`, and the new `generic_category()` export returns that object; a title's inline category object in a bundled libc can still have a different identity. Its `message` and direct message export construct the existing [GuestLocale::String](../../../core/libs/prx/libc/include/GuestLocale.hpp) model: a reserved eight-byte word, 16-byte inline buffer, length and capacity, with guest `operator new` storage above 15 bytes. This 40-byte string layout and virtual return ABI have not been verified against a console library. The deleting destructor still throws. `_Syserror_map` covers the 72 distinct Dinkumware `errc` values present in FreeBSD 11.4 [errno.h](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/sys/errno.h), returning null for other values; category messages use `unknown error` for those. First-match aliases are `EAGAIN`/`EWOULDBLOCK` -> `operation would block` and `ENOTSUP`/`EOPNOTSUPP` -> `not supported`. STREAMS-only errno assignments absent from that header (`ENODATA`, `ENOSR`, `ENOSTR`, `ETIME`) are not inferred
- [Call tracing](../../../core/libs/prx/libc/src/specifics/windows/CallTrace.cpp) (`--trace-imports`, `APS5_TRACE_CALLS`) exists on Windows only and records the entry of a call, not its arguments or its result. One process-wide ring keeps the last 65536 calls, so a thread that calls often pushes the others out. Calls are also counted per traced target since start-up; an import bound to a target already traced under another name is counted under the first name. The thunk saves the integer argument registers and `xmm0` to `xmm7`; arguments in wider vector registers pass only as long as the recording code stays free of AVX instructions, which is not tested
- [_Dtest](../../../core/libs/prx/libc/src/Math.cpp) (libc) follows Dinkumware double classification matching _FDtest: finite values return -1, subnormals -2, zeros 0, infinities 1, and NaNs 2.