Skip to content

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

Merged
simonrozsival merged 13 commits into
mainfrom
dev/simonrozsival/clr-replace-std-mutex
Sep 3, 2026
Merged

[native] Replace std::mutex with pthread_mutex_t in the CoreCLR host#12541
simonrozsival merged 13 commits into
mainfrom
dev/simonrozsival/clr-replace-std-mutex

Conversation

@simonrozsival

@simonrozsivalsimonrozsival commented Aug 27, 2026

Copy link
Copy Markdown
Member

PR relationship: Prerequisite: #12534 (merged). This is the bottom of stack #12653.

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

std::mutex is a thin wrapper over pthread_mutex_t, but using it pulls <mutex> and out-of-line libc++ symbols into the native host. This PR stores pthread_mutex_t directly and provides a small CoreCLR RAII guard so locked scopes retain automatic unlock behavior without libc++.

What

  • Replace CoreCLR's std::mutex instances with pthread_mutex_t, initialized using PTHREAD_MUTEX_INITIALIZER.
  • Add xamarin::android::lock_guard under the CoreCLR include tree. It takes pthread_mutex_t& and calls pthread_mutex_lock / pthread_mutex_unlock.
  • Update CoreCLR's StartupAwareLock to take pthread_mutex_t& directly.
  • Convert shared Timing to direct pthread calls and keep it explicitly non-copyable and non-movable.
  • Leave src/native/mono unchanged; MonoVM retains its existing mutex and templated lock_guard implementation in mono/shared/cppcompat.hh.

PTHREAD_MUTEX_INITIALIZER keeps static instances constant-initialized, so this adds no thread-safe initialization guards. The CoreCLR guard is header-only and compiles down to direct pthread calls.

Effect

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

files including <mutex>std::mutex / std::lock_guard uses
before613
after00

It also drops eight undefined libc++ references:

symbolbeforeafter
std::__ndk1::mutex::lock()30
std::__ndk1::mutex::unlock()30
std::__ndk1::mutex::~mutex()20
refs__cxa_guard_*
#12534 (base)5510
this PR4710

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.

Verification

  • CoreCLR, MonoVM and NativeAOT build clean.
  • fastdev-assemblies.cc is #if defined(DEBUG) and was additionally compiled with -DDEBUG.
  • The CoreCLR guard compiled with the runtime's no-C++-exceptions settings references only pthread_mutex_lock and pthread_mutex_unlock; it introduces no C++ exception-runtime or initialization-guard symbols.
  • Real libc++ references: CoreCLR 55 → 47, NativeAOT 0. The eight removed references are exactly the mutex members listed above.
  • __cxa_guard_* undefined references remain at 10, confirming that the static mutexes remain constant-initialized.

CopilotAI 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
FileDescription
src/native/common/include/runtime-base/timing.hhSwitch timing sequence locking to Mutex/MutexGuard.
src/native/common/include/runtime-base/mutex.hhIntroduce pthread-backed Mutex and MutexGuard.
src/native/clr/include/runtime-base/startup-aware-lock.hhReplace std::mutex reference with Mutex.
src/native/clr/include/runtime-base/monodroid-dl.hhReplace static std::mutex with Mutex for DSO handle write lock.
src/native/clr/include/host/fastdev-assemblies.hhReplace override directory lock from std::mutex to Mutex.
src/native/clr/include/host/assembly-store.hhReplace assembly decompress lock from std::mutex to Mutex.
src/native/clr/host/fastdev-assemblies.ccReplace std::lock_guard usage with MutexGuard.
src/native/clr/host/assembly-store.ccReplace 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 threadsrc/native/common/include/runtime-base/mutex.hh Outdated
Comment threadsrc/native/clr/include/runtime-base/monodroid-dl.hh Outdated
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/clr-replace-std-mutex branch from 9b98570 to 83ca915CompareAugust 27, 2026 15:47
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/clr-replace-std-mutex branch from 83ca915 to d43d7d7CompareAugust 27, 2026 16:09
@simonrozsivalsimonrozsival 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 hostAug 27, 2026
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/clr-replace-std-mutex branch from 11cdb27 to 36f1cc4CompareAugust 27, 2026 19:29
@simonrozsival
simonrozsival changed the base branch from dev/simonrozsival/clr-timing-raw-pointer to dev/simonrozsival/clr-remove-std-formatAugust 27, 2026 19:34
@simonrozsivalsimonrozsival added the drop-libcpp Work to remove the libc++ dependency from Android NativeAOT label Aug 27, 2026
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/clr-replace-std-mutex branch from 36f1cc4 to 85c0bf3CompareAugust 27, 2026 21:42
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/clr-replace-std-mutex branch from 85c0bf3 to 875ef58CompareAugust 28, 2026 07:54
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/clr-replace-std-mutex branch from 875ef58 to a3811d6CompareAugust 28, 2026 08:47
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/clr-replace-std-mutex branch from a3811d6 to 82ea5c4CompareAugust 28, 2026 08:55
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/clr-replace-std-mutex branch from 82ea5c4 to ba25053CompareAugust 28, 2026 09:51
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/clr-replace-std-mutex branch from ba25053 to 2507635CompareAugust 28, 2026 10:29
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/clr-replace-std-mutex branch from 2507635 to df1152cCompareAugust 28, 2026 12:06
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/clr-replace-std-mutex branch from 7383635 to a6019dfCompareSeptember 2, 2026 11:56
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/clr-replace-std-mutex branch from a6019df to 2486f72CompareSeptember 2, 2026 13:20
Base automatically changed from dev/simonrozsival/clr-remove-std-format to mainSeptember 3, 2026 05:58
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/clr-replace-std-mutex branch from 2486f72 to 74fc6f2CompareSeptember 3, 2026 05:58
simonrozsivaland others added 7 commits September 3, 2026 08:13
`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
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

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

Generated by Android PR Reviewer for #12541

@github-actionsgithub-actionsBot 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.

⚠️ Needs Changes

Findings: 0 errors, 1 warning, 0 suggestions.

The direct pthread conversion is otherwise mechanically consistent, keeps the static mutexes constant-initialized, and preserves the startup-aware locking behavior. The remaining issue is that Timing no longer inherits std::mutex’s non-copyable semantics; its copy/move operations should be explicitly deleted before merging.

CI is still in progress: 41 of 44 checks had passed, with two running and one queued at review time; no failures were reported.

Generated by Android PR Reviewer for #12541 · gpt56 · 82.1 AIC · ⌖ 20.2 AIC · ⊞ 25.7K
Comment /review to run again

Comment threadsrc/native/common/include/runtime-base/timing.hh
simonrozsivaland others added 6 commits September 3, 2026 12:43
Explicitly delete copy and move operations after replacing std::mutex with pthread_mutex_t.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Move the existing MonoVM pthread mutex abstraction into the shared runtime headers and use its RAII guard throughout CoreCLR. This keeps one non-copyable mutex implementation across runtime flavors while preserving constant initialization and avoiding libc++ symbols.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Store pthread_mutex_t directly at every call site and have the shared RAII guard lock and unlock it without an intermediate Mutex class. Keep Timing explicitly non-copyable because it owns mutex storage.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Rename MutexGuard to pthread_mutex_guard to make its direct pthread_mutex_t ownership explicit.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use xamarin::android::lock_guard for the concrete pthread_mutex_t RAII guard.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Restore src/native/mono to the PR base, move the new lock_guard into the CoreCLR include tree, and keep shared Timing on direct pthread calls so MonoVM does not consume the CoreCLR helper.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@simonrozsivalsimonrozsival added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label Sep 3, 2026
@simonrozsival
simonrozsival merged commit 876c57e into mainSep 3, 2026
44 checks passed
@simonrozsival
simonrozsival deleted the dev/simonrozsival/clr-replace-std-mutex branch September 3, 2026 16:41
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

drop-libcppWork to remove the libc++ dependency from Android NativeAOTready-to-reviewThis PR is ready to review/merge, I think any CI failures are just flaky (ignorable).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@simonrozsival@rolfbjarne