[libc-commits] [libc] 41fecca - [libc] add rwlock (#94156)
via libc-commits
libc-commits at lists.llvm.org
Fri Jun 14 13:34:33 PDT 2024
Author: Schrodinger ZHU Yifan
Date: 2024-06-14T13:34:28-07:00
New Revision: 41fecca97b77a80926cb1b1a83c8af3c90d354bd
URL: https://github.com/llvm/llvm-project/commit/41fecca97b77a80926cb1b1a83c8af3c90d354bd
DIFF: https://github.com/llvm/llvm-project/commit/41fecca97b77a80926cb1b1a83c8af3c90d354bd.diff
LOG: [libc] add rwlock (#94156)
Added:
libc/include/llvm-libc-types/pthread_rwlock_t.h
libc/src/__support/threads/linux/rwlock.h
libc/src/pthread/pthread_rwlock_destroy.cpp
libc/src/pthread/pthread_rwlock_destroy.h
libc/src/pthread/pthread_rwlock_init.cpp
libc/src/pthread/pthread_rwlock_init.h
libc/src/pthread/pthread_rwlock_rdlock.cpp
libc/src/pthread/pthread_rwlock_rdlock.h
libc/src/pthread/pthread_rwlock_timedrdlock.cpp
libc/src/pthread/pthread_rwlock_timedrdlock.h
libc/src/pthread/pthread_rwlock_timedwrlock.cpp
libc/src/pthread/pthread_rwlock_timedwrlock.h
libc/src/pthread/pthread_rwlock_tryrdlock.cpp
libc/src/pthread/pthread_rwlock_tryrdlock.h
libc/src/pthread/pthread_rwlock_trywrlock.cpp
libc/src/pthread/pthread_rwlock_trywrlock.h
libc/src/pthread/pthread_rwlock_unlock.cpp
libc/src/pthread/pthread_rwlock_unlock.h
libc/src/pthread/pthread_rwlock_wrlock.cpp
libc/src/pthread/pthread_rwlock_wrlock.h
libc/test/integration/src/pthread/pthread_rwlock_test.cpp
Modified:
libc/config/config.json
libc/config/linux/aarch64/entrypoints.txt
libc/config/linux/api.td
libc/config/linux/x86_64/entrypoints.txt
libc/docs/configure.rst
libc/include/CMakeLists.txt
libc/include/llvm-libc-types/CMakeLists.txt
libc/include/pthread.h.def
libc/spec/posix.td
libc/spec/spec.td
libc/src/__support/macros/attributes.h
libc/src/__support/threads/linux/CMakeLists.txt
libc/src/pthread/CMakeLists.txt
libc/test/integration/src/pthread/CMakeLists.txt
Removed:
################################################################################
diff --git a/libc/config/config.json b/libc/config/config.json
index d3d1ff1e28716..8d6a84e732597 100644
--- a/libc/config/config.json
+++ b/libc/config/config.json
@@ -49,6 +49,10 @@
"LIBC_CONF_RAW_MUTEX_DEFAULT_SPIN_COUNT": {
"value": 100,
"doc": "Default number of spins before blocking if a mutex is in contention (default to 100)."
+ },
+ "LIBC_CONF_RWLOCK_DEFAULT_SPIN_COUNT": {
+ "value": 100,
+ "doc": "Default number of spins before blocking if a rwlock is in contention (default to 100)."
}
}
}
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index f2f9803523df2..905f7e4871384 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -645,6 +645,15 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.pthread.pthread_mutexattr_setrobust
libc.src.pthread.pthread_mutexattr_settype
libc.src.pthread.pthread_once
+ libc.src.pthread.pthread_rwlock_init
+ libc.src.pthread.pthread_rwlock_tryrdlock
+ libc.src.pthread.pthread_rwlock_rdlock
+ libc.src.pthread.pthread_rwlock_timedrdlock
+ libc.src.pthread.pthread_rwlock_trywrlock
+ libc.src.pthread.pthread_rwlock_wrlock
+ libc.src.pthread.pthread_rwlock_timedwrlock
+ libc.src.pthread.pthread_rwlock_unlock
+ libc.src.pthread.pthread_rwlock_destroy
libc.src.pthread.pthread_rwlockattr_destroy
libc.src.pthread.pthread_rwlockattr_getkind_np
libc.src.pthread.pthread_rwlockattr_getpshared
diff --git a/libc/config/linux/api.td b/libc/config/linux/api.td
index 902839b3e5b8f..eb0090c80b0da 100644
--- a/libc/config/linux/api.td
+++ b/libc/config/linux/api.td
@@ -181,6 +181,7 @@ def PThreadAPI : PublicAPI<"pthread.h"> {
"pthread_mutexattr_t",
"pthread_once_t",
"pthread_rwlockattr_t",
+ "pthread_rwlock_t",
"pthread_t",
];
}
@@ -270,6 +271,7 @@ def SysTypesAPI : PublicAPI<"sys/types.h"> {
"pthread_mutexattr_t",
"pthread_once_t",
"pthread_rwlockattr_t",
+ "pthread_rwlock_t",
"pthread_t",
"size_t",
"ssize_t",
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 45914fe9f7ad2..a5a634f61b7e7 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -726,6 +726,15 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.pthread.pthread_mutexattr_setrobust
libc.src.pthread.pthread_mutexattr_settype
libc.src.pthread.pthread_once
+ libc.src.pthread.pthread_rwlock_init
+ libc.src.pthread.pthread_rwlock_tryrdlock
+ libc.src.pthread.pthread_rwlock_rdlock
+ libc.src.pthread.pthread_rwlock_timedrdlock
+ libc.src.pthread.pthread_rwlock_trywrlock
+ libc.src.pthread.pthread_rwlock_wrlock
+ libc.src.pthread.pthread_rwlock_timedwrlock
+ libc.src.pthread.pthread_rwlock_unlock
+ libc.src.pthread.pthread_rwlock_destroy
libc.src.pthread.pthread_rwlockattr_destroy
libc.src.pthread.pthread_rwlockattr_getkind_np
libc.src.pthread.pthread_rwlockattr_getpshared
diff --git a/libc/docs/configure.rst b/libc/docs/configure.rst
index 77ade07714fdf..bdae6c54052f2 100644
--- a/libc/docs/configure.rst
+++ b/libc/docs/configure.rst
@@ -36,6 +36,7 @@ to learn about the defaults for your platform and target.
- ``LIBC_CONF_PRINTF_FLOAT_TO_STR_USE_MEGA_LONG_DOUBLE_TABLE``: Use large table for better printf long double performance.
* **"pthread" options**
- ``LIBC_CONF_RAW_MUTEX_DEFAULT_SPIN_COUNT``: Default number of spins before blocking if a mutex is in contention (default to 100).
+ - ``LIBC_CONF_RWLOCK_DEFAULT_SPIN_COUNT``: Default number of spins before blocking if a rwlock is in contention (default to 100).
- ``LIBC_CONF_TIMEOUT_ENSURE_MONOTONICITY``: Automatically adjust timeout to CLOCK_MONOTONIC (default to true). POSIX API may require CLOCK_REALTIME, which can be unstable and leading to unexpected behavior. This option will convert the real-time timestamp to monotonic timestamp relative to the time of call.
* **"string" options**
- ``LIBC_CONF_MEMSET_X86_USE_SOFTWARE_PREFETCHING``: Inserts prefetch for write instructions (PREFETCHW) for memset on x86 to recover performance when hardware prefetcher is disabled.
diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt
index 2a41ec46abdab..bb10fd4c94703 100644
--- a/libc/include/CMakeLists.txt
+++ b/libc/include/CMakeLists.txt
@@ -332,6 +332,7 @@ add_gen_header(
.llvm-libc-types.pthread_mutex_t
.llvm-libc-types.pthread_mutexattr_t
.llvm-libc-types.pthread_once_t
+ .llvm-libc-types.pthread_rwlock_t
.llvm-libc-types.pthread_rwlockattr_t
.llvm-libc-types.pthread_t
)
diff --git a/libc/include/llvm-libc-types/CMakeLists.txt b/libc/include/llvm-libc-types/CMakeLists.txt
index 356ac037770d2..d8b975572e0dd 100644
--- a/libc/include/llvm-libc-types/CMakeLists.txt
+++ b/libc/include/llvm-libc-types/CMakeLists.txt
@@ -54,6 +54,7 @@ add_header(pthread_key_t HDR pthread_key_t.h)
add_header(pthread_mutex_t HDR pthread_mutex_t.h DEPENDS .__futex_word .__mutex_type)
add_header(pthread_mutexattr_t HDR pthread_mutexattr_t.h)
add_header(pthread_once_t HDR pthread_once_t.h DEPENDS .__futex_word)
+add_header(pthread_rwlock_t HDR pthread_rwlock_t.h DEPENDS .__futex_word .pid_t)
add_header(pthread_rwlockattr_t HDR pthread_rwlockattr_t.h)
add_header(pthread_t HDR pthread_t.h DEPENDS .__thread_type)
add_header(rlim_t HDR rlim_t.h)
diff --git a/libc/include/llvm-libc-types/pthread_rwlock_t.h b/libc/include/llvm-libc-types/pthread_rwlock_t.h
new file mode 100644
index 0000000000000..da49a155a2bdb
--- /dev/null
+++ b/libc/include/llvm-libc-types/pthread_rwlock_t.h
@@ -0,0 +1,26 @@
+//===-- Definition of pthread_mutex_t type --------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_TYPES_PTHREAD_RWLOCK_T_H
+#define LLVM_LIBC_TYPES_PTHREAD_RWLOCK_T_H
+
+#include "llvm-libc-types/__futex_word.h"
+#include "llvm-libc-types/pid_t.h"
+typedef struct {
+ unsigned __is_pshared : 1;
+ unsigned __preference : 1;
+ int __state;
+ pid_t __writer_tid;
+ __futex_word __wait_queue_mutex;
+ __futex_word __pending_readers;
+ __futex_word __pending_writers;
+ __futex_word __reader_serialization;
+ __futex_word __writer_serialization;
+} pthread_rwlock_t;
+
+#endif // LLVM_LIBC_TYPES_PTHREAD_RWLOCK_T_H
diff --git a/libc/include/pthread.h.def b/libc/include/pthread.h.def
index d41273b5590ea..4dbeed6b5f321 100644
--- a/libc/include/pthread.h.def
+++ b/libc/include/pthread.h.def
@@ -17,6 +17,7 @@
#define PTHREAD_STACK_MIN (1 << 14) // 16KB
#define PTHREAD_MUTEX_INITIALIZER {0}
+#define PTHREAD_RWLOCK_INITIALIZER {}
#define PTHREAD_ONCE_INIT {0}
enum {
diff --git a/libc/spec/posix.td b/libc/spec/posix.td
index e16353b8142de..40b9fa09dd61c 100644
--- a/libc/spec/posix.td
+++ b/libc/spec/posix.td
@@ -113,6 +113,8 @@ def POSIX : StandardSpec<"POSIX"> {
NamedType PThreadRWLockAttrTType = NamedType<"pthread_rwlockattr_t">;
PtrType PThreadRWLockAttrTPtr = PtrType<PThreadRWLockAttrTType>;
ConstType ConstPThreadRWLockAttrTPtr = ConstType<PThreadRWLockAttrTPtr>;
+ RestrictedPtrType RestrictedPThreadRWLockAttrTPtr = RestrictedPtrType<PThreadRWLockAttrTType>;
+ ConstType ConstRestrictedPThreadRWLockAttrTPtr = ConstType<RestrictedPThreadRWLockAttrTPtr>;
NamedType PThreadMutexAttrTType = NamedType<"pthread_mutexattr_t">;
PtrType PThreadMutexAttrTPtr = PtrType<PThreadMutexAttrTType>;
@@ -126,6 +128,10 @@ def POSIX : StandardSpec<"POSIX"> {
ConstType ConstPThreadMutexTPtr = ConstType<PThreadMutexTPtr>;
ConstType ConstRestrictedPThreadMutexTPtr = ConstType<RestrictedPThreadMutexTPtr>;
+ NamedType PThreadRWLockTType = NamedType<"pthread_rwlock_t">;
+ PtrType PThreadRWLockTPtr = PtrType<PThreadRWLockTType>;
+ RestrictedPtrType RestrictedPThreadRWLockTPtr = RestrictedPtrType<PThreadRWLockTType>;
+
PtrType PThreadTPtr = PtrType<PThreadTType>;
RestrictedPtrType RestrictedPThreadTPtr = RestrictedPtrType<PThreadTType>;
@@ -1003,6 +1009,7 @@ def POSIX : StandardSpec<"POSIX"> {
PThreadOnceCallback,
PThreadOnceT,
PThreadRWLockAttrTType,
+ PThreadRWLockTType,
PThreadStartT,
PThreadTSSDtorT,
PThreadTType,
@@ -1259,6 +1266,51 @@ def POSIX : StandardSpec<"POSIX"> {
RetValSpec<IntType>,
[ArgSpec<PThreadRWLockAttrTPtr>, ArgSpec<IntType>]
>,
+ FunctionSpec<
+ "pthread_rwlock_init",
+ RetValSpec<IntType>,
+ [ArgSpec<PThreadRWLockTPtr>, ArgSpec<ConstRestrictedPThreadRWLockAttrTPtr>]
+ >,
+ FunctionSpec<
+ "pthread_rwlock_tryrdlock",
+ RetValSpec<IntType>,
+ [ArgSpec<PThreadRWLockTPtr>]
+ >,
+ FunctionSpec<
+ "pthread_rwlock_trywrlock",
+ RetValSpec<IntType>,
+ [ArgSpec<PThreadRWLockTPtr>]
+ >,
+ FunctionSpec<
+ "pthread_rwlock_timedrdlock",
+ RetValSpec<IntType>,
+ [ArgSpec<RestrictedPThreadRWLockTPtr>, ArgSpec<ConstRestrictStructTimeSpecPtr>]
+ >,
+ FunctionSpec<
+ "pthread_rwlock_timedwrlock",
+ RetValSpec<IntType>,
+ [ArgSpec<RestrictedPThreadRWLockTPtr>, ArgSpec<ConstRestrictStructTimeSpecPtr>]
+ >,
+ FunctionSpec<
+ "pthread_rwlock_rdlock",
+ RetValSpec<IntType>,
+ [ArgSpec<PThreadRWLockTPtr>]
+ >,
+ FunctionSpec<
+ "pthread_rwlock_wrlock",
+ RetValSpec<IntType>,
+ [ArgSpec<PThreadRWLockTPtr>]
+ >,
+ FunctionSpec<
+ "pthread_rwlock_unlock",
+ RetValSpec<IntType>,
+ [ArgSpec<PThreadRWLockTPtr>]
+ >,
+ FunctionSpec<
+ "pthread_rwlock_destroy",
+ RetValSpec<IntType>,
+ [ArgSpec<PThreadRWLockTPtr>]
+ >,
]
>;
@@ -1616,6 +1668,7 @@ def POSIX : StandardSpec<"POSIX"> {
PThreadMutexTType,
PThreadOnceT,
PThreadRWLockAttrTType,
+ PThreadRWLockTType,
PThreadTType,
PidT,
SSizeTType,
diff --git a/libc/spec/spec.td b/libc/spec/spec.td
index 7e1283e67fab2..a3a5db7465b39 100644
--- a/libc/spec/spec.td
+++ b/libc/spec/spec.td
@@ -126,6 +126,8 @@ def TimeTType : NamedType<"time_t">;
def StructTimeSpec : NamedType<"struct timespec">;
def StructTimeSpecPtr : PtrType<StructTimeSpec>;
def ConstStructTimeSpecPtr : ConstType<StructTimeSpecPtr>;
+def RestrictStructTimeSpecPtr : RestrictedPtrType<StructTimeSpec>;
+def ConstRestrictStructTimeSpecPtr : ConstType<RestrictStructTimeSpecPtr>;
def BSearchCompareT : NamedType<"__bsearchcompare_t">;
def QSortCompareT : NamedType<"__qsortcompare_t">;
diff --git a/libc/src/__support/macros/attributes.h b/libc/src/__support/macros/attributes.h
index c489a288fbb11..7e8e2ddfac9b1 100644
--- a/libc/src/__support/macros/attributes.h
+++ b/libc/src/__support/macros/attributes.h
@@ -42,4 +42,10 @@
#define LIBC_CONSTINIT
#endif
+#ifdef __clang__
+#define LIBC_PREFERED_TYPE(TYPE) [[clang::preferred_type(TYPE)]]
+#else
+#define LIBC_PREFERED_TYPE(TYPE)
+#endif
+
#endif // LLVM_LIBC_SRC___SUPPORT_MACROS_ATTRIBUTES_H
diff --git a/libc/src/__support/threads/linux/CMakeLists.txt b/libc/src/__support/threads/linux/CMakeLists.txt
index 8e6cd7227b2c8..95e509b7a825d 100644
--- a/libc/src/__support/threads/linux/CMakeLists.txt
+++ b/libc/src/__support/threads/linux/CMakeLists.txt
@@ -22,11 +22,11 @@ add_header_library(
libc.src.__support.time.linux.abs_timeout
)
-set(raw_mutex_additional_flags)
+set(monotonicity_flags)
if (LIBC_CONF_TIMEOUT_ENSURE_MONOTONICITY)
- set(raw_mutex_additional_flags -DLIBC_COPT_TIMEOUT_ENSURE_MONOTONICITY=1)
+ set(monotonicity_flags -DLIBC_COPT_TIMEOUT_ENSURE_MONOTONICITY=1)
else()
- set(raw_mutex_additional_flags -DLIBC_COPT_TIMEOUT_ENSURE_MONOTONICITY=0)
+ set(monotonicity_flags -DLIBC_COPT_TIMEOUT_ENSURE_MONOTONICITY=0)
endif()
add_header_library(
@@ -42,8 +42,22 @@ add_header_library(
libc.hdr.types.pid_t
COMPILE_OPTIONS
-DLIBC_COPT_RAW_MUTEX_DEFAULT_SPIN_COUNT=${LIBC_CONF_RAW_MUTEX_DEFAULT_SPIN_COUNT}
- ${raw_mutex_additional_flags}
-
+ ${monotonicity_flags}
+)
+
+add_header_library(
+ rwlock
+ HDRS
+ rwlock.h
+ DEPENDS
+ .futex_utils
+ .raw_mutex
+ libc.src.__support.common
+ libc.src.__support.OSUtil.osutil
+ libc.src.__support.CPP.limits
+ COMPILE_OPTIONS
+ -DLIBC_COPT_RWLOCK_DEFAULT_SPIN_COUNT=${LIBC_CONF_RWLOCK_DEFAULT_SPIN_COUNT}
+ ${monotonicity_flags}
)
add_header_library(
diff --git a/libc/src/__support/threads/linux/rwlock.h b/libc/src/__support/threads/linux/rwlock.h
new file mode 100644
index 0000000000000..5db0590fd6aa0
--- /dev/null
+++ b/libc/src/__support/threads/linux/rwlock.h
@@ -0,0 +1,556 @@
+//===--- Implementation of a Linux RwLock class ---------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_LIBC_SRC_SUPPORT_THREADS_LINUX_RWLOCK_H
+#define LLVM_LIBC_SRC_SUPPORT_THREADS_LINUX_RWLOCK_H
+
+#include "hdr/errno_macros.h"
+#include "hdr/types/pid_t.h"
+#include "src/__support/CPP/atomic.h"
+#include "src/__support/CPP/limits.h"
+#include "src/__support/CPP/optional.h"
+#include "src/__support/OSUtil/syscall.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_assert.h"
+#include "src/__support/macros/attributes.h"
+#include "src/__support/macros/optimization.h"
+#include "src/__support/threads/linux/futex_utils.h"
+#include "src/__support/threads/linux/futex_word.h"
+#include "src/__support/threads/linux/raw_mutex.h"
+#include "src/__support/threads/sleep.h"
+
+#ifndef LIBC_COPT_RWLOCK_DEFAULT_SPIN_COUNT
+#define LIBC_COPT_RWLOCK_DEFAULT_SPIN_COUNT 100
+#endif
+
+#ifndef LIBC_COPT_TIMEOUT_ENSURE_MONOTONICITY
+#define LIBC_COPT_TIMEOUT_ENSURE_MONOTONICITY 1
+#warning "LIBC_COPT_TIMEOUT_ENSURE_MONOTONICITY is not defined, defaulting to 1"
+#endif
+
+#if LIBC_COPT_TIMEOUT_ENSURE_MONOTONICITY
+#include "src/__support/time/linux/monotonicity.h"
+#endif
+
+namespace LIBC_NAMESPACE {
+// Forward declaration of the RwLock class.
+class RwLock;
+// A namespace to rwlock specific utilities.
+namespace rwlock {
+// The role of the thread in the RwLock.
+enum class Role { Reader = 0, Writer = 1 };
+
+// A waiting queue to keep track of the pending readers and writers.
+class WaitingQueue final : private RawMutex {
+ /* FutexWordType raw_mutex; (from base class) */
+
+ // Pending reader count (protected by the mutex)
+ FutexWordType pending_readers;
+ // Pending writer count (protected by the mutex)
+ FutexWordType pending_writers;
+ // Reader serialization (increases on each reader-waking operation)
+ Futex reader_serialization;
+ // Writer serialization (increases on each writer-waking operation)
+ Futex writer_serialization;
+
+public:
+ // RAII guard to lock and unlock the waiting queue.
+ class Guard {
+ WaitingQueue &queue;
+ bool is_pshared;
+
+ LIBC_INLINE constexpr Guard(WaitingQueue &queue, bool is_pshared)
+ : queue(queue), is_pshared(is_pshared) {
+ queue.lock(cpp::nullopt, is_pshared);
+ }
+
+ public:
+ LIBC_INLINE ~Guard() { queue.unlock(is_pshared); }
+ template <Role role> LIBC_INLINE FutexWordType &pending_count() {
+ if constexpr (role == Role::Reader)
+ return queue.pending_readers;
+ else
+ return queue.pending_writers;
+ }
+ template <Role role> LIBC_INLINE FutexWordType &serialization() {
+ if constexpr (role == Role::Reader)
+ return queue.reader_serialization.val;
+ else
+ return queue.writer_serialization.val;
+ }
+ friend WaitingQueue;
+ };
+
+public:
+ LIBC_INLINE constexpr WaitingQueue()
+ : RawMutex(), pending_readers(0), pending_writers(0),
+ reader_serialization(0), writer_serialization(0) {}
+
+ LIBC_INLINE Guard acquire(bool is_pshared) {
+ return Guard(*this, is_pshared);
+ }
+
+ template <Role role>
+ LIBC_INLINE long wait(FutexWordType expected,
+ cpp::optional<Futex::Timeout> timeout,
+ bool is_pshared) {
+ if constexpr (role == Role::Reader)
+ return reader_serialization.wait(expected, timeout, is_pshared);
+ else
+ return writer_serialization.wait(expected, timeout, is_pshared);
+ }
+
+ template <Role role> LIBC_INLINE long notify(bool is_pshared) {
+ if constexpr (role == Role::Reader)
+ return reader_serialization.notify_all(is_pshared);
+ else
+ return writer_serialization.notify_one(is_pshared);
+ }
+};
+
+// The RwState of the RwLock is stored in an integer word, consisting of the
+// following components:
+// -----------------------------------------------
+// | Range | Description |
+// ===============================================
+// | 0 | Pending Reader Bit |
+// -----------------------------------------------
+// | 1 | Pending Writer Bit |
+// -----------------------------------------------
+// | [2, MSB) | Active Reader Count |
+// -----------------------------------------------
+// | MSB | Active Writer Bit |
+// -----------------------------------------------
+class RwState {
+ // Shift amounts to access the components of the state.
+ LIBC_INLINE_VAR static constexpr int PENDING_READER_SHIFT = 0;
+ LIBC_INLINE_VAR static constexpr int PENDING_WRITER_SHIFT = 1;
+ LIBC_INLINE_VAR static constexpr int ACTIVE_READER_SHIFT = 2;
+ LIBC_INLINE_VAR static constexpr int ACTIVE_WRITER_SHIFT =
+ cpp::numeric_limits<int>::digits;
+
+ // Bitmasks to access the components of the state.
+ LIBC_INLINE_VAR static constexpr int PENDING_READER_BIT =
+ 1 << PENDING_READER_SHIFT;
+ LIBC_INLINE_VAR static constexpr int PENDING_WRITER_BIT =
+ 1 << PENDING_WRITER_SHIFT;
+ LIBC_INLINE_VAR static constexpr int ACTIVE_READER_COUNT_UNIT =
+ 1 << ACTIVE_READER_SHIFT;
+ LIBC_INLINE_VAR static constexpr int ACTIVE_WRITER_BIT =
+ 1 << ACTIVE_WRITER_SHIFT;
+ LIBC_INLINE_VAR static constexpr int PENDING_MASK =
+ PENDING_READER_BIT | PENDING_WRITER_BIT;
+
+private:
+ // We use the signed integer as the state type. It is easier
+ // to reason about the state transitions using signness.
+ int state;
+
+public:
+ // Construction and conversion functions.
+ LIBC_INLINE constexpr RwState(int state = 0) : state(state) {}
+ LIBC_INLINE constexpr operator int() const { return state; }
+
+ // Utilities to check the state of the RwLock.
+ LIBC_INLINE constexpr bool has_active_writer() const { return state < 0; }
+ LIBC_INLINE constexpr bool has_active_reader() const {
+ return state >= ACTIVE_READER_COUNT_UNIT;
+ }
+ LIBC_INLINE constexpr bool has_acitve_owner() const {
+ return has_active_reader() || has_active_writer();
+ }
+ LIBC_INLINE constexpr bool has_last_reader() const {
+ return (state >> ACTIVE_READER_SHIFT) == 1;
+ }
+ LIBC_INLINE constexpr bool has_pending_writer() const {
+ return state & PENDING_WRITER_BIT;
+ }
+ LIBC_INLINE constexpr bool has_pending() const {
+ return state & PENDING_MASK;
+ }
+
+ LIBC_INLINE constexpr RwState set_writer_bit() const {
+ return RwState(state | ACTIVE_WRITER_BIT);
+ }
+
+ // The preference parameter changes the behavior of the lock acquisition
+ // if there are both readers and writers waiting for the lock. If writers
+ // are preferred, reader acquisition will be blocked until all pending
+ // writers are served.
+ template <Role role> LIBC_INLINE bool can_acquire(Role preference) const {
+ if constexpr (role == Role::Reader) {
+ switch (preference) {
+ case Role::Reader:
+ return !has_active_writer();
+ case Role::Writer:
+ return !has_active_writer() && !has_pending_writer();
+ }
+ } else
+ return !has_acitve_owner();
+ }
+
+ // This function check if it is possible to grow the reader count without
+ // overflowing the state.
+ LIBC_INLINE cpp::optional<RwState> try_increase_reader_count() const {
+ LIBC_ASSERT(!has_active_writer() &&
+ "try_increase_reader_count shall only be called when there "
+ "is no active writer.");
+ RwState res;
+ if (LIBC_UNLIKELY(__builtin_sadd_overflow(state, ACTIVE_READER_COUNT_UNIT,
+ &res.state)))
+ return cpp::nullopt;
+ return res;
+ }
+
+ // Utilities to do atomic operations on the state.
+ LIBC_INLINE static RwState fetch_sub_reader_count(cpp::Atomic<int> &target,
+ cpp::MemoryOrder order) {
+ return RwState(target.fetch_sub(ACTIVE_READER_COUNT_UNIT, order));
+ }
+
+ LIBC_INLINE static RwState load(cpp::Atomic<int> &target,
+ cpp::MemoryOrder order) {
+ return RwState(target.load(order));
+ }
+
+ template <Role role>
+ LIBC_INLINE static RwState fetch_set_pending_bit(cpp::Atomic<int> &target,
+ cpp::MemoryOrder order) {
+ if constexpr (role == Role::Reader)
+ return RwState(target.fetch_or(PENDING_READER_BIT, order));
+ else
+ return RwState(target.fetch_or(PENDING_WRITER_BIT, order));
+ }
+ template <Role role>
+ LIBC_INLINE static RwState fetch_clear_pending_bit(cpp::Atomic<int> &target,
+ cpp::MemoryOrder order) {
+ if constexpr (role == Role::Reader)
+ return RwState(target.fetch_and(~PENDING_READER_BIT, order));
+ else
+ return RwState(target.fetch_and(~PENDING_WRITER_BIT, order));
+ }
+
+ LIBC_INLINE static RwState fetch_clear_active_writer(cpp::Atomic<int> &target,
+ cpp::MemoryOrder order) {
+ return RwState(target.fetch_and(~ACTIVE_WRITER_BIT, order));
+ }
+
+ LIBC_INLINE bool compare_exchange_weak_with(cpp::Atomic<int> &target,
+ RwState desired,
+ cpp::MemoryOrder success_order,
+ cpp::MemoryOrder failure_order) {
+ return target.compare_exchange_weak(state, desired, success_order,
+ failure_order);
+ }
+
+ // Utilities to spin and reload the state.
+private:
+ template <class F>
+ LIBC_INLINE static RwState spin_reload_until(cpp::Atomic<int> &target,
+ F &&func, unsigned spin_count) {
+ for (;;) {
+ auto state = RwState::load(target, cpp::MemoryOrder::RELAXED);
+ if (func(state) || spin_count == 0)
+ return state;
+ sleep_briefly();
+ spin_count--;
+ }
+ }
+
+public:
+ template <Role role>
+ LIBC_INLINE static RwState spin_reload(cpp::Atomic<int> &target,
+ Role preference, unsigned spin_count) {
+ if constexpr (role == Role::Reader) {
+ // Return the reader state if either the lock is available or there is
+ // any ongoing contention.
+ return spin_reload_until(
+ target,
+ [=](RwState state) {
+ return state.can_acquire<Role::Reader>(preference) ||
+ state.has_pending();
+ },
+ spin_count);
+ } else {
+ // Return the writer state if either the lock is available or there is
+ // any contention *between writers*. Since writers can be way less than
+ // readers, we allow them to spin more to improve the fairness.
+ return spin_reload_until(
+ target,
+ [=](RwState state) {
+ return state.can_acquire<Role::Writer>(preference) ||
+ state.has_pending_writer();
+ },
+ spin_count);
+ }
+ }
+
+ friend class RwLockTester;
+};
+} // namespace rwlock
+
+class RwLock {
+ using RwState = rwlock::RwState;
+ using Role = rwlock::Role;
+ using WaitingQueue = rwlock::WaitingQueue;
+
+public:
+ // Return types for the lock functions.
+ // All the locking routines returning this type are marked as [[nodiscard]]
+ // because it is a common error to assume the lock success without checking
+ // the return value, which can lead to undefined behaviors or other subtle
+ // bugs that are hard to reason about.
+ enum class LockResult : int {
+ Success = 0,
+ TimedOut = ETIMEDOUT,
+ Overflow = EAGAIN, /* EAGAIN is specified in the standard for overflow. */
+ Busy = EBUSY,
+ Deadlock = EDEADLOCK,
+ PermissionDenied = EPERM,
+ };
+
+private:
+ // Whether the RwLock is shared between processes.
+ LIBC_PREFERED_TYPE(bool)
+ unsigned is_pshared : 1;
+ // Reader/Writer preference.
+ LIBC_PREFERED_TYPE(Role)
+ unsigned preference : 1;
+ // RwState to keep track of the RwLock.
+ cpp::Atomic<int> state;
+ // writer_tid is used to keep track of the thread id of the writer. Notice
+ // that TLS address is not a good idea here since it may remains the same
+ // across forked processes.
+ cpp::Atomic<pid_t> writer_tid;
+ // Waiting queue to keep track of the readers and writers.
+ WaitingQueue queue;
+
+private:
+ // Load the bitfield preference.
+ LIBC_INLINE Role get_preference() const {
+ return static_cast<Role>(preference);
+ }
+ // TODO: use cached thread id once implemented.
+ LIBC_INLINE static pid_t gettid() { return syscall_impl<pid_t>(SYS_gettid); }
+
+ template <Role role> LIBC_INLINE LockResult try_lock(RwState &old) {
+ if constexpr (role == Role::Reader) {
+ while (LIBC_LIKELY(old.can_acquire<Role::Reader>(get_preference()))) {
+ cpp::optional<RwState> next = old.try_increase_reader_count();
+ if (!next)
+ return LockResult::Overflow;
+ if (LIBC_LIKELY(old.compare_exchange_weak_with(
+ state, *next, cpp::MemoryOrder::ACQUIRE,
+ cpp::MemoryOrder::RELAXED)))
+ return LockResult::Success;
+ // Notice that old is updated by the compare_exchange_weak_with
+ // function.
+ }
+ return LockResult::Busy;
+ } else {
+ // This while loop should terminate quickly
+ while (LIBC_LIKELY(old.can_acquire<Role::Writer>(get_preference()))) {
+ if (LIBC_LIKELY(old.compare_exchange_weak_with(
+ state, old.set_writer_bit(), cpp::MemoryOrder::ACQUIRE,
+ cpp::MemoryOrder::RELAXED))) {
+ writer_tid.store(gettid(), cpp::MemoryOrder::RELAXED);
+ return LockResult::Success;
+ }
+ // Notice that old is updated by the compare_exchange_weak_with
+ // function.
+ }
+ return LockResult::Busy;
+ }
+ }
+
+public:
+ LIBC_INLINE constexpr RwLock(Role preference = Role::Reader,
+ bool is_pshared = false)
+ : is_pshared(is_pshared), preference(static_cast<unsigned>(preference)),
+ state(0), writer_tid(0), queue() {}
+
+ [[nodiscard]]
+ LIBC_INLINE LockResult try_read_lock() {
+ RwState old = RwState::load(state, cpp::MemoryOrder::RELAXED);
+ return try_lock<Role::Reader>(old);
+ }
+ [[nodiscard]]
+ LIBC_INLINE LockResult try_write_lock() {
+ RwState old = RwState::load(state, cpp::MemoryOrder::RELAXED);
+ return try_lock<Role::Writer>(old);
+ }
+
+private:
+ template <Role role>
+ LIBC_INLINE LockResult
+ lock_slow(cpp::optional<Futex::Timeout> timeout = cpp::nullopt,
+ unsigned spin_count = LIBC_COPT_RWLOCK_DEFAULT_SPIN_COUNT) {
+ // Phase 1: deadlock detection.
+ // A deadlock happens if this is a RAW/WAW lock in the same thread.
+ if (writer_tid.load(cpp::MemoryOrder::RELAXED) == gettid())
+ return LockResult::Deadlock;
+
+#if LIBC_COPT_TIMEOUT_ENSURE_MONOTONICITY
+ // Phase 2: convert the timeout if necessary.
+ if (timeout)
+ ensure_monotonicity(*timeout);
+#endif
+
+ // Phase 3: spin to get the initial state. We ignore the timing due to
+ // spin since it should end quickly.
+ RwState old =
+ RwState::spin_reload<role>(state, get_preference(), spin_count);
+
+ // Enter the main acquisition loop.
+ for (;;) {
+ // Phase 4: if the lock can be acquired, try to acquire it.
+ LockResult result = try_lock<role>(old);
+ if (result != LockResult::Busy)
+ return result;
+
+ // Phase 5: register ourselves as a reader.
+ int serial_number;
+ {
+ // The queue need to be protected by a mutex since the operations in
+ // this block must be executed as a whole transaction. It is possible
+ // that this lock will make the timeout imprecise, but this is the
+ // best we can do. The transaction is small and everyone should make
+ // progress rather quickly.
+ WaitingQueue::Guard guard = queue.acquire(is_pshared);
+ guard.template pending_count<role>()++;
+
+ // Use atomic operation to guarantee the total order of the operations
+ // on the state. The pending flag update should be visible to any
+ // succeeding unlock events. Or, if a unlock does happen before we
+ // sleep on the futex, we can avoid such waiting.
+ old = RwState::fetch_set_pending_bit<role>(state,
+ cpp::MemoryOrder::RELAXED);
+ // no need to use atomic since it is already protected by the mutex.
+ serial_number = guard.serialization<role>();
+ }
+
+ // Phase 6: do futex wait until the lock is available or timeout is
+ // reached.
+ bool timeout_flag = false;
+ if (!old.can_acquire<role>(get_preference()))
+ timeout_flag = (queue.wait<role>(serial_number, timeout, is_pshared) ==
+ -ETIMEDOUT);
+
+ // Phase 7: unregister ourselves as a pending reader/writer.
+ {
+ // Similarly, the unregister operation should also be an atomic
+ // transaction.
+ WaitingQueue::Guard guard = queue.acquire(is_pshared);
+ guard.pending_count<role>()--;
+ // Clear the flag if we are the last reader. The flag must be
+ // cleared otherwise operations like trylock may fail even though
+ // there is no competitors.
+ if (guard.pending_count<role>() == 0)
+ RwState::fetch_clear_pending_bit<role>(state,
+ cpp::MemoryOrder::RELAXED);
+ }
+
+ // Phase 8: exit the loop is timeout is reached.
+ if (timeout_flag)
+ return LockResult::TimedOut;
+
+ // Phase 9: reload the state and retry the acquisition.
+ old = RwState::spin_reload<role>(state, get_preference(), spin_count);
+ }
+ }
+
+public:
+ [[nodiscard]]
+ LIBC_INLINE LockResult
+ read_lock(cpp::optional<Futex::Timeout> timeout = cpp::nullopt,
+ unsigned spin_count = LIBC_COPT_RWLOCK_DEFAULT_SPIN_COUNT) {
+ LockResult result = try_read_lock();
+ if (LIBC_LIKELY(result != LockResult::Busy))
+ return result;
+ return lock_slow<Role::Reader>(timeout, spin_count);
+ }
+ [[nodiscard]]
+ LIBC_INLINE LockResult
+ write_lock(cpp::optional<Futex::Timeout> timeout = cpp::nullopt,
+ unsigned spin_count = LIBC_COPT_RWLOCK_DEFAULT_SPIN_COUNT) {
+ LockResult result = try_write_lock();
+ if (LIBC_LIKELY(result != LockResult::Busy))
+ return result;
+ return lock_slow<Role::Writer>(timeout, spin_count);
+ }
+
+private:
+ // Compiler (clang 19.0) somehow decides that this function may be inlined,
+ // which leads to a larger unlock function that is infeasible to be inlined.
+ // Since notifcation routine is colder we mark it as noinline explicitly.
+ [[gnu::noinline]]
+ LIBC_INLINE void notify_pending_threads() {
+ enum class WakeTarget { Readers, Writers, None };
+ WakeTarget status;
+
+ {
+ WaitingQueue::Guard guard = queue.acquire(is_pshared);
+ if (guard.pending_count<Role::Writer>() != 0) {
+ guard.serialization<Role::Writer>()++;
+ status = WakeTarget::Writers;
+ } else if (guard.pending_count<Role::Reader>() != 0) {
+ guard.serialization<Role::Reader>()++;
+ status = WakeTarget::Readers;
+ } else
+ status = WakeTarget::None;
+ }
+
+ if (status == WakeTarget::Readers)
+ queue.notify<Role::Reader>(is_pshared);
+ else if (status == WakeTarget::Writers)
+ queue.notify<Role::Writer>(is_pshared);
+ }
+
+public:
+ [[nodiscard]]
+ LIBC_INLINE LockResult unlock() {
+ RwState old = RwState::load(state, cpp::MemoryOrder::RELAXED);
+ if (old.has_active_writer()) {
+ // The lock is held by a writer.
+ // Check if we are the owner of the lock.
+ if (writer_tid.load(cpp::MemoryOrder::RELAXED) != gettid())
+ return LockResult::PermissionDenied;
+ // clear writer tid.
+ writer_tid.store(0, cpp::MemoryOrder::RELAXED);
+ // clear the writer bit.
+ old =
+ RwState::fetch_clear_active_writer(state, cpp::MemoryOrder::RELEASE);
+ // If there is no pending readers or writers, we are done.
+ if (!old.has_pending())
+ return LockResult::Success;
+ } else if (old.has_active_reader()) {
+ // The lock is held by readers.
+ // Decrease the reader count.
+ old = RwState::fetch_sub_reader_count(state, cpp::MemoryOrder::RELEASE);
+ // If there is no pending readers or writers, we are done.
+ if (!old.has_last_reader() || !old.has_pending())
+ return LockResult::Success;
+ } else
+ return LockResult::PermissionDenied;
+
+ notify_pending_threads();
+ return LockResult::Success;
+ }
+
+ // We do not allocate any special resources for the RwLock, so this function
+ // will only check if the lock is currently held by any thread.
+ [[nodiscard]]
+ LIBC_INLINE LockResult check_for_destroy() {
+ RwState old = RwState::load(state, cpp::MemoryOrder::RELAXED);
+ if (old.has_acitve_owner())
+ return LockResult::Busy;
+ return LockResult::Success;
+ }
+};
+} // namespace LIBC_NAMESPACE
+
+#endif // LLVM_LIBC_SRC_SUPPORT_THREADS_LINUX_RWLOCK_H
diff --git a/libc/src/pthread/CMakeLists.txt b/libc/src/pthread/CMakeLists.txt
index e5bebb63c6401..dc748b22e0378 100644
--- a/libc/src/pthread/CMakeLists.txt
+++ b/libc/src/pthread/CMakeLists.txt
@@ -522,6 +522,106 @@ add_entrypoint_object(
libc.include.errno
)
+add_entrypoint_object(
+ pthread_rwlock_init
+ SRCS
+ pthread_rwlock_init.cpp
+ HDRS
+ pthread_rwlock_init.h
+ DEPENDS
+ libc.include.pthread
+ libc.src.__support.threads.linux.rwlock
+ libc.src.__support.CPP.new
+)
+
+add_entrypoint_object(
+ pthread_rwlock_tryrdlock
+ SRCS
+ pthread_rwlock_tryrdlock.cpp
+ HDRS
+ pthread_rwlock_tryrdlock.h
+ DEPENDS
+ libc.include.pthread
+ libc.src.__support.threads.linux.rwlock
+)
+
+add_entrypoint_object(
+ pthread_rwlock_trywrlock
+ SRCS
+ pthread_rwlock_trywrlock.cpp
+ HDRS
+ pthread_rwlock_trywrlock.h
+ DEPENDS
+ libc.include.pthread
+ libc.src.__support.threads.linux.rwlock
+)
+
+add_entrypoint_object(
+ pthread_rwlock_timedrdlock
+ SRCS
+ pthread_rwlock_timedrdlock.cpp
+ HDRS
+ pthread_rwlock_timedrdlock.h
+ DEPENDS
+ libc.include.pthread
+ libc.src.__support.threads.linux.rwlock
+)
+
+add_entrypoint_object(
+ pthread_rwlock_timedwrlock
+ SRCS
+ pthread_rwlock_timedwrlock.cpp
+ HDRS
+ pthread_rwlock_timedwrlock.h
+ DEPENDS
+ libc.include.pthread
+ libc.src.__support.threads.linux.rwlock
+)
+
+add_entrypoint_object(
+ pthread_rwlock_rdlock
+ SRCS
+ pthread_rwlock_rdlock.cpp
+ HDRS
+ pthread_rwlock_rdlock.h
+ DEPENDS
+ libc.include.pthread
+ libc.src.__support.threads.linux.rwlock
+)
+
+add_entrypoint_object(
+ pthread_rwlock_wrlock
+ SRCS
+ pthread_rwlock_wrlock.cpp
+ HDRS
+ pthread_rwlock_wrlock.h
+ DEPENDS
+ libc.include.pthread
+ libc.src.__support.threads.linux.rwlock
+)
+
+add_entrypoint_object(
+ pthread_rwlock_unlock
+ SRCS
+ pthread_rwlock_unlock.cpp
+ HDRS
+ pthread_rwlock_unlock.h
+ DEPENDS
+ libc.include.pthread
+ libc.src.__support.threads.linux.rwlock
+)
+
+add_entrypoint_object(
+ pthread_rwlock_destroy
+ SRCS
+ pthread_rwlock_destroy.cpp
+ HDRS
+ pthread_rwlock_destroy.h
+ DEPENDS
+ libc.include.pthread
+ libc.src.__support.threads.linux.rwlock
+)
+
add_entrypoint_object(
pthread_once
SRCS
diff --git a/libc/src/pthread/pthread_rwlock_destroy.cpp b/libc/src/pthread/pthread_rwlock_destroy.cpp
new file mode 100644
index 0000000000000..d82bb376bda2c
--- /dev/null
+++ b/libc/src/pthread/pthread_rwlock_destroy.cpp
@@ -0,0 +1,33 @@
+//===-- Implementation for Rwlock's destroy function ----------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/pthread/pthread_rwlock_destroy.h"
+
+#include "src/__support/common.h"
+#include "src/__support/threads/linux/rwlock.h"
+
+#include <errno.h>
+#include <pthread.h>
+
+namespace LIBC_NAMESPACE {
+
+LLVM_LIBC_FUNCTION(int, pthread_rwlock_destroy, (pthread_rwlock_t * rwlock)) {
+ if (!rwlock)
+ return EINVAL;
+ auto *rw = reinterpret_cast<RwLock *>(rwlock);
+ RwLock::LockResult res = rw->check_for_destroy();
+
+ // this is currently no-op, but we still call the destructor as a symmetry
+ // to its constructor call;
+ if (res == RwLock::LockResult::Success)
+ rw->~RwLock();
+
+ return static_cast<int>(res);
+}
+
+} // namespace LIBC_NAMESPACE
diff --git a/libc/src/pthread/pthread_rwlock_destroy.h b/libc/src/pthread/pthread_rwlock_destroy.h
new file mode 100644
index 0000000000000..f845e806d6e60
--- /dev/null
+++ b/libc/src/pthread/pthread_rwlock_destroy.h
@@ -0,0 +1,20 @@
+//===-- Implementation header for Rwlock's destroy function -------*-C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_DESTROY_H
+#define LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_DESTROY_H
+
+#include <pthread.h>
+
+namespace LIBC_NAMESPACE {
+
+int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
+
+} // namespace LIBC_NAMESPACE
+
+#endif // LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_DESTROY_H
diff --git a/libc/src/pthread/pthread_rwlock_init.cpp b/libc/src/pthread/pthread_rwlock_init.cpp
new file mode 100644
index 0000000000000..b1b58aac6c6ff
--- /dev/null
+++ b/libc/src/pthread/pthread_rwlock_init.cpp
@@ -0,0 +1,67 @@
+//===-- Linux implementation of the pthread_rwlock_init function ----------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/pthread/pthread_rwlock_init.h"
+
+#include "src/__support/CPP/new.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_assert.h"
+#include "src/__support/threads/linux/rwlock.h"
+
+#include <errno.h>
+#include <pthread.h>
+
+namespace LIBC_NAMESPACE {
+
+static_assert(
+ sizeof(RwLock) == sizeof(pthread_rwlock_t) &&
+ alignof(RwLock) == alignof(pthread_rwlock_t),
+ "The public pthread_rwlock_t type must be of the same size and alignment "
+ "as the internal rwlock type.");
+
+LLVM_LIBC_FUNCTION(int, pthread_rwlock_init,
+ (pthread_rwlock_t * rwlock,
+ const pthread_rwlockattr_t *__restrict attr)) {
+ pthread_rwlockattr_t rwlockattr{
+ /*pshared=*/PTHREAD_PROCESS_PRIVATE,
+ /*pref*/ PTHREAD_RWLOCK_PREFER_READER_NP,
+ };
+ // POSIX does not specify this check, so we add an assertion to catch it.
+ LIBC_ASSERT(rwlock && "rwlock is null");
+ if (attr)
+ rwlockattr = *attr;
+
+ // PTHREAD_RWLOCK_PREFER_WRITER_NP is not supported.
+ rwlock::Role preference;
+ switch (rwlockattr.pref) {
+ case PTHREAD_RWLOCK_PREFER_READER_NP:
+ preference = rwlock::Role::Reader;
+ break;
+ case PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP:
+ preference = rwlock::Role::Writer;
+ break;
+ default:
+ return EINVAL;
+ }
+ bool is_pshared;
+ switch (rwlockattr.pshared) {
+ case PTHREAD_PROCESS_PRIVATE:
+ is_pshared = false;
+ break;
+ case PTHREAD_PROCESS_SHARED:
+ is_pshared = true;
+ break;
+ default:
+ return EINVAL;
+ }
+
+ new (rwlock) RwLock(preference, is_pshared);
+ return 0;
+}
+
+} // namespace LIBC_NAMESPACE
diff --git a/libc/src/pthread/pthread_rwlock_init.h b/libc/src/pthread/pthread_rwlock_init.h
new file mode 100644
index 0000000000000..78d2934882c1d
--- /dev/null
+++ b/libc/src/pthread/pthread_rwlock_init.h
@@ -0,0 +1,21 @@
+//===-- Implementation header for pthread_rwlock_init function ---*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_INIT_H
+#define LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_INIT_H
+
+#include <pthread.h>
+
+namespace LIBC_NAMESPACE {
+
+int pthread_rwlock_init(pthread_rwlock_t *rwlock,
+ const pthread_rwlockattr_t *__restrict attr);
+
+} // namespace LIBC_NAMESPACE
+
+#endif // LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_INIT_H
diff --git a/libc/src/pthread/pthread_rwlock_rdlock.cpp b/libc/src/pthread/pthread_rwlock_rdlock.cpp
new file mode 100644
index 0000000000000..e9aee5da4e7e4
--- /dev/null
+++ b/libc/src/pthread/pthread_rwlock_rdlock.cpp
@@ -0,0 +1,32 @@
+//===-- Implementation of the Rwlock's rdlock function --------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/pthread/pthread_rwlock_rdlock.h"
+
+#include "src/__support/common.h"
+#include "src/__support/threads/linux/rwlock.h"
+
+#include <errno.h>
+#include <pthread.h>
+
+namespace LIBC_NAMESPACE {
+
+static_assert(
+ sizeof(RwLock) == sizeof(pthread_rwlock_t) &&
+ alignof(RwLock) == alignof(pthread_rwlock_t),
+ "The public pthread_rwlock_t type must be of the same size and alignment "
+ "as the internal rwlock type.");
+
+LLVM_LIBC_FUNCTION(int, pthread_rwlock_rdlock, (pthread_rwlock_t * rwlock)) {
+ if (!rwlock)
+ return EINVAL;
+ RwLock *rw = reinterpret_cast<RwLock *>(rwlock);
+ return static_cast<int>(rw->read_lock());
+}
+
+} // namespace LIBC_NAMESPACE
diff --git a/libc/src/pthread/pthread_rwlock_rdlock.h b/libc/src/pthread/pthread_rwlock_rdlock.h
new file mode 100644
index 0000000000000..79027739f4b7c
--- /dev/null
+++ b/libc/src/pthread/pthread_rwlock_rdlock.h
@@ -0,0 +1,20 @@
+//===-- Implementation header for Rwlock's rdlock function -------*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_RDLOCK_H
+#define LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_RDLOCK_H
+
+#include <pthread.h>
+
+namespace LIBC_NAMESPACE {
+
+int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
+
+} // namespace LIBC_NAMESPACE
+
+#endif // LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_RDLOCK_H
diff --git a/libc/src/pthread/pthread_rwlock_timedrdlock.cpp b/libc/src/pthread/pthread_rwlock_timedrdlock.cpp
new file mode 100644
index 0000000000000..d503d50b79f6c
--- /dev/null
+++ b/libc/src/pthread/pthread_rwlock_timedrdlock.cpp
@@ -0,0 +1,49 @@
+//===-- Implementation of the Rwlock's timedrdlock function ---------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/pthread/pthread_rwlock_timedrdlock.h"
+
+#include "src/__support/common.h"
+#include "src/__support/libc_assert.h"
+#include "src/__support/macros/optimization.h"
+#include "src/__support/threads/linux/rwlock.h"
+#include "src/__support/time/linux/abs_timeout.h"
+
+#include <errno.h>
+#include <pthread.h>
+
+namespace LIBC_NAMESPACE {
+
+static_assert(
+ sizeof(RwLock) == sizeof(pthread_rwlock_t) &&
+ alignof(RwLock) == alignof(pthread_rwlock_t),
+ "The public pthread_rwlock_t type must be of the same size and alignment "
+ "as the internal rwlock type.");
+
+LLVM_LIBC_FUNCTION(int, pthread_rwlock_timedrdlock,
+ (pthread_rwlock_t * rwlock,
+ const struct timespec *abstime)) {
+ if (!rwlock)
+ return EINVAL;
+ RwLock *rw = reinterpret_cast<RwLock *>(rwlock);
+ LIBC_ASSERT(abstime && "timedrdlock called with a null timeout");
+ auto timeout =
+ internal::AbsTimeout::from_timespec(*abstime, /*is_realtime=*/true);
+ if (LIBC_LIKELY(timeout.has_value()))
+ return static_cast<int>(rw->read_lock(timeout.value()));
+
+ switch (timeout.error()) {
+ case internal::AbsTimeout::Error::Invalid:
+ return EINVAL;
+ case internal::AbsTimeout::Error::BeforeEpoch:
+ return ETIMEDOUT;
+ // default: unreachable, all two cases are covered.
+ }
+}
+
+} // namespace LIBC_NAMESPACE
diff --git a/libc/src/pthread/pthread_rwlock_timedrdlock.h b/libc/src/pthread/pthread_rwlock_timedrdlock.h
new file mode 100644
index 0000000000000..dfa43f25ba706
--- /dev/null
+++ b/libc/src/pthread/pthread_rwlock_timedrdlock.h
@@ -0,0 +1,21 @@
+//===-- Implementation header for Rwlock's timedrdlock function --*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_TIMEDRDLOCK_H
+#define LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_TIMEDRDLOCK_H
+
+#include <pthread.h>
+
+namespace LIBC_NAMESPACE {
+
+int pthread_rwlock_timedrdlock(pthread_rwlock_t *__restrict rwlock,
+ const struct timespec *__restrict abs_timeout);
+
+} // namespace LIBC_NAMESPACE
+
+#endif // LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_TIMEDRDLOCK_H
diff --git a/libc/src/pthread/pthread_rwlock_timedwrlock.cpp b/libc/src/pthread/pthread_rwlock_timedwrlock.cpp
new file mode 100644
index 0000000000000..5e67730141946
--- /dev/null
+++ b/libc/src/pthread/pthread_rwlock_timedwrlock.cpp
@@ -0,0 +1,43 @@
+//===-- Implementation for Rwlock's timedwrlock function ------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/pthread/pthread_rwlock_timedwrlock.h"
+
+#include "src/__support/common.h"
+#include "src/__support/libc_assert.h"
+#include "src/__support/macros/optimization.h"
+#include "src/__support/threads/linux/rwlock.h"
+#include "src/__support/time/linux/abs_timeout.h"
+
+#include <errno.h>
+#include <pthread.h>
+
+namespace LIBC_NAMESPACE {
+
+LLVM_LIBC_FUNCTION(int, pthread_rwlock_timedwrlock,
+ (pthread_rwlock_t *__restrict rwlock,
+ const struct timespec *__restrict abstime)) {
+ if (!rwlock)
+ return EINVAL;
+ RwLock *rw = reinterpret_cast<RwLock *>(rwlock);
+ LIBC_ASSERT(abstime && "timedwrlock called with a null timeout");
+ auto timeout =
+ internal::AbsTimeout::from_timespec(*abstime, /*is_realtime=*/true);
+ if (LIBC_LIKELY(timeout.has_value()))
+ return static_cast<int>(rw->write_lock(timeout.value()));
+
+ switch (timeout.error()) {
+ case internal::AbsTimeout::Error::Invalid:
+ return EINVAL;
+ case internal::AbsTimeout::Error::BeforeEpoch:
+ return ETIMEDOUT;
+ // default: unreachable, all two cases are covered.
+ }
+}
+
+} // namespace LIBC_NAMESPACE
diff --git a/libc/src/pthread/pthread_rwlock_timedwrlock.h b/libc/src/pthread/pthread_rwlock_timedwrlock.h
new file mode 100644
index 0000000000000..a39d8de8d330f
--- /dev/null
+++ b/libc/src/pthread/pthread_rwlock_timedwrlock.h
@@ -0,0 +1,21 @@
+//===-- Implementation header for Rwlock's timedwrlock function --*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_TIMEDWRLOCK_H
+#define LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_TIMEDWRLOCK_H
+
+#include <pthread.h>
+
+namespace LIBC_NAMESPACE {
+
+int pthread_rwlock_timedwrlock(pthread_rwlock_t *__restrict rwlock,
+ const struct timespec *__restrict abs_timeout);
+
+} // namespace LIBC_NAMESPACE
+
+#endif // LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_TIMEDWRLOCK_H
diff --git a/libc/src/pthread/pthread_rwlock_tryrdlock.cpp b/libc/src/pthread/pthread_rwlock_tryrdlock.cpp
new file mode 100644
index 0000000000000..9dc1bf09bc830
--- /dev/null
+++ b/libc/src/pthread/pthread_rwlock_tryrdlock.cpp
@@ -0,0 +1,32 @@
+//===-- Implementation of the Rwlock's tryrdlock function -----------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/pthread/pthread_rwlock_tryrdlock.h"
+
+#include "src/__support/common.h"
+#include "src/__support/threads/linux/rwlock.h"
+
+#include <errno.h>
+#include <pthread.h>
+
+namespace LIBC_NAMESPACE {
+
+static_assert(
+ sizeof(RwLock) == sizeof(pthread_rwlock_t) &&
+ alignof(RwLock) == alignof(pthread_rwlock_t),
+ "The public pthread_rwlock_t type must be of the same size and alignment "
+ "as the internal rwlock type.");
+
+LLVM_LIBC_FUNCTION(int, pthread_rwlock_tryrdlock, (pthread_rwlock_t * rwlock)) {
+ if (!rwlock)
+ return EINVAL;
+ RwLock *rw = reinterpret_cast<RwLock *>(rwlock);
+ return static_cast<int>(rw->try_read_lock());
+}
+
+} // namespace LIBC_NAMESPACE
diff --git a/libc/src/pthread/pthread_rwlock_tryrdlock.h b/libc/src/pthread/pthread_rwlock_tryrdlock.h
new file mode 100644
index 0000000000000..b07ab5b152b1a
--- /dev/null
+++ b/libc/src/pthread/pthread_rwlock_tryrdlock.h
@@ -0,0 +1,20 @@
+//===-- Implementation header for Rwlock's tryrdlock function ----*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_TRYRDLOCK_H
+#define LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_TRYRDLOCK_H
+
+#include <pthread.h>
+
+namespace LIBC_NAMESPACE {
+
+int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
+
+} // namespace LIBC_NAMESPACE
+
+#endif // LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_TRYRDLOCK_H
diff --git a/libc/src/pthread/pthread_rwlock_trywrlock.cpp b/libc/src/pthread/pthread_rwlock_trywrlock.cpp
new file mode 100644
index 0000000000000..e4ace3cb350af
--- /dev/null
+++ b/libc/src/pthread/pthread_rwlock_trywrlock.cpp
@@ -0,0 +1,32 @@
+//===-- Implementation for Rwlock's trywrlock function -------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/pthread/pthread_rwlock_trywrlock.h"
+
+#include "src/__support/common.h"
+#include "src/__support/threads/linux/rwlock.h"
+
+#include <errno.h>
+#include <pthread.h>
+
+namespace LIBC_NAMESPACE {
+
+static_assert(
+ sizeof(RwLock) == sizeof(pthread_rwlock_t) &&
+ alignof(RwLock) == alignof(pthread_rwlock_t),
+ "The public pthread_rwlock_t type must be of the same size and alignment "
+ "as the internal rwlock type.");
+
+LLVM_LIBC_FUNCTION(int, pthread_rwlock_trywrlock, (pthread_rwlock_t * rwlock)) {
+ if (!rwlock)
+ return EINVAL;
+ RwLock *rw = reinterpret_cast<RwLock *>(rwlock);
+ return static_cast<int>(rw->try_write_lock());
+}
+
+} // namespace LIBC_NAMESPACE
diff --git a/libc/src/pthread/pthread_rwlock_trywrlock.h b/libc/src/pthread/pthread_rwlock_trywrlock.h
new file mode 100644
index 0000000000000..fc146c6db859f
--- /dev/null
+++ b/libc/src/pthread/pthread_rwlock_trywrlock.h
@@ -0,0 +1,20 @@
+//===-- Implementation header for Rwlock's trywrlock function ----*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_TRYWRLOCK_H
+#define LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_TRYWRLOCK_H
+
+#include <pthread.h>
+
+namespace LIBC_NAMESPACE {
+
+int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
+
+} // namespace LIBC_NAMESPACE
+
+#endif // LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_TRYWRLOCK_H
diff --git a/libc/src/pthread/pthread_rwlock_unlock.cpp b/libc/src/pthread/pthread_rwlock_unlock.cpp
new file mode 100644
index 0000000000000..21cedf42a8d50
--- /dev/null
+++ b/libc/src/pthread/pthread_rwlock_unlock.cpp
@@ -0,0 +1,26 @@
+//===-- Implementation for Rwlock's unlock function -----------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/pthread/pthread_rwlock_unlock.h"
+
+#include "src/__support/common.h"
+#include "src/__support/threads/linux/rwlock.h"
+
+#include <errno.h>
+#include <pthread.h>
+
+namespace LIBC_NAMESPACE {
+
+LLVM_LIBC_FUNCTION(int, pthread_rwlock_unlock, (pthread_rwlock_t * rwlock)) {
+ if (!rwlock)
+ return EINVAL;
+ auto *rw = reinterpret_cast<RwLock *>(rwlock);
+ return static_cast<int>(rw->unlock());
+}
+
+} // namespace LIBC_NAMESPACE
diff --git a/libc/src/pthread/pthread_rwlock_unlock.h b/libc/src/pthread/pthread_rwlock_unlock.h
new file mode 100644
index 0000000000000..b9a72f1e06992
--- /dev/null
+++ b/libc/src/pthread/pthread_rwlock_unlock.h
@@ -0,0 +1,20 @@
+//===-- Implementation header for Rwlock's unlock function -------*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_UNLOCK_H
+#define LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_UNLOCK_H
+
+#include <pthread.h>
+
+namespace LIBC_NAMESPACE {
+
+int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
+
+} // namespace LIBC_NAMESPACE
+
+#endif // LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_UNLOCK_H
diff --git a/libc/src/pthread/pthread_rwlock_wrlock.cpp b/libc/src/pthread/pthread_rwlock_wrlock.cpp
new file mode 100644
index 0000000000000..5d3868a58f4e9
--- /dev/null
+++ b/libc/src/pthread/pthread_rwlock_wrlock.cpp
@@ -0,0 +1,32 @@
+//===-- Implementation for Rwlock's wrlock function -------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/pthread/pthread_rwlock_wrlock.h"
+
+#include "src/__support/common.h"
+#include "src/__support/threads/linux/rwlock.h"
+
+#include <errno.h>
+#include <pthread.h>
+
+namespace LIBC_NAMESPACE {
+
+static_assert(
+ sizeof(RwLock) == sizeof(pthread_rwlock_t) &&
+ alignof(RwLock) == alignof(pthread_rwlock_t),
+ "The public pthread_rwlock_t type must be of the same size and alignment "
+ "as the internal rwlock type.");
+
+LLVM_LIBC_FUNCTION(int, pthread_rwlock_wrlock, (pthread_rwlock_t * rwlock)) {
+ if (!rwlock)
+ return EINVAL;
+ RwLock *rw = reinterpret_cast<RwLock *>(rwlock);
+ return static_cast<int>(rw->write_lock());
+}
+
+} // namespace LIBC_NAMESPACE
diff --git a/libc/src/pthread/pthread_rwlock_wrlock.h b/libc/src/pthread/pthread_rwlock_wrlock.h
new file mode 100644
index 0000000000000..ba77c1f1f09ac
--- /dev/null
+++ b/libc/src/pthread/pthread_rwlock_wrlock.h
@@ -0,0 +1,20 @@
+//===-- Implementation header for Rwlock's wrlock function -------*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_WRLOCK_H
+#define LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_WRLOCK_H
+
+#include <pthread.h>
+
+namespace LIBC_NAMESPACE {
+
+int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
+
+} // namespace LIBC_NAMESPACE
+
+#endif // LLVM_LIBC_SRC_PTHREAD_PTHREAD_RWLOCK_WRLOCK_H
diff --git a/libc/test/integration/src/pthread/CMakeLists.txt b/libc/test/integration/src/pthread/CMakeLists.txt
index a10dc256200d9..16a1ff61887f8 100644
--- a/libc/test/integration/src/pthread/CMakeLists.txt
+++ b/libc/test/integration/src/pthread/CMakeLists.txt
@@ -17,6 +17,45 @@ add_integration_test(
libc.src.pthread.pthread_join
)
+add_integration_test(
+ pthread_rwlock_test
+ SUITE
+ libc-pthread-integration-tests
+ SRCS
+ pthread_rwlock_test.cpp
+ DEPENDS
+ libc.include.pthread
+ libc.include.time
+ libc.include.errno
+ libc.src.pthread.pthread_rwlock_destroy
+ libc.src.pthread.pthread_rwlock_init
+ libc.src.pthread.pthread_rwlock_rdlock
+ libc.src.pthread.pthread_rwlock_tryrdlock
+ libc.src.pthread.pthread_rwlock_timedrdlock
+ libc.src.pthread.pthread_rwlock_wrlock
+ libc.src.pthread.pthread_rwlock_trywrlock
+ libc.src.pthread.pthread_rwlock_timedwrlock
+ libc.src.pthread.pthread_rwlock_unlock
+ libc.src.pthread.pthread_create
+ libc.src.pthread.pthread_join
+ libc.src.pthread.pthread_rwlockattr_init
+ libc.src.pthread.pthread_rwlockattr_destroy
+ libc.src.pthread.pthread_rwlockattr_setpshared
+ libc.src.pthread.pthread_rwlockattr_setkind_np
+ libc.src.__support.threads.linux.raw_mutex
+ libc.src.stdio.printf
+ libc.src.stdlib.getenv
+ libc.src.sys.mman.mmap
+ libc.src.sys.mman.munmap
+ libc.src.time.clock_gettime
+ libc.src.sys.random.getrandom
+ libc.src.unistd.fork
+ libc.src.sys.wait.waitpid
+ libc.src.stdlib.exit
+ libc.src.__support.CPP.atomic
+ libc.src.__support.threads.sleep
+)
+
add_integration_test(
pthread_test
SUITE
diff --git a/libc/test/integration/src/pthread/pthread_rwlock_test.cpp b/libc/test/integration/src/pthread/pthread_rwlock_test.cpp
new file mode 100644
index 0000000000000..215db1fc5addf
--- /dev/null
+++ b/libc/test/integration/src/pthread/pthread_rwlock_test.cpp
@@ -0,0 +1,478 @@
+//===-- Tests for pthread_rwlock ------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/CPP/atomic.h"
+#include "src/__support/OSUtil/syscall.h"
+#include "src/__support/threads/linux/raw_mutex.h"
+#include "src/__support/threads/linux/rwlock.h"
+#include "src/__support/threads/sleep.h"
+#include "src/pthread/pthread_create.h"
+#include "src/pthread/pthread_join.h"
+#include "src/pthread/pthread_rwlock_destroy.h"
+#include "src/pthread/pthread_rwlock_init.h"
+#include "src/pthread/pthread_rwlock_rdlock.h"
+#include "src/pthread/pthread_rwlock_timedrdlock.h"
+#include "src/pthread/pthread_rwlock_timedwrlock.h"
+#include "src/pthread/pthread_rwlock_tryrdlock.h"
+#include "src/pthread/pthread_rwlock_trywrlock.h"
+#include "src/pthread/pthread_rwlock_unlock.h"
+#include "src/pthread/pthread_rwlock_wrlock.h"
+#include "src/pthread/pthread_rwlockattr_destroy.h"
+#include "src/pthread/pthread_rwlockattr_init.h"
+#include "src/pthread/pthread_rwlockattr_setkind_np.h"
+#include "src/pthread/pthread_rwlockattr_setpshared.h"
+#include "src/stdio/printf.h"
+#include "src/stdlib/exit.h"
+#include "src/stdlib/getenv.h"
+#include "src/sys/mman/mmap.h"
+#include "src/sys/mman/munmap.h"
+#include "src/sys/random/getrandom.h"
+#include "src/sys/wait/waitpid.h"
+#include "src/time/clock_gettime.h"
+#include "src/unistd/fork.h"
+#include "test/IntegrationTest/test.h"
+#include <errno.h>
+#include <optional>
+#include <pthread.h>
+#include <time.h>
+
+namespace LIBC_NAMESPACE::rwlock {
+class RwLockTester {
+public:
+ static constexpr int full_reader_state() {
+ return (~0) & (~RwState::PENDING_MASK) & (~RwState::ACTIVE_WRITER_BIT);
+ }
+};
+} // namespace LIBC_NAMESPACE::rwlock
+
+static void smoke_test() {
+ pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, nullptr), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_rdlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_trywrlock(&rwlock), EBUSY);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_rdlock(&rwlock), EDEADLK);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(&rwlock), EDEADLK);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&rwlock), EBUSY);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_trywrlock(&rwlock), EBUSY);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0);
+}
+
+static void deadlock_detection_test() {
+ pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, nullptr), 0);
+ // We only detect RAW, WAW deadlocks.
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(&rwlock), EDEADLK);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0);
+}
+
+static void try_lock_test() {
+ pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, nullptr), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_trywrlock(&rwlock), EBUSY);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&rwlock), EBUSY);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_rdlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_trywrlock(&rwlock), EBUSY);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0);
+}
+
+static void destroy_before_unlock_test() {
+ pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, nullptr), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), EBUSY);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0);
+}
+
+static void nullptr_test() {
+ timespec ts = {};
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_rdlock(nullptr), EINVAL);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(nullptr), EINVAL);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedrdlock(nullptr, &ts), EINVAL);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedwrlock(nullptr, &ts), EINVAL);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(nullptr), EINVAL);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_trywrlock(nullptr), EINVAL);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(nullptr), EINVAL);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(nullptr), EINVAL);
+}
+
+// If you are a user reading this code, please do not do something like this.
+// We manually modify the internal state of the rwlock to test high reader
+// counts.
+static void high_reader_count_test() {
+ pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
+ rwlock.__state = LIBC_NAMESPACE::rwlock::RwLockTester::full_reader_state();
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_rdlock(&rwlock), EAGAIN);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&rwlock), EAGAIN);
+ // allocate 4 reader slots.
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0);
+
+ pthread_t threads[20];
+ for (auto &i : threads)
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_create(
+ &i, nullptr,
+ [](void *arg) -> void * {
+ pthread_rwlock_t *rwlock =
+ reinterpret_cast<pthread_rwlock_t *>(arg);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_trywrlock(rwlock),
+ EBUSY);
+ while (LIBC_NAMESPACE::pthread_rwlock_rdlock(rwlock) ==
+ EAGAIN)
+ LIBC_NAMESPACE::sleep_briefly();
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(rwlock), 0);
+ return nullptr;
+ },
+ &rwlock),
+ 0);
+
+ for (auto &i : threads)
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_join(i, nullptr), 0);
+}
+
+static void unusual_timespec_test() {
+ pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
+ timespec ts = {0, -1};
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedrdlock(&rwlock, &ts), EINVAL);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedwrlock(&rwlock, &ts), EINVAL);
+ ts.tv_nsec = 1'000'000'000;
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedrdlock(&rwlock, &ts), EINVAL);
+ ts.tv_nsec += 1;
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedwrlock(&rwlock, &ts), EINVAL);
+ ts.tv_nsec = 0;
+ ts.tv_sec = -1;
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedrdlock(&rwlock, &ts),
+ ETIMEDOUT);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedwrlock(&rwlock, &ts),
+ ETIMEDOUT);
+}
+
+static void timedlock_with_deadlock_test() {
+ pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
+ timespec ts{};
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_rdlock(&rwlock), 0);
+ LIBC_NAMESPACE::clock_gettime(CLOCK_REALTIME, &ts);
+ ts.tv_nsec += 50'000;
+ if (ts.tv_nsec >= 1'000'000'000) {
+ ts.tv_nsec -= 1'000'000'000;
+ ts.tv_sec += 1;
+ }
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedwrlock(&rwlock, &ts),
+ ETIMEDOUT);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_timedrdlock(&rwlock, &ts), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0);
+ // notice that ts is already expired, but the following should still succeed.
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_trywrlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_rdlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_wrlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_unlock(&rwlock), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0);
+}
+
+static void attributed_initialization_test() {
+ pthread_rwlockattr_t attr{};
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_init(&attr), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setkind_np(
+ &attr, PTHREAD_RWLOCK_PREFER_READER_NP),
+ 0);
+ {
+ pthread_rwlock_t rwlock{};
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, &attr), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0);
+ }
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setkind_np(
+ &attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP),
+ 0);
+ {
+ pthread_rwlock_t rwlock{};
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, &attr), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0);
+ }
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setkind_np(
+ &attr, PTHREAD_RWLOCK_PREFER_WRITER_NP),
+ 0);
+ {
+ pthread_rwlock_t rwlock{};
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, &attr), EINVAL);
+ }
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setkind_np(
+ &attr, PTHREAD_RWLOCK_PREFER_READER_NP),
+ 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setpshared(
+ &attr, PTHREAD_PROCESS_PRIVATE),
+ 0);
+ {
+ pthread_rwlock_t rwlock{};
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, &attr), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0);
+ }
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setpshared(
+ &attr, PTHREAD_PROCESS_SHARED),
+ 0);
+ {
+ pthread_rwlock_t rwlock{};
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, &attr), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&rwlock), 0);
+ }
+ attr.pref = -1;
+ {
+ pthread_rwlock_t rwlock{};
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, &attr), EINVAL);
+ }
+ attr.pref = PTHREAD_RWLOCK_PREFER_READER_NP;
+ attr.pshared = -1;
+ {
+ pthread_rwlock_t rwlock{};
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&rwlock, &attr), EINVAL);
+ }
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_destroy(&attr), 0);
+}
+
+struct SharedData {
+ pthread_rwlock_t lock;
+ int data;
+ LIBC_NAMESPACE::cpp::Atomic<int> reader_count;
+ bool writer_flag;
+ LIBC_NAMESPACE::cpp::Atomic<int> total_writer_count;
+};
+
+enum class Operation : int {
+ READ = 0,
+ WRITE = 1,
+ TIMED_READ = 2,
+ TIMED_WRITE = 3,
+ TRY_READ = 4,
+ TRY_WRITE = 5,
+ COUNT = 6
+};
+
+LIBC_NAMESPACE::RawMutex *io_mutex;
+struct ThreadGuard {
+ Operation record[64]{};
+ size_t cursor = 0;
+ void push(Operation op) { record[cursor++] = op; }
+ ~ThreadGuard() {
+ if (!LIBC_NAMESPACE::getenv("LIBC_PTHREAD_RWLOCK_TEST_VERBOSE"))
+ return;
+ pid_t pid = LIBC_NAMESPACE::syscall_impl(SYS_getpid);
+ pid_t tid = LIBC_NAMESPACE::syscall_impl(SYS_gettid);
+ io_mutex->lock(LIBC_NAMESPACE::cpp::nullopt, true);
+ LIBC_NAMESPACE::printf("process %d thread %d: ", pid, tid);
+ for (size_t i = 0; i < cursor; ++i)
+ LIBC_NAMESPACE::printf("%d ", static_cast<int>(record[i]));
+ LIBC_NAMESPACE::printf("\n");
+ io_mutex->unlock(true);
+ }
+};
+
+static void randomized_thread_operation(SharedData *data, ThreadGuard &guard) {
+ int buffer;
+ // We cannot reason about thread order anyway, let's go wild and randomize it
+ // directly using getrandom.
+ LIBC_NAMESPACE::getrandom(&buffer, sizeof(buffer), 0);
+ constexpr int TOTAL = static_cast<int>(Operation::COUNT);
+ Operation op = static_cast<Operation>(((buffer % TOTAL) + TOTAL) % TOTAL);
+ guard.push(op);
+ auto read_ops = [data]() {
+ ASSERT_FALSE(data->writer_flag);
+ data->reader_count.fetch_add(1, LIBC_NAMESPACE::cpp::MemoryOrder::RELAXED);
+ for (int i = 0; i < 10; ++i)
+ LIBC_NAMESPACE::sleep_briefly();
+ data->reader_count.fetch_sub(1, LIBC_NAMESPACE::cpp::MemoryOrder::RELAXED);
+ };
+ auto write_ops = [data]() {
+ ASSERT_FALSE(data->writer_flag);
+ data->data += 1;
+ data->writer_flag = true;
+ for (int i = 0; i < 10; ++i)
+ LIBC_NAMESPACE::sleep_briefly();
+ ASSERT_EQ(data->reader_count, 0);
+ data->writer_flag = false;
+ data->total_writer_count.fetch_add(1);
+ };
+ auto get_ts = []() {
+ timespec ts{};
+ LIBC_NAMESPACE::clock_gettime(CLOCK_REALTIME, &ts);
+ ts.tv_nsec += 5'000;
+ if (ts.tv_nsec >= 1'000'000'000) {
+ ts.tv_nsec -= 1'000'000'000;
+ ts.tv_sec += 1;
+ }
+ return ts;
+ };
+ switch (op) {
+ case Operation::READ: {
+ LIBC_NAMESPACE::pthread_rwlock_rdlock(&data->lock);
+ read_ops();
+ LIBC_NAMESPACE::pthread_rwlock_unlock(&data->lock);
+ break;
+ }
+ case Operation::WRITE: {
+ LIBC_NAMESPACE::pthread_rwlock_wrlock(&data->lock);
+ write_ops();
+ LIBC_NAMESPACE::pthread_rwlock_unlock(&data->lock);
+ break;
+ }
+ case Operation::TIMED_READ: {
+ timespec ts = get_ts();
+ if (LIBC_NAMESPACE::pthread_rwlock_timedrdlock(&data->lock, &ts) == 0) {
+ read_ops();
+ LIBC_NAMESPACE::pthread_rwlock_unlock(&data->lock);
+ }
+ break;
+ }
+ case Operation::TIMED_WRITE: {
+ timespec ts = get_ts();
+ if (LIBC_NAMESPACE::pthread_rwlock_timedwrlock(&data->lock, &ts) == 0) {
+ write_ops();
+ LIBC_NAMESPACE::pthread_rwlock_unlock(&data->lock);
+ }
+ break;
+ }
+ case Operation::TRY_READ: {
+ if (LIBC_NAMESPACE::pthread_rwlock_tryrdlock(&data->lock) == 0) {
+ read_ops();
+ LIBC_NAMESPACE::pthread_rwlock_unlock(&data->lock);
+ }
+ break;
+ }
+ case Operation::TRY_WRITE: {
+ if (LIBC_NAMESPACE::pthread_rwlock_trywrlock(&data->lock) == 0) {
+ write_ops();
+ LIBC_NAMESPACE::pthread_rwlock_unlock(&data->lock);
+ }
+ break;
+ }
+ case Operation::COUNT:
+ __builtin_trap();
+ }
+}
+
+static void
+randomized_process_operation(SharedData &data,
+ LIBC_NAMESPACE::cpp::Atomic<int> &finish_count,
+ int expected_count) {
+ pthread_t threads[32];
+ for (auto &i : threads)
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_create(
+ &i, nullptr,
+ [](void *arg) -> void * {
+ ThreadGuard guard{};
+ for (int i = 0; i < 64; ++i)
+ randomized_thread_operation(
+ reinterpret_cast<SharedData *>(arg), guard);
+ return nullptr;
+ },
+ &data),
+ 0);
+
+ for (auto &i : threads)
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_join(i, nullptr), 0);
+
+ finish_count.fetch_add(1);
+ while (finish_count.load() != expected_count)
+ LIBC_NAMESPACE::sleep_briefly();
+
+ ASSERT_EQ(data.total_writer_count.load(), data.data);
+ ASSERT_FALSE(data.writer_flag);
+ ASSERT_EQ(data.reader_count, 0);
+}
+
+static void single_process_test(int preference) {
+ SharedData data{};
+ data.data = 0;
+ data.reader_count = 0;
+ data.writer_flag = false;
+ data.total_writer_count.store(0);
+ pthread_rwlockattr_t attr{};
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_init(&attr), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setkind_np(&attr, preference),
+ 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&data.lock, nullptr), 0);
+ LIBC_NAMESPACE::cpp::Atomic<int> finish_count{0};
+ randomized_process_operation(data, finish_count, 1);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&data.lock), 0);
+}
+
+static void multiple_process_test(int preference) {
+ struct PShared {
+ SharedData data;
+ LIBC_NAMESPACE::cpp::Atomic<int> finish_count;
+ };
+ PShared *shared_data = reinterpret_cast<PShared *>(
+ LIBC_NAMESPACE::mmap(nullptr, sizeof(PShared), PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1, 0));
+ shared_data->data.data = 0;
+ shared_data->data.reader_count = 0;
+ shared_data->data.writer_flag = false;
+ shared_data->data.total_writer_count.store(0);
+ shared_data->finish_count.store(0);
+ pthread_rwlockattr_t attr{};
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_init(&attr), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setkind_np(&attr, preference),
+ 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlockattr_setpshared(
+ &attr, PTHREAD_PROCESS_SHARED),
+ 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_init(&shared_data->data.lock, &attr),
+ 0);
+ int pid = LIBC_NAMESPACE::fork();
+ randomized_process_operation(shared_data->data, shared_data->finish_count, 2);
+ if (pid == 0)
+ LIBC_NAMESPACE::exit(0);
+ else {
+ int status;
+ LIBC_NAMESPACE::waitpid(pid, &status, 0);
+ ASSERT_EQ(status, 0);
+ }
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_rwlock_destroy(&shared_data->data.lock), 0);
+ LIBC_NAMESPACE::munmap(shared_data, sizeof(PShared));
+}
+
+TEST_MAIN() {
+ io_mutex = new (LIBC_NAMESPACE::mmap(
+ nullptr, sizeof(LIBC_NAMESPACE::RawMutex), PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_SHARED, -1, 0)) LIBC_NAMESPACE::RawMutex();
+ smoke_test();
+ deadlock_detection_test();
+ try_lock_test();
+ destroy_before_unlock_test();
+ nullptr_test();
+ high_reader_count_test();
+ unusual_timespec_test();
+ timedlock_with_deadlock_test();
+ attributed_initialization_test();
+ single_process_test(PTHREAD_RWLOCK_PREFER_READER_NP);
+ single_process_test(PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
+ multiple_process_test(PTHREAD_RWLOCK_PREFER_READER_NP);
+ multiple_process_test(PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
+ io_mutex->~RawMutex();
+ LIBC_NAMESPACE::munmap(io_mutex, sizeof(LIBC_NAMESPACE::RawMutex));
+ return 0;
+}
More information about the libc-commits
mailing list