Skip to content

[native] Replace std::mutex with pthread_mutex_t in the CoreCLR host - #12541

Open
simonrozsival wants to merge 7 commits into
dev/simonrozsival/clr-remove-std-formatfrom
dev/simonrozsival/clr-replace-std-mutex
Open

[native] Replace std::mutex with pthread_mutex_t in the CoreCLR host#12541
simonrozsival wants to merge 7 commits into
dev/simonrozsival/clr-remove-std-formatfrom
dev/simonrozsival/clr-replace-std-mutex

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Aug 27, 2026

Copy link
Copy Markdown
Member

Part of #12533 (drop libc++ from the CoreCLR host). Stacked on top of #12534.

std::mutex is a thin wrapper over pthread_mutex_t — it holds one as its only member and its lock/unlock forward straight to pthread_mutex_lock/pthread_mutex_unlock. Using it in the host buys us nothing over the primitive itself, but it drags <mutex> into six translation units.

This replaces it with pthread_mutex_t directly.

What

  • std::mutexpthread_mutex_t x = PTHREAD_MUTEX_INITIALIZER
  • std::lock_guard → explicit pthread_mutex_lock (&x) / pthread_mutex_unlock (&x), with the unlock placed immediately before any early return
  • StartupAwareLock now takes a pthread_mutex_t&
  • #include <mutex> removed; explicit #include <pthread.h> added where the type is used

No locked region changed shape. Every hunk is a type change, a lock call, or an added unlock — there is no restructured logic to re-verify.

File Lock
clr/host/assembly-store.cc + include/host/assembly-store.hh state_lock
clr/host/fastdev-assemblies.cc + include/host/fastdev-assemblies.hh override_dir_lock (DEBUG-only)
clr/include/runtime-base/monodroid-dl.hh dso_handle_write_lock
clr/include/runtime-base/startup-aware-lock.hh takes pthread_mutex_t&
common/include/runtime-base/timing.hh sequence_lock

timing.hh is the only file outside clr/. It used std::mutex directly, so it cannot be skipped; MonoVM builds clean with the change and mono/ is otherwise untouched (MonoVM has its own xamarin::android::mutex in mono/shared/cppcompat.hh, which already wrapped pthreads and is left alone).

Effect

This removes the last #include <mutex> in the repository:

files including <mutex> std::mutex / std::lock_guard uses
before 6 13
after 0 0

It also drops eight undefined references, since lock (), unlock () and the destructor are out-of-line in libc++ rather than header-inlined:

symbol before after
std::__ndk1::mutex::lock() 3 0
std::__ndk1::mutex::unlock() 3 0
std::__ndk1::mutex::~mutex() 2 0
refs __cxa_guard_*
#12534 (base) 55 10
this PR 47 10

The link-time libc++ requirement only disappears when every cause reaches zero, so this is one of several prerequisites rather than a self-sufficient win — the remaining causes (operator new/delete[], __cxa_guard_*, std::string, __libcpp_verbose_abort) are tracked in #12533.

Why manual unlock is safe here

The native runtime is built with -fno-exceptions (confirmed in compile_commands.json), so there is no unwind path that std::lock_guard would have covered and a manual unlock would miss. Every return inside a locked region was verified to be immediately preceded by an unlock of that mutex, across all 13 regions.

Verification

  • CoreCLR, MonoVM and NativeAOT all build clean. fastdev-assemblies.cc is #if defined(DEBUG) and therefore not built in Release, so it was additionally compiled with -DDEBUG to confirm it is correct.
  • Real libc++ references: CoreCLR 55 → 47, NativeAOT 0 — the eight removed are exactly the mutex members listed above, confirmed by diffing the demangled undefined-symbol sets of the two archives.
  • __cxa_guard_* undefined references stay at 10. This is the check that matters for correctness of the conversion: pthread_mutex_t is a POD and PTHREAD_MUTEX_INITIALIZER is a constant initializer, so the four statics stay constant-initialized. Had they needed dynamic initialization we would have traded eight mutex symbols for eight guard symbols and gained nothing.
  • Only pthread_mutex_lock / pthread_mutex_unlock (both libc) remain as mutex symbols.

Copilot AI 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.

Pull request overview

This PR reduces the CoreCLR host’s dependence on libc++ by replacing std::mutex/std::lock_guard usage with a minimal pthread-backed Mutex/MutexGuard wrapper in the shared native runtime-base headers.

Changes:

  • Add Mutex and MutexGuard wrappers around pthread_mutex_t in runtime-base/mutex.hh.
  • Update CoreCLR host code paths (assembly store, FastDev assemblies, DSO loader, startup-aware lock) to use the new wrapper instead of <mutex>.
  • Update shared timing code to use the new wrapper (affecting other runtime lanes via common sources).
Show a summary per file
File Description
src/native/common/include/runtime-base/timing.hh Switch timing sequence locking to Mutex/MutexGuard.
src/native/common/include/runtime-base/mutex.hh Introduce pthread-backed Mutex and MutexGuard.
src/native/clr/include/runtime-base/startup-aware-lock.hh Replace std::mutex reference with Mutex.
src/native/clr/include/runtime-base/monodroid-dl.hh Replace static std::mutex with Mutex for DSO handle write lock.
src/native/clr/include/host/fastdev-assemblies.hh Replace override directory lock from std::mutex to Mutex.
src/native/clr/include/host/assembly-store.hh Replace assembly decompress lock from std::mutex to Mutex.
src/native/clr/host/fastdev-assemblies.cc Replace std::lock_guard usage with MutexGuard.
src/native/clr/host/assembly-store.cc Replace internal state_lock and std::lock_guard usage with Mutex/MutexGuard.

Review details

  • Files reviewed: 8/8 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread src/native/common/include/runtime-base/mutex.hh Outdated
Comment thread src/native/clr/include/runtime-base/monodroid-dl.hh Outdated
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/clr-replace-std-mutex branch from 9b98570 to 83ca915 Compare August 27, 2026 15:47
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/clr-replace-std-mutex branch from 83ca915 to d43d7d7 Compare August 27, 2026 16:09
@simonrozsival simonrozsival changed the title [native] Replace std::mutex with a pthread wrapper in the CoreCLR host [native] Replace std::mutex with pthread_mutex_t in the CoreCLR host Aug 27, 2026
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/clr-replace-std-mutex branch from 11cdb27 to 36f1cc4 Compare August 27, 2026 19:29
@simonrozsival
simonrozsival changed the base branch from dev/simonrozsival/clr-timing-raw-pointer to dev/simonrozsival/clr-remove-std-format August 27, 2026 19:34
@simonrozsival simonrozsival added the drop-libcpp Work to remove the libc++ dependency from Android NativeAOT label Aug 27, 2026
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/clr-replace-std-mutex branch from 36f1cc4 to 85c0bf3 Compare August 27, 2026 21:42
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/clr-replace-std-mutex branch from 85c0bf3 to 875ef58 Compare August 28, 2026 07:54
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/clr-replace-std-mutex branch from 875ef58 to a3811d6 Compare August 28, 2026 08:47
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/clr-replace-std-mutex branch from a3811d6 to 82ea5c4 Compare August 28, 2026 08:55
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/clr-replace-std-mutex branch from 82ea5c4 to ba25053 Compare August 28, 2026 09:51
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/clr-replace-std-mutex branch from ba25053 to 2507635 Compare August 28, 2026 10:29
simonrozsival and others added 3 commits August 28, 2026 14:02
`std::mutex` and `std::lock_guard` are thin wrappers over pthreads, but
using them makes the runtime depend on libc++. Add `Mutex` and `MutexGuard`
in `common/include/runtime-base/mutex.hh` and use them instead.

`Mutex` uses `PTHREAD_MUTEX_INITIALIZER` as a default member initializer and
has a `constexpr` default constructor, so the four static instances
(`assembly_decompress_mutex`, `override_dir_lock`, `dso_handle_write_lock`
and `Timing::sequence_lock`) are constant-initialized. That means they need
neither dynamic initialization nor a thread-safe initialization guard - the
number of `__cxa_guard_*` references is unchanged by this commit.

This drops all eight `std::__ndk1::mutex` references from
`libnet-android.release-static-release.a`, taking the host from 48 to 40
undefined libc++ symbols.

`Timing` lives in the shared `common` sources, so this affects the MonoVM
host too; it still links libc++ for other reasons, and the behaviour is
unchanged either way.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e
Reuse a single pthread mutex wrapper instead of adding a second one.

`mono/shared/cppcompat.hh` already contained `xamarin::android::mutex` and
`xamarin::android::lock_guard`, added for exactly the same reason: `<mutex>`
makes the runtime depend on libc++. Rather than maintain two wrappers with
the same purpose, delete `cppcompat.hh` and move the MonoVM host over to the
shared `Mutex`/`MutexGuard` in `common/include/runtime-base/mutex.hh`.

`Mutex` is the stricter of the two: it deletes the copy and move operations,
which the old `mutex` left implicitly defined even though copying a
`pthread_mutex_t` is never correct. It is also explicitly `constexpr`
default constructible, so static instances stay constant-initialized.

Also refresh the two comments explaining why `NDEBUG` is defined before
including `robin_map.h`. They claimed `<mutex>` "conflicts with our
std::mutex definition in cppcompat.hh", which stopped being true once the
wrapper moved into the `xamarin::android` namespace. The hack is still worth
keeping, but the real reason is that `<iostream>` and `<mutex>` would both
pull in libc++.

Finally, value-initialize `dso_handle_write_lock` for consistency with the
other static `Mutex` instances.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e
Drop the RAII guard in favour of calling pthread_mutex_lock/unlock
through Mutex directly. Critical sections that used to return, break or
continue while holding the lock now delegate to a `_locked` helper that
holds the branching logic, so each locked region has exactly one entry
and one exit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e
simonrozsival and others added 4 commits August 28, 2026 14:02
Drop the Mutex class and use pthread_mutex_t with
pthread_mutex_lock/unlock at the call sites. PTHREAD_MUTEX_INITIALIZER
keeps the static instances constant-initialized, so they still need no
thread-safe initialization guard.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e
Keep cppcompat.hh and the MonoVM hosts's use of it as they were. The
CoreCLR host no longer shares a mutex wrapper with MonoVM, so there is
no reason for this change to reach into src/native/mono.

Timing lives in the shared common sources, so it is still converted.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e
The previous commit split every locked region that contained an early
exit into an outer method (which locks) and an inner `_locked` method
(which holds the logic). That kept the lock/unlock pairing obvious, but
it introduced six new methods and a helper enum purely to work around
`return` statements.

Restructure the locked regions in place instead: use a result flag plus
`break`/`if`-`else` so control always falls through to the unlock, and
move the early `return` after it. This drops all six helpers along with
the `ReserveResult` enum and keeps the diff against the original code
much smaller.

* `writer_loop` uses `have_request` / `write_failed`
* `enqueue_write` restores the original `queue_full` bool and adds
  `writes_allowed`
* `get_available_sequence` uses `ret == nullptr` + `break`
* `open_assembly` inverts the `opendir` check and re-tests
  `override_dir_fd` after unlocking

No behavioural change. libc++ references are unchanged at 40 (CoreCLR)
and 0 (NativeAOT), and `__cxa_guard_*` stays at 8, confirming the
statics are still constant-initialized.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e
The previous two commits reshaped the locked regions to avoid returning
while the lock was held: first by splitting them into `_locked` helpers,
then by threading result flags so control fell through to the unlock.
Both worked, but both restructured code that did not otherwise need to
change.

Just call `pthread_mutex_unlock` immediately before the early `return`
instead. The regions keep their original shape, so the diff against the
pre-existing code is now purely mechanical -- a type change, a
`std::lock_guard` turning into a `pthread_mutex_lock`, and an added
unlock. Churn against the base drops from 145 changed lines to 62.

This is safe because the native runtime is built with `-fno-exceptions`
(verified in `compile_commands.json`), so there is no unwind path that
`std::lock_guard` would have covered and a manual unlock would miss.

Verified that every `return` inside a locked region is immediately
preceded by an unlock of that mutex, across all 13 regions. libc++
references remain 40 (CoreCLR) and 0 (NativeAOT), and `__cxa_guard_*`
stays at 8.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/clr-replace-std-mutex branch from 2507635 to df1152c Compare August 28, 2026 12:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

drop-libcpp Work to remove the libc++ dependency from Android NativeAOT

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants