Skip to content

feat(libc): implement the Dinkumware mtx and cnd functions - #1952

Closed
overlordxrz-source wants to merge 1 commit into
boykopovar:mainfrom
overlordxrz-source:feat/libc-dinkumware-mtx-cnd
Closed

overlordxrz-source wants to merge 1 commit into
boykopovar:mainfrom
overlordxrz-source:feat/libc-dinkumware-mtx-cnd

Conversation

@overlordxrz-source

@overlordxrz-source overlordxrz-source commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

What

Cyberpunk 2077 (PPSA04029) imports eight Dinkumware thread functions that libc only has as throwing libcCyberUnknown* stubs. Each name hashes to its stub's NID with tools/nid_names.py's compute_nid, and python3 tools/nid_names.py already lists them as rename-ready:

NID Stub Function
YaHc3GS7y7g libcCyberUnknown12 _Mtx_init
5Lf51jvohTQ libcCyberUnknown03 _Mtx_destroy
iS4aWbUonl0 libcCyberUnknown14 _Mtx_lock
gTuXQwP9rrs libcCyberUnknown13 _Mtx_unlock
SreZybSRWpU libcCyberUnknown10 _Cnd_init
7yMFgcS8EPA libcCyberUnknown05 _Cnd_destroy
vEaqE-7IZYc libcCyberUnknown16 _Cnd_wait
VsP3daJgmVA libcCyberUnknown11 _Cnd_broadcast

It also adds _Mtx_trylock (k6pGNMwJB08, which Minecraft Legends PPSA05510 imports according to #268) and _Cnd_signal (0uuqgRz9qfo, imported by GTA III Definitive, PPSA05510 and Deathloop according to #267, #268 and #272). The eight Cyberpunk NIDs are also in the import lists of #267, #268 and #272.

This follows the review on #492, which was closed with an invitation to reopen with only the mtx/cnd functions:

  • _Mtx_init makes the mutex recursive only when type & 0x100 is set. _Mtx_plain, _Mtx_try and _Mtx_timed give a plain mutex.
  • The return codes are Dinkumware's: 0 for success and 1 for out of memory. _Mtx_lock and _Mtx_trylock return 3 (_Thrd_busy) when a plain mutex is relocked by its owner or when trylock finds it busy. _Mtx_unlock and _Cnd_wait return 4 (_Thrd_error) when the caller does not own the mutex, and so does a call with a null pointer.
  • Every export takes the address of the guest _Mtx_t/_Cnd_t, which holds one pointer to host state. This is the pre-VS2015 Dinkumware <xthreads.h> ABI (_Mtx_lock(_Mtx_t *), _Cnd_wait(_Cnd_t *, _Mtx_t *)) that fpPS4's reverse-engineered libSceLibcInternal follows under the same NIDs. A zeroed one is set up on first use, the way libkernel handles a static mutex. _Mtx_destroy and _Cnd_destroy return void and clear the slot.
  • _Cnd_wait on a recursive mutex releases every level and restores the count after waking.

The review suggested going through libkernel's scePthreadMutex*/scePthreadCond*. That isn't possible here because libkernel links libc (core/libs/prx/libkernel/CMakeLists.txt), so libc/src/Threads.cpp keeps its own host state and returns the codes that the error-checking pthread mutex would produce through Sony's wrapper.

Not included, because they are separate topics: abs (Ye20uNnlglA, in #1097), __powidf2, _WStoul, _Iswctype, vsnprintf_s and vsscanf_s. #1498 also changes these eight stubs, without renaming them. Its libcCyberUnknown13 locks and its libcCyberUnknown14 unlocks, but the NIDs say 13 is _Mtx_unlock and 14 is _Mtx_lock.

Log

I don't own Cyberpunk 2077, so I can't paste the line where PPSA04029 stops on main, and I found no issue or discussion that names one of these NIDs as a stop. The evidence is the PPSA04029 relinker import list in #266, where all eight NIDs appear, and the existing stubs on main that were added for this title in #358. If someone with the title can post the NotImplemented line it reaches on main, I'll add it here.

Tested

aarch64 Debian 13 host. The full project can't be built here because it needs x86-64.

  • New core/libs/tests/GuestLibcThreads.cpp (guest_libc_threads), linked natively against Threads.cpp, passes 30 out of 30 runs, and also passes under ASan and UBSan. It checks plain types 1, 2, 3 and 5 (relock and busy trylock return 3; unlock and trylock from another thread return 4 and 3; a second unlock returns 4), a recursive mutex with nested lock and trylock that blocks another thread until fully released, eight-thread contention, zeroed slots, broadcast waking four waiters, signal handing out three tokens, waiting on an unowned mutex returning 4, a wait on a twice-locked recursive mutex restoring its count, and null arguments.
  • Threads.cpp, Export.cpp and the test compile for x86_64-linux-gnu and x86_64-windows-gnu with -Wall -Wextra (Zig 0.17 clang). The only warning is the existing -Wreturn-type-c-linkage from General.hpp.
  • python3 tools/check_conventions.py --base origin/main passes.
  • Not run with a title.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes

Cyberpunk 2077 (PPSA04029) imports _Mtx_init, _Mtx_destroy, _Mtx_lock,
_Mtx_unlock, _Cnd_init, _Cnd_destroy, _Cnd_wait and _Cnd_broadcast,
which were throwing libcCyberUnknown stubs. Each name hashes to the
stub's NID. _Mtx_trylock and _Cnd_signal complete the set other titles
import.

The guest's _Mtx_t and _Cnd_t hold a pointer to host state, created on
first use when zero. type & 0x100 selects a recursive mutex; other bits
give a plain one that, like Sony's error-checking pthread mutex, returns
_Thrd_busy (3) on relock or a busy trylock and _Thrd_error (4) when the
caller does not own it.
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown

Progress report (f145139 → 9a33c0b)

📈 System libraries: 85.03% (+0.27%, +10 implemented, -8 removed functions)

✅ 10 implemented
Library Name
libc _Cnd_broadcast_nid_postfix
libc _Cnd_destroy_nid_postfix
libc _Cnd_init_nid_postfix
libc _Cnd_signal_nid_postfix
libc _Cnd_wait_nid_postfix
libc _Mtx_destroy_nid_postfix
libc _Mtx_init_nid_postfix
libc _Mtx_lock_nid_postfix
libc _Mtx_trylock_nid_postfix
libc _Mtx_unlock_nid_postfix
🗑️ 8 removed
Library Name
libc libcCyberUnknown03
libc libcCyberUnknown05
libc libcCyberUnknown10
libc libcCyberUnknown11
libc libcCyberUnknown12
libc libcCyberUnknown13
libc libcCyberUnknown14
libc libcCyberUnknown16

@MarcZX10 MarcZX10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking ABI question: every operation export in Threads.cpp currently accepts LibcMutex** / LibcCondition** and dereferences the argument as a slot. The corresponding Dinkumware handle types are _Mtx_t = _Mtx_internal_imp_t* and _Cnd_t = _Cnd_internal_imp_t*; _Mtx_lock, _Mtx_trylock, _Mtx_unlock, _Mtx_destroy, _Cnd_wait, _Cnd_signal, _Cnd_broadcast, and _Cnd_destroy take those handles by value. Only _Mtx_init and _Cnd_init take pointer-to-handle. The official declarations are in Microsoft STL xthreads.h and the handle definitions in __msvc_threads_core.hpp.

The new test declares all operations as void** and passes &mutex / &condition, so it reproduces the implementation's assumption instead of checking the caller ABI. Please provide disassembly or another source showing that the PS5 wrappers intentionally use pointer-to-slot for these non-init functions. If they use the Dinkumware handle ABI, adjust the export signatures and tests so callers pass the stored handle itself; otherwise a real title passes an object pointer that this code interprets as a pointer slot.

@overlordxrz-source

Copy link
Copy Markdown
Contributor Author

Thanks for checking this against the headers. I looked into it, and the PS4/PS5 libc uses the older Dinkumware signatures, where every _Mtx_*/_Cnd_* call takes a pointer to the handle. The by-value form you quoted comes from Microsoft's rework of <xthreads.h>, which shipped after the Dinkumware version Sony's SDK is based on.

  • Dinkumware xthreads.h before Microsoft's rework (VS2013 copy): int _Mtx_lock(_Mtx_t *), _Mtx_trylock(_Mtx_t *), _Mtx_unlock(_Mtx_t *), void _Mtx_destroy(_Mtx_t *), _Cnd_wait(_Cnd_t *, _Mtx_t *), _Cnd_signal(_Cnd_t *), _Cnd_broadcast(_Cnd_t *), void _Cnd_destroy(_Cnd_t *), with _Mtx_t = struct _Mtx_internal_imp_t * (lines 40-71): http://sandbox.hlt.bme.hu/~gaebor/STLdoc/VS2013/xthreads_8h_source.html. The callers in that library pass the address of the member, for example _Mtx_lockX(&_Mtx), _Cnd_waitX(&_Cnd, &_Mtx) and _Mtx_destroy(&_Mtx) in condition_variable_any: http://sandbox.hlt.bme.hu/~gaebor/STLdoc/VS2013/classcondition__variable__any.html. You can see the change in microsoft/STL itself. The initial commit still has _Mtx_init(_Mtx_t*, int) paired with by-value _Mtx_destroy(_Mtx_t) and _Mtx_init_in_situ (xthreads.h@2195148, L77-L109), and the lines you linked on main no longer have _Mtx_init/_Cnd_init at all.
  • PS4 libSceLibcInternal, as reverse engineered in fpPS4: ps4__Mtx_lock(pmtx:pp_mtx), _trylock, _unlock, _destroy, _timedlock and _current_owns all take pp_mtx = ^p_mtx and start with _mtx := pmtx^. _Mtx_lock even reloads pmtx^ after each cond wait (ps4_mtx_internal.pas L19, L36-L43, L167-L189). They are registered under the same NIDs this PR exports. For example $892E1A59B5289E5D is iS4aWbUonl0 = _Mtx_lock, and $61A1DCDC64BBCBB8 is YaHc3GS7y7g = _Mtx_init (ps4_libscelibcinternal.pas L450-L457).
  • This also lines up with C11 <threads.h> in Dinkumware's library, where mtx_lock(mtx_t *) maps straight onto _Mtx_lock. That only works if _Mtx_lock takes a pointer.

fpPS4 doesn't implement _Cnd_*. I'm following the same header for those, where _Cnd_wait(_Cnd_t *, _Mtx_t *) and the other calls also take the handle by address.

So I've kept the LibcMutex** / LibcCondition** exports, and the test still calls init(&h) and then lock(&h), which is what Sony's std::mutex does with its _Mtx_t member. I don't have a PS5 libc disassembly to paste. If you have one that shows a by-value _Mtx_lock, I'll switch it.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Closing: only import lists show these, and Cyberpunk's PPSA03974 build ships its own libc.prx, which its imports bind to first (#1980). Reopen with the not implemented line from main when a title stops here.

@oneandonlydean oneandonlydean mentioned this pull request Oct 9, 2026
9 tasks done
ycbuzi pushed a commit to ycbuzi/AnyPS5 that referenced this pull request Oct 10, 2026
…d cnd functions

Harvested from upstream (closed as need-gated, not on quality). Hand
merge: the PR replaces the placeholder exports in libc/Export.cpp with
src/Threads.cpp, while the stack had already renamed those placeholders
to _Cnd_*/_Mtx_* stubs. Kept our Export.cpp and deleted the eight stubs
that Threads.cpp now implements. Library completion 2935/3298 ->
2945/3300.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011krDVCnnMQPaEFEqCDnKmA
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants