diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt index ebdaa0f6de7fd..8dd11759ec28f 100644 --- a/libc/config/linux/aarch64/entrypoints.txt +++ b/libc/config/linux/aarch64/entrypoints.txt @@ -734,6 +734,11 @@ if(LLVM_LIBC_FULL_BUILD) libc.src.pthread.pthread_rwlockattr_init libc.src.pthread.pthread_rwlockattr_setkind_np libc.src.pthread.pthread_rwlockattr_setpshared + libc.src.pthread.pthread_spin_destroy + libc.src.pthread.pthread_spin_init + libc.src.pthread.pthread_spin_lock + libc.src.pthread.pthread_spin_trylock + libc.src.pthread.pthread_spin_unlock libc.src.pthread.pthread_self libc.src.pthread.pthread_setname_np libc.src.pthread.pthread_setspecific diff --git a/libc/config/linux/api.td b/libc/config/linux/api.td index 320f3e92183bf..6a7c64296bf92 100644 --- a/libc/config/linux/api.td +++ b/libc/config/linux/api.td @@ -143,6 +143,7 @@ def PThreadAPI : PublicAPI<"pthread.h"> { "pthread_once_t", "pthread_rwlockattr_t", "pthread_rwlock_t", + "pthread_spinlock_t", "pthread_t", ]; } diff --git a/libc/docs/dev/undefined_behavior.rst b/libc/docs/dev/undefined_behavior.rst index 9f50545d745d6..d0d882b7010e3 100644 --- a/libc/docs/dev/undefined_behavior.rst +++ b/libc/docs/dev/undefined_behavior.rst @@ -98,3 +98,11 @@ Unrecognized ``clockid_t`` values for ``pthread_rwlock_clock*`` APIs ---------------------------------------------------------------------- POSIX.1-2024 only demands support for ``CLOCK_REALTIME`` and ``CLOCK_MONOTONIC``. Currently, as in LLVM libc, if other clock ids are used, they will be treated as monotonic clocks. + +PThread SpinLock Destroy +------------------------ +POSIX.1 Issue 7 updates the spinlock destroy behavior description such that the return code for +uninitialized spinlock and invalid spinlock is left undefined. We follow the recommendation as in +POSIX.1-2024, where EINVAL is returned if the spinlock is invalid (here we only check for null pointers) or +EBUSY is returned if the spinlock is currently locked. The lock is poisoned after a successful destroy. That is, +subsequent operations on the lock object without any reinitialization will return EINVAL. diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt index 37cae19123318..cbde24e17619f 100644 --- a/libc/include/CMakeLists.txt +++ b/libc/include/CMakeLists.txt @@ -386,6 +386,7 @@ add_header_macro( .llvm-libc-types.pthread_once_t .llvm-libc-types.pthread_rwlock_t .llvm-libc-types.pthread_rwlockattr_t + .llvm-libc-types.pthread_spinlock_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 d8b975572e0dd..9e77ab226ce6c 100644 --- a/libc/include/llvm-libc-types/CMakeLists.txt +++ b/libc/include/llvm-libc-types/CMakeLists.txt @@ -56,6 +56,7 @@ 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_spinlock_t HDR pthread_spinlock_t.h DEPENDS .pid_t) add_header(pthread_t HDR pthread_t.h DEPENDS .__thread_type) add_header(rlim_t HDR rlim_t.h) add_header(time_t HDR time_t.h) diff --git a/libc/include/llvm-libc-types/pthread_spinlock_t.h b/libc/include/llvm-libc-types/pthread_spinlock_t.h new file mode 100644 index 0000000000000..03eb02dded9ef --- /dev/null +++ b/libc/include/llvm-libc-types/pthread_spinlock_t.h @@ -0,0 +1,17 @@ +//===-- Definition of pthread_spinlock_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_SPINLOCK_T_H +#define LLVM_LIBC_TYPES_PTHREAD_SPINLOCK_T_H +#include "llvm-libc-types/pid_t.h" +typedef struct { + unsigned char __lockword; + pid_t __owner; +} pthread_spinlock_t; + +#endif // LLVM_LIBC_TYPES_PTHREAD_SPINLOCK_T_H diff --git a/libc/newhdrgen/yaml/pthread.yaml b/libc/newhdrgen/yaml/pthread.yaml index d492dfcbd51eb..3d945d49140ca 100644 --- a/libc/newhdrgen/yaml/pthread.yaml +++ b/libc/newhdrgen/yaml/pthread.yaml @@ -14,6 +14,7 @@ types: - type_name: __pthread_start_t - type_name: __pthread_once_func_t - type_name: __atfork_callback_t + - type_name: pthread_spinlock_t enums: [] functions: - name: pthread_atfork @@ -404,3 +405,29 @@ functions: return_type: int arguments: - type: pthread_rwlock_t * + - name: pthread_spin_init + standards: POSIX + return_type: int + arguments: + - type: pthread_spinlock_t * + - type: int + - name: pthread_spin_destroy + standards: POSIX + return_type: int + arguments: + - type: pthread_spinlock_t * + - name: pthread_spin_lock + standards: POSIX + return_type: int + arguments: + - type: pthread_spinlock_t * + - name: pthread_spin_trylock + standards: POSIX + return_type: int + arguments: + - type: pthread_spinlock_t * + - name: pthread_spin_unlock + standards: POSIX + return_type: int + arguments: + - type: pthread_spinlock_t * diff --git a/libc/spec/posix.td b/libc/spec/posix.td index 0edf9080c712e..085f2ec34ab34 100644 --- a/libc/spec/posix.td +++ b/libc/spec/posix.td @@ -132,6 +132,9 @@ def POSIX : StandardSpec<"POSIX"> { PtrType PThreadRWLockTPtr = PtrType; RestrictedPtrType RestrictedPThreadRWLockTPtr = RestrictedPtrType; + NamedType PThreadSpinLockTType = NamedType<"pthread_spinlock_t">; + PtrType PThreadSpinLockTPtr = PtrType; + PtrType PThreadTPtr = PtrType; RestrictedPtrType RestrictedPThreadTPtr = RestrictedPtrType; @@ -1049,6 +1052,7 @@ def POSIX : StandardSpec<"POSIX"> { PThreadOnceT, PThreadRWLockAttrTType, PThreadRWLockTType, + PThreadSpinLockTType, PThreadStartT, PThreadTSSDtorT, PThreadTType, @@ -1360,6 +1364,31 @@ def POSIX : StandardSpec<"POSIX"> { RetValSpec, [ArgSpec] >, + FunctionSpec< + "pthread_spin_init", + RetValSpec, + [ArgSpec, ArgSpec] + >, + FunctionSpec< + "pthread_spin_destroy", + RetValSpec, + [ArgSpec] + >, + FunctionSpec< + "pthread_spin_lock", + RetValSpec, + [ArgSpec] + >, + FunctionSpec< + "pthread_spin_trylock", + RetValSpec, + [ArgSpec] + >, + FunctionSpec< + "pthread_spin_unlock", + RetValSpec, + [ArgSpec] + > ] >; diff --git a/libc/src/__support/threads/spin_lock.h b/libc/src/__support/threads/spin_lock.h index 8a36550568464..e176ad9eeac2a 100644 --- a/libc/src/__support/threads/spin_lock.h +++ b/libc/src/__support/threads/spin_lock.h @@ -11,26 +11,17 @@ #include "src/__support/CPP/atomic.h" #include "src/__support/macros/attributes.h" -#include "src/__support/macros/properties/architectures.h" #include "src/__support/threads/sleep.h" namespace LIBC_NAMESPACE_DECL { -namespace spinlock { -template -using AtomicOp = Return (cpp::Atomic::*)(LockWord, cpp::MemoryOrder, - cpp::MemoryScope); -} - -template Acquire, - spinlock::AtomicOp Release> -class SpinLockAdaptor { - cpp::Atomic flag; +class SpinLock { + cpp::Atomic flag; public: - LIBC_INLINE constexpr SpinLockAdaptor() : flag{false} {} + LIBC_INLINE constexpr SpinLock() : flag{0} {} LIBC_INLINE bool try_lock() { - return !flag.*Acquire(static_cast(1), cpp::MemoryOrder::ACQUIRE); + return !flag.exchange(1u, cpp::MemoryOrder::ACQUIRE); } LIBC_INLINE void lock() { // clang-format off @@ -60,22 +51,15 @@ class SpinLockAdaptor { while (flag.load(cpp::MemoryOrder::RELAXED)) sleep_briefly(); } - LIBC_INLINE void unlock() { - flag.*Release(static_cast(0), cpp::MemoryOrder::RELEASE); + LIBC_INLINE void unlock() { flag.store(0u, cpp::MemoryOrder::RELEASE); } + LIBC_INLINE bool is_locked() { return flag.load(cpp::MemoryOrder::ACQUIRE); } + LIBC_INLINE bool is_invalid() { + return flag.load(cpp::MemoryOrder::ACQUIRE) > 1; } + // poison the lock + LIBC_INLINE ~SpinLock() { flag.store(0xffu, cpp::MemoryOrder::RELEASE); } }; -// It is reported that atomic operations with higher-order semantics -// lead to better performance on GPUs. -#ifdef LIBC_TARGET_ARCH_IS_GPU -using SpinLock = - SpinLockAdaptor::fetch_or, - &cpp::Atomic::fetch_and>; -#else -using SpinLock = SpinLockAdaptor::exchange, - &cpp::Atomic::store>; -#endif - } // namespace LIBC_NAMESPACE_DECL #endif // LLVM_LIBC_SRC___SUPPORT_THREADS_SPIN_LOCK_H diff --git a/libc/src/pthread/CMakeLists.txt b/libc/src/pthread/CMakeLists.txt index 70d10e6c4e3f8..e7e92e5b60dc2 100644 --- a/libc/src/pthread/CMakeLists.txt +++ b/libc/src/pthread/CMakeLists.txt @@ -644,6 +644,71 @@ add_entrypoint_object( libc.src.__support.threads.linux.rwlock ) +add_entrypoint_object( + pthread_spin_init + SRCS + pthread_spin_init.cpp + HDRS + pthread_spin_init.h + DEPENDS + libc.include.pthread + libc.src.__support.threads.spin_lock + libc.src.__support.threads.identifier + libc.hdr.errno_macros +) + +add_entrypoint_object( + pthread_spin_destroy + SRCS + pthread_spin_destroy.cpp + HDRS + pthread_spin_destroy.h + DEPENDS + libc.include.pthread + libc.src.__support.threads.spin_lock + libc.src.__support.threads.identifier + libc.hdr.errno_macros +) + +add_entrypoint_object( + pthread_spin_lock + SRCS + pthread_spin_lock.cpp + HDRS + pthread_spin_lock.h + DEPENDS + libc.include.pthread + libc.src.__support.threads.spin_lock + libc.src.__support.threads.identifier + libc.hdr.errno_macros +) + +add_entrypoint_object( + pthread_spin_trylock + SRCS + pthread_spin_trylock.cpp + HDRS + pthread_spin_trylock.h + DEPENDS + libc.include.pthread + libc.src.__support.threads.spin_lock + libc.src.__support.threads.identifier + libc.hdr.errno_macros +) + +add_entrypoint_object( + pthread_spin_unlock + SRCS + pthread_spin_unlock.cpp + HDRS + pthread_spin_unlock.h + DEPENDS + libc.include.pthread + libc.src.__support.threads.spin_lock + libc.src.__support.threads.identifier + libc.hdr.errno_macros +) + add_entrypoint_object( pthread_once SRCS diff --git a/libc/src/pthread/pthread_spin_destroy.cpp b/libc/src/pthread/pthread_spin_destroy.cpp new file mode 100644 index 0000000000000..7d93dd967afed --- /dev/null +++ b/libc/src/pthread/pthread_spin_destroy.cpp @@ -0,0 +1,47 @@ +//===-- Implementation of pthread_spin_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_spin_destroy.h" +#include "hdr/errno_macros.h" +#include "src/__support/common.h" +#include "src/__support/threads/spin_lock.h" +namespace LIBC_NAMESPACE_DECL { + +static_assert(sizeof(pthread_spinlock_t::__lockword) == sizeof(SpinLock) && + alignof(decltype(pthread_spinlock_t::__lockword)) == + alignof(SpinLock), + "pthread_spinlock_t::__lockword and SpinLock must be of the same " + "size and alignment"); + +LLVM_LIBC_FUNCTION(int, pthread_spin_destroy, + ([[maybe_unused]] pthread_spinlock_t * lock)) { + // If an implementation detects that the value specified by the lock argument + // to pthread_spin_lock() or pthread_spin_trylock() does not refer to an + // initialized spin lock object, it is recommended that the function should + // fail and report an [EINVAL] error. + if (!lock) + return EINVAL; + auto spin_lock = reinterpret_cast(&lock->__lockword); + if (!spin_lock || spin_lock->is_invalid()) + return EINVAL; + + // If an implementation detects that the value specified by the lock argument + // to pthread_spin_destroy() or pthread_spin_init() refers to a locked spin + // lock object, or detects that the value specified by the lock argument to + // pthread_spin_init() refers to an already initialized spin lock object, it + // is recommended that the function should fail and report an [EBUSY] error. + if (spin_lock->is_locked()) + return EBUSY; + + // poison the lock + spin_lock->~SpinLock(); + lock->__owner = 0; + return 0; +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/pthread/pthread_spin_destroy.h b/libc/src/pthread/pthread_spin_destroy.h new file mode 100644 index 0000000000000..2581e9e92f70d --- /dev/null +++ b/libc/src/pthread/pthread_spin_destroy.h @@ -0,0 +1,21 @@ +//===-- Implementation header for pthread_spin_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_SPIN_DESTROY_H +#define LLVM_LIBC_SRC_PTHREAD_PTHREAD_SPIN_DESTROY_H + +#include "src/__support/macros/config.h" +#include + +namespace LIBC_NAMESPACE_DECL { + +int pthread_spin_destroy(pthread_spinlock_t *lock); + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC_PTHREAD_PTHREAD_SPIN_DESTROY_H diff --git a/libc/src/pthread/pthread_spin_init.cpp b/libc/src/pthread/pthread_spin_init.cpp new file mode 100644 index 0000000000000..54972475288bc --- /dev/null +++ b/libc/src/pthread/pthread_spin_init.cpp @@ -0,0 +1,37 @@ +//===-- Implementation of pthread_spin_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_spin_init.h" +#include "hdr/errno_macros.h" +#include "src/__support/CPP/new.h" +#include "src/__support/common.h" +#include "src/__support/threads/spin_lock.h" +#include // for PTHREAD_PROCESS_SHARED, PTHREAD_PROCESS_PRIVATE + +namespace LIBC_NAMESPACE_DECL { + +static_assert(sizeof(pthread_spinlock_t::__lockword) == sizeof(SpinLock) && + alignof(decltype(pthread_spinlock_t::__lockword)) == + alignof(SpinLock), + "pthread_spinlock_t::__lockword and SpinLock must be of the same " + "size and alignment"); + +LLVM_LIBC_FUNCTION(int, pthread_spin_init, + (pthread_spinlock_t * lock, [[maybe_unused]] int pshared)) { + if (!lock) + return EINVAL; + if (pshared != PTHREAD_PROCESS_SHARED && pshared != PTHREAD_PROCESS_PRIVATE) + return EINVAL; + // The spin lock here is a simple atomic flag, so we don't need to do any + // special handling for pshared. + ::new (&lock->__lockword) SpinLock(); + lock->__owner = 0; + return 0; +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/pthread/pthread_spin_init.h b/libc/src/pthread/pthread_spin_init.h new file mode 100644 index 0000000000000..89f7e3adc82e9 --- /dev/null +++ b/libc/src/pthread/pthread_spin_init.h @@ -0,0 +1,21 @@ +//===-- Implementation header for pthread_spin_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_SPIN_INIT_H +#define LLVM_LIBC_SRC_PTHREAD_PTHREAD_SPIN_INIT_H + +#include "src/__support/macros/config.h" +#include + +namespace LIBC_NAMESPACE_DECL { + +int pthread_spin_init(pthread_spinlock_t *lock, int pshared); + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC_PTHREAD_PTHREAD_SPIN_INIT_H diff --git a/libc/src/pthread/pthread_spin_lock.cpp b/libc/src/pthread/pthread_spin_lock.cpp new file mode 100644 index 0000000000000..61c8db14e4873 --- /dev/null +++ b/libc/src/pthread/pthread_spin_lock.cpp @@ -0,0 +1,47 @@ +//===-- Implementation of pthread_spin_lock 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_spin_lock.h" +#include "hdr/errno_macros.h" +#include "src/__support/common.h" +#include "src/__support/threads/identifier.h" +#include "src/__support/threads/spin_lock.h" + +namespace LIBC_NAMESPACE_DECL { + +static_assert(sizeof(pthread_spinlock_t::__lockword) == sizeof(SpinLock) && + alignof(decltype(pthread_spinlock_t::__lockword)) == + alignof(SpinLock), + "pthread_spinlock_t::__lockword and SpinLock must be of the same " + "size and alignment"); + +LLVM_LIBC_FUNCTION(int, pthread_spin_lock, (pthread_spinlock_t * lock)) { + // If an implementation detects that the value specified by the lock argument + // to pthread_spin_lock() or pthread_spin_trylock() does not refer to an + // initialized spin lock object, it is recommended that the function should + // fail and report an [EINVAL] error. + if (!lock) + return EINVAL; + auto spin_lock = reinterpret_cast(&lock->__lockword); + if (spin_lock->is_invalid()) + return EINVAL; + + pid_t self_tid = internal::gettid(); + // If an implementation detects that the value specified by the lock argument + // to pthread_spin_lock() refers to a spin lock object for which the calling + // thread already holds the lock, it is recommended that the function should + // fail and report an [EDEADLK] error. + if (lock->__owner == self_tid) + return EDEADLK; + + spin_lock->lock(); + lock->__owner = self_tid; + return 0; +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/pthread/pthread_spin_lock.h b/libc/src/pthread/pthread_spin_lock.h new file mode 100644 index 0000000000000..835aa858b25f8 --- /dev/null +++ b/libc/src/pthread/pthread_spin_lock.h @@ -0,0 +1,21 @@ +//===-- Implementation header for pthread_spin_lock 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_SPIN_LOCK_H +#define LLVM_LIBC_SRC_PTHREAD_PTHREAD_SPIN_LOCK_H + +#include "src/__support/macros/config.h" +#include + +namespace LIBC_NAMESPACE_DECL { + +int pthread_spin_lock(pthread_spinlock_t *lock); + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC_PTHREAD_PTHREAD_SPIN_LOCK_H diff --git a/libc/src/pthread/pthread_spin_trylock.cpp b/libc/src/pthread/pthread_spin_trylock.cpp new file mode 100644 index 0000000000000..99b0eac413554 --- /dev/null +++ b/libc/src/pthread/pthread_spin_trylock.cpp @@ -0,0 +1,41 @@ +//===-- Implementation of pthread_spin_trylock 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_spin_trylock.h" +#include "hdr/errno_macros.h" +#include "src/__support/common.h" +#include "src/__support/threads/identifier.h" +#include "src/__support/threads/spin_lock.h" + +namespace LIBC_NAMESPACE_DECL { + +static_assert(sizeof(pthread_spinlock_t::__lockword) == sizeof(SpinLock) && + alignof(decltype(pthread_spinlock_t::__lockword)) == + alignof(SpinLock), + "pthread_spinlock_t::__lockword and SpinLock must be of the same " + "size and alignment"); + +LLVM_LIBC_FUNCTION(int, pthread_spin_trylock, (pthread_spinlock_t * lock)) { + // If an implementation detects that the value specified by the lock argument + // to pthread_spin_lock() or pthread_spin_trylock() does not refer to an + // initialized spin lock object, it is recommended that the function should + // fail and report an [EINVAL] error. + if (!lock) + return EINVAL; + auto spin_lock = reinterpret_cast(&lock->__lockword); + if (!spin_lock || spin_lock->is_invalid()) + return EINVAL; + // Try to acquire the lock without blocking. + if (!spin_lock->try_lock()) + return EBUSY; + // We have acquired the lock. Update the owner field. + lock->__owner = internal::gettid(); + return 0; +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/pthread/pthread_spin_trylock.h b/libc/src/pthread/pthread_spin_trylock.h new file mode 100644 index 0000000000000..e175ab8d7cfbe --- /dev/null +++ b/libc/src/pthread/pthread_spin_trylock.h @@ -0,0 +1,22 @@ +//===-- Implementation header for pthread_spin_trylock 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_SPIN_TRYLOCK_H +#define LLVM_LIBC_SRC_PTHREAD_PTHREAD_SPIN_TRYLOCK_H + +#include "src/__support/macros/config.h" +#include + +namespace LIBC_NAMESPACE_DECL { + +int pthread_spin_trylock(pthread_spinlock_t *lock); + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC_PTHREAD_PTHREAD_SPIN_TRYLOCK_H diff --git a/libc/src/pthread/pthread_spin_unlock.cpp b/libc/src/pthread/pthread_spin_unlock.cpp new file mode 100644 index 0000000000000..a02f2b3604587 --- /dev/null +++ b/libc/src/pthread/pthread_spin_unlock.cpp @@ -0,0 +1,44 @@ +//===-- Implementation of pthread_spin_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_spin_unlock.h" +#include "hdr/errno_macros.h" +#include "src/__support/common.h" +#include "src/__support/threads/identifier.h" +#include "src/__support/threads/spin_lock.h" + +namespace LIBC_NAMESPACE_DECL { + +static_assert(sizeof(pthread_spinlock_t::__lockword) == sizeof(SpinLock) && + alignof(decltype(pthread_spinlock_t::__lockword)) == + alignof(SpinLock), + "pthread_spinlock_t::__lockword and SpinLock must be of the same " + "size and alignment"); + +LLVM_LIBC_FUNCTION(int, pthread_spin_unlock, (pthread_spinlock_t * lock)) { + // If an implementation detects that the value specified by the lock argument + // to pthread_spin_lock() or pthread_spin_trylock() does not refer to an + // initialized spin lock object, it is recommended that the function should + // fail and report an [EINVAL] error. + if (!lock) + return EINVAL; + auto spin_lock = reinterpret_cast(&lock->__lockword); + if (!spin_lock || spin_lock->is_invalid()) + return EINVAL; + // If an implementation detects that the value specified by the lock argument + // to pthread_spin_unlock() refers to a spin lock object for which the current + // thread does not hold the lock, it is recommended that the function should + // fail and report an [EPERM] error. + if (lock->__owner != internal::gettid()) + return EPERM; + // Release the lock. + lock->__owner = 0; + spin_lock->unlock(); + return 0; +} +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/pthread/pthread_spin_unlock.h b/libc/src/pthread/pthread_spin_unlock.h new file mode 100644 index 0000000000000..4918613f2e6c6 --- /dev/null +++ b/libc/src/pthread/pthread_spin_unlock.h @@ -0,0 +1,21 @@ +//===-- Implementation header for pthread_spin_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_SPIN_UNLOCK_H +#define LLVM_LIBC_SRC_PTHREAD_PTHREAD_SPIN_UNLOCK_H + +#include "src/__support/macros/config.h" +#include + +namespace LIBC_NAMESPACE_DECL { + +int pthread_spin_unlock(pthread_spinlock_t *lock); + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC_PTHREAD_PTHREAD_SPIN_UNLOCK_H diff --git a/libc/test/integration/src/pthread/CMakeLists.txt b/libc/test/integration/src/pthread/CMakeLists.txt index eb26822597c2f..48d4368df156d 100644 --- a/libc/test/integration/src/pthread/CMakeLists.txt +++ b/libc/test/integration/src/pthread/CMakeLists.txt @@ -24,9 +24,9 @@ add_integration_test( SRCS pthread_rwlock_test.cpp DEPENDS + libc.hdr.time_macros + libc.hdr.errno_macros 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 @@ -59,6 +59,24 @@ add_integration_test( libc.src.__support.threads.sleep ) +add_integration_test( + pthread_spinlock_test + SUITE + libc-pthread-integration-tests + SRCS + pthread_spinlock_test.cpp + DEPENDS + libc.hdr.errno_macros + libc.include.pthread + libc.src.pthread.pthread_spin_init + libc.src.pthread.pthread_spin_destroy + libc.src.pthread.pthread_spin_lock + libc.src.pthread.pthread_spin_trylock + libc.src.pthread.pthread_spin_unlock + libc.src.pthread.pthread_create + libc.src.pthread.pthread_join +) + 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 index 9f5fba187713e..4cd4255a333e6 100644 --- a/libc/test/integration/src/pthread/pthread_rwlock_test.cpp +++ b/libc/test/integration/src/pthread/pthread_rwlock_test.cpp @@ -6,6 +6,8 @@ // //===----------------------------------------------------------------------===// +#include "hdr/errno_macros.h" +#include "hdr/time_macros.h" #include "src/__support/CPP/atomic.h" #include "src/__support/CPP/new.h" #include "src/__support/OSUtil/syscall.h" @@ -40,9 +42,7 @@ #include "src/time/clock_gettime.h" #include "src/unistd/fork.h" #include "test/IntegrationTest/test.h" -#include #include -#include namespace LIBC_NAMESPACE_DECL { namespace rwlock { diff --git a/libc/test/integration/src/pthread/pthread_spinlock_test.cpp b/libc/test/integration/src/pthread/pthread_spinlock_test.cpp new file mode 100644 index 0000000000000..233daf8332fb0 --- /dev/null +++ b/libc/test/integration/src/pthread/pthread_spinlock_test.cpp @@ -0,0 +1,145 @@ +//===-- Tests for pthread_spinlock ----------------------------------------===// +// +// 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 "hdr/errno_macros.h" +#include "src/pthread/pthread_create.h" +#include "src/pthread/pthread_join.h" +#include "src/pthread/pthread_spin_destroy.h" +#include "src/pthread/pthread_spin_init.h" +#include "src/pthread/pthread_spin_lock.h" +#include "src/pthread/pthread_spin_trylock.h" +#include "src/pthread/pthread_spin_unlock.h" +#include "test/IntegrationTest/test.h" +#include + +namespace { +void smoke_test() { + pthread_spinlock_t lock; + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE), + 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_lock(&lock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_unlock(&lock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(&lock), 0); +} + +void trylock_test() { + pthread_spinlock_t lock; + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE), + 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_trylock(&lock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_trylock(&lock), EBUSY); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_unlock(&lock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_trylock(&lock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_unlock(&lock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(&lock), 0); +} + +void destroy_held_lock_test() { + pthread_spinlock_t lock; + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE), + 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_lock(&lock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(&lock), EBUSY); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_unlock(&lock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(&lock), 0); +} + +void use_after_destroy_test() { + pthread_spinlock_t lock; + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE), + 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(&lock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_unlock(&lock), EINVAL); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_lock(&lock), EINVAL); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_trylock(&lock), EINVAL); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(&lock), EINVAL); +} + +void unlock_without_holding_test() { + pthread_spinlock_t lock; + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE), + 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_unlock(&lock), EPERM); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(&lock), 0); +} + +void deadlock_test() { + pthread_spinlock_t lock; + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE), + 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_lock(&lock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_lock(&lock), EDEADLK); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_unlock(&lock), 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(&lock), 0); +} + +void null_lock_test() { + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_init(nullptr, 0), EINVAL); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_lock(nullptr), EINVAL); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_trylock(nullptr), EINVAL); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_unlock(nullptr), EINVAL); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(nullptr), EINVAL); +} + +void pshared_attribute_test() { + pthread_spinlock_t lock; + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_init(&lock, PTHREAD_PROCESS_SHARED), + 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(&lock), 0); + + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE), + 0); + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(&lock), 0); + + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_init(&lock, -1), EINVAL); +} + +void multi_thread_test() { + struct shared_data { + pthread_spinlock_t lock; + int count = 0; + } shared; + pthread_t thread[10]; + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_init(&shared.lock, 0), 0); + for (int i = 0; i < 10; ++i) { + ASSERT_EQ( + LIBC_NAMESPACE::pthread_create( + &thread[i], nullptr, + [](void *arg) -> void * { + auto *data = static_cast(arg); + for (int j = 0; j < 1000; ++j) { + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_lock(&data->lock), 0); + data->count += j; + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_unlock(&data->lock), 0); + } + return nullptr; + }, + &shared), + 0); + } + for (int i = 0; i < 10; ++i) { + ASSERT_EQ(LIBC_NAMESPACE::pthread_join(thread[i], nullptr), 0); + } + ASSERT_EQ(LIBC_NAMESPACE::pthread_spin_destroy(&shared.lock), 0); + ASSERT_EQ(shared.count, 1000 * 999 * 5); +} + +} // namespace + +TEST_MAIN() { + smoke_test(); + trylock_test(); + destroy_held_lock_test(); + use_after_destroy_test(); + unlock_without_holding_test(); + deadlock_test(); + multi_thread_test(); + null_lock_test(); + pshared_attribute_test(); + return 0; +}