Skip to content

Commit 876c57e

Browse files
[native] Replace std::mutex with pthread_mutex_t in the CoreCLR host (#12541)
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 | |---|---:|---:| | before | 6 | 13 | | after | **0** | **0** | It also drops eight undefined libc++ references: | 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. ### 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.
1 parent 19b0184 commit 876c57e

8 files changed

Lines changed: 64 additions & 24 deletions

File tree

src/native/clr/host/assembly-store.cc

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
#include <cstring>
55
#include <deque>
66
#include <memory>
7-
#include <mutex>
87
#include <string>
98

109
#include <dirent.h>
@@ -79,7 +78,7 @@ namespace {
7978
Failed,
8079
};
8180

82-
std::mutex state_lock;
81+
pthread_mutex_t state_lock = PTHREAD_MUTEX_INITIALIZER;
8382
std::deque<WriteRequest> write_queue;
8483
std::string cache_dir;
8584
std::unique_ptr<uint8_t*[]> tracking;
@@ -177,7 +176,7 @@ namespace {
177176
while (true) {
178177
WriteRequest request;
179178
{
180-
std::lock_guard lock (state_lock);
179+
lock_guard lock (state_lock);
181180
if (write_queue.empty ()) {
182181
writer_running = false;
183182
return nullptr;
@@ -192,7 +191,7 @@ namespace {
192191
request.data.reset ();
193192

194193
{
195-
std::lock_guard lock (state_lock);
194+
lock_guard lock (state_lock);
196195
queued_bytes -= request_size;
197196
if (write_result == WriteResult::Failed) {
198197
writes_enabled = false;
@@ -345,7 +344,7 @@ namespace {
345344
}
346345

347346
{
348-
std::lock_guard lock (state_lock);
347+
lock_guard lock (state_lock);
349348
writes_enabled = true;
350349
}
351350

@@ -426,7 +425,7 @@ namespace {
426425
size_t bytes_queued = 0;
427426
bool queue_full = false;
428427
{
429-
std::lock_guard lock (state_lock);
428+
lock_guard lock (state_lock);
430429
if (!writes_enabled) {
431430
return;
432431
}
@@ -463,7 +462,7 @@ namespace {
463462

464463
auto snapshot = std::unique_ptr<uint8_t[]> (new (std::nothrow) uint8_t[total]);
465464
if (snapshot == nullptr) {
466-
std::lock_guard lock (state_lock);
465+
lock_guard lock (state_lock);
467466
queued_bytes -= total;
468467
return;
469468
}
@@ -488,7 +487,7 @@ namespace {
488487
};
489488

490489
{
491-
std::lock_guard lock (state_lock);
490+
lock_guard lock (state_lock);
492491
if (!writes_enabled) {
493492
queued_bytes -= total;
494493
return;

src/native/clr/host/fastdev-assemblies.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ auto FastDevAssemblies::open_assembly (std::string_view const& name, int64_t &si
4545
// NOTE: override_dir will be kept open, we have no way of knowing when it will be no longer
4646
// needed
4747
if (override_dir_fd < 0) [[unlikely]] {
48-
std::lock_guard dir_lock { override_dir_lock };
48+
lock_guard dir_lock { override_dir_lock };
4949
if (override_dir_fd < 0) [[likely]] {
5050
override_dir = opendir (override_dir_path.c_str ());
5151
if (override_dir == nullptr) [[unlikely]] {

src/native/clr/include/host/assembly-store.hh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
#include <cstdint>
44
#include <functional>
55
#include <limits>
6-
#include <mutex>
76
#include <string>
87
#include <string_view>
98
#include <tuple>
109

10+
#include <runtime-base/mutex.hh>
1111
#include <xamarin-app.hh>
1212

1313
namespace xamarin::android {
@@ -36,6 +36,6 @@ namespace xamarin::android {
3636
// CRC32 hash collisions in the store index. Built once when the store is mapped.
3737
static inline std::string_view *assembly_store_names = nullptr;
3838
static inline uint64_t assembly_store_content_id = 0;
39-
static inline std::mutex assembly_decompress_mutex {};
39+
static inline pthread_mutex_t assembly_decompress_mutex = PTHREAD_MUTEX_INITIALIZER;
4040
};
4141
}

src/native/clr/include/host/fastdev-assemblies.hh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
#include <dirent.h>
44

55
#include <cstdint>
6-
#include <mutex>
76
#include <string>
87
#include <string_view>
98

9+
#include <runtime-base/mutex.hh>
10+
1011
namespace xamarin::android {
1112
class FastDevAssemblies
1213
{
@@ -30,7 +31,7 @@ namespace xamarin::android {
3031
#if defined(DEBUG)
3132
static inline DIR *override_dir = nullptr;
3233
static inline int override_dir_fd = -1;
33-
static inline std::mutex override_dir_lock {};
34+
static inline pthread_mutex_t override_dir_lock = PTHREAD_MUTEX_INITIALIZER;
3435
// Set by `build_tpa_list` when assemblies in the override directory are
3536
// passed to CoreCLR via `TRUSTED_PLATFORM_ASSEMBLIES`. When true, the
3637
// external assembly probe yields to TPA-based loading so that

src/native/clr/include/runtime-base/monodroid-dl.hh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
#pragma once
22

3-
#include <mutex>
43
#include <string_view>
54

65
#include <dlfcn.h>
76
#include <android/dlext.h>
87

98
#include <java-interop-dlfcn.h>
9+
#include <runtime-base/mutex.hh>
1010

1111
#include "../xamarin-app.hh"
1212

@@ -20,7 +20,7 @@ namespace xamarin::android
2020
{
2121
class MonodroidDl
2222
{
23-
static inline std::mutex dso_handle_write_lock;
23+
static inline pthread_mutex_t dso_handle_write_lock = PTHREAD_MUTEX_INITIALIZER;
2424

2525
[[gnu::always_inline]]
2626
static constexpr auto ascii_to_lower (char c) noexcept -> char
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#pragma once
2+
3+
#include <pthread.h>
4+
5+
namespace xamarin::android
6+
{
7+
// Scope-based locking without `std::lock_guard`, which would make the runtime depend on libc++.
8+
// See https://github.com/dotnet/android/issues/12533
9+
class lock_guard final
10+
{
11+
public:
12+
explicit lock_guard (pthread_mutex_t &mutex) noexcept
13+
: mutex (mutex)
14+
{
15+
pthread_mutex_lock (&mutex);
16+
}
17+
18+
~lock_guard () noexcept
19+
{
20+
pthread_mutex_unlock (&mutex);
21+
}
22+
23+
lock_guard (lock_guard const&) = delete;
24+
lock_guard (lock_guard&&) = delete;
25+
26+
auto operator= (lock_guard const&) -> lock_guard& = delete;
27+
auto operator= (lock_guard&&) -> lock_guard& = delete;
28+
29+
private:
30+
pthread_mutex_t &mutex;
31+
};
32+
}
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#pragma once
22

3-
#include <mutex>
3+
#include <runtime-base/mutex.hh>
44

55
#include "monodroid-state.hh"
66

@@ -9,21 +9,21 @@ namespace xamarin::android
99
class StartupAwareLock final
1010
{
1111
public:
12-
explicit StartupAwareLock (std::mutex &m)
12+
explicit StartupAwareLock (pthread_mutex_t &m)
1313
: lock (m)
1414
{
1515
if (MonodroidState::is_startup_in_progress ()) {
1616
// During startup we run without threads, do nothing
1717
return;
1818
}
19-
lock.lock ();
19+
pthread_mutex_lock (&lock);
2020
owns_lock = true;
2121
}
2222

2323
~StartupAwareLock ()
2424
{
2525
if (owns_lock) {
26-
lock.unlock ();
26+
pthread_mutex_unlock (&lock);
2727
}
2828
}
2929

@@ -33,7 +33,7 @@ namespace xamarin::android
3333
StartupAwareLock& operator= (StartupAwareLock const&) = delete;
3434

3535
private:
36-
std::mutex& lock;
36+
pthread_mutex_t &lock;
3737
bool owns_lock = false;
3838
};
3939
}

src/native/common/include/runtime-base/timing.hh

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#pragma once
22

3+
#include <pthread.h>
34
#include <sys/time.h>
45

56
#include <chrono>
6-
#include <mutex>
77
#include <vector>
88
#include <string_view>
99

@@ -32,6 +32,11 @@ namespace xamarin::android
3232
sequence_pool.resize (initial_pool_size);
3333
}
3434

35+
Timing (Timing const&) = delete;
36+
Timing (Timing&&) = delete;
37+
Timing& operator= (Timing const&) = delete;
38+
Timing& operator= (Timing&&) = delete;
39+
3540
static void info (managed_timing_sequence const *seq, const char *message)
3641
{
3742
do_log (LogLevel::Info, seq, message);
@@ -44,7 +49,7 @@ namespace xamarin::android
4449

4550
auto get_available_sequence () noexcept -> managed_timing_sequence*
4651
{
47-
std::lock_guard<std::mutex> lock (sequence_lock);
52+
pthread_mutex_lock (&sequence_lock);
4853

4954
managed_timing_sequence *ret;
5055
for (size_t i = 0uz; i < sequence_pool.size (); i++) {
@@ -55,11 +60,13 @@ namespace xamarin::android
5560
ret = &sequence_pool[i];
5661
ret->in_use = true;
5762

63+
pthread_mutex_unlock (&sequence_lock);
5864
return ret;
5965
}
6066
ret = &sequence_pool.emplace_back ();
6167
ret->in_use = true;
6268

69+
pthread_mutex_unlock (&sequence_lock);
6370
return ret;
6471
}
6572

@@ -69,10 +76,11 @@ namespace xamarin::android
6976
return;
7077
}
7178

72-
std::lock_guard<std::mutex> lock (sequence_lock);
79+
pthread_mutex_lock (&sequence_lock);
7380
sequence->start = time_point::min ();
7481
sequence->end = time_point::min ();
7582
sequence->in_use = false;
83+
pthread_mutex_unlock (&sequence_lock);
7684
}
7785

7886
private:
@@ -98,6 +106,6 @@ namespace xamarin::android
98106

99107
private:
100108
std::vector<managed_timing_sequence> sequence_pool;
101-
std::mutex sequence_lock;
109+
pthread_mutex_t sequence_lock = PTHREAD_MUTEX_INITIALIZER;
102110
};
103111
}

0 commit comments

Comments
 (0)