-
Notifications
You must be signed in to change notification settings - Fork 15.3k
[libc] Add Darwin mutex support via os_sync primitives #167722
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
493e811
1f802c9
21fe2f8
06e7d96
731dcc1
f080e84
4cf8557
74ed72e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,8 +21,14 @@ | |
|
|
||
| #include "llvm-libc-macros/linux/error-number-macros.h" | ||
|
|
||
| #else // __linux__ | ||
| #elif __APPLE__ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| #include <sys/errno.h> | ||
|
|
||
| #else // __APPLE__ | ||
|
|
||
| #include "llvm-libc-macros/generic-error-number-macros.h" | ||
|
|
||
| #endif | ||
|
|
||
| __BEGIN_C_DECLS | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,13 +23,36 @@ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS}) | |
| add_subdirectory(${LIBC_TARGET_OS}) | ||
| endif() | ||
|
|
||
| if(TARGET libc.src.__support.threads.${LIBC_TARGET_OS}.mutex) | ||
| if(TARGET libc.src.__support.threads.${LIBC_TARGET_OS}.futex_utils) | ||
| add_header_library( | ||
| mutex | ||
| HDRS | ||
| mutex.h | ||
| mutex.h | ||
| DEPENDS | ||
| .unix_mutex | ||
| ) | ||
| add_header_library( | ||
| unix_mutex | ||
| HDRS | ||
| unix_mutex.h | ||
| DEPENDS | ||
| .raw_mutex | ||
| ) | ||
|
Comment on lines
+34
to
+40
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move this above |
||
|
|
||
| add_header_library( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move this above |
||
| raw_mutex | ||
| HDRS | ||
| raw_mutex.h | ||
| DEPENDS | ||
| .${LIBC_TARGET_OS}.mutex | ||
| .${LIBC_TARGET_OS}.futex_utils | ||
| libc.src.__support.threads.sleep | ||
| libc.src.__support.time.abs_timeout | ||
| libc.src.__support.time.monotonicity | ||
| libc.src.__support.CPP.optional | ||
| libc.hdr.types.pid_t | ||
| COMPILE_OPTIONS | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move |
||
| -DLIBC_COPT_RAW_MUTEX_DEFAULT_SPIN_COUNT=${LIBC_CONF_RAW_MUTEX_DEFAULT_SPIN_COUNT} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| ${monotonicity_flags} | ||
| ) | ||
|
|
||
| add_object_library( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| if(NOT TARGET libc.src.__support.OSUtil.osutil) | ||
| return() | ||
| endif() | ||
|
|
||
| add_header_library( | ||
| futex_utils | ||
| HDRS | ||
| futex_utils.h | ||
| DEPENDS | ||
| libc.include.sys_syscall | ||
| libc.src.__support.OSUtil.osutil | ||
| libc.src.__support.CPP.atomic | ||
| libc.src.__support.CPP.limits | ||
| libc.src.__support.CPP.optional | ||
| libc.src.__support.threads.mutex_common | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| //===--- Futex utils for Darwin ------------------------*- C++-*-===// | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The license header line length seems strange. It should be 80-characters long in total. |
||
| // | ||
| // 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_DARWIN_FUTEX_UTILS_H | ||
| #define LLVM_LIBC_SRC___SUPPORT_THREADS_DARWIN_FUTEX_UTILS_H | ||
|
|
||
| #include "src/__support/CPP/atomic.h" | ||
| #include "src/__support/CPP/optional.h" | ||
| #include "src/__support/time/abs_timeout.h" | ||
| #include "src/__support/time/clock_conversion.h" | ||
|
|
||
| #include <os/os_sync_wait_on_address.h> | ||
|
|
||
| namespace LIBC_NAMESPACE_DECL { | ||
|
|
||
| using FutexWordType = uint32_t; | ||
|
|
||
| struct Futex : public cpp::Atomic<FutexWordType> { | ||
| using cpp::Atomic<FutexWordType>::Atomic; | ||
| using Timeout = internal::AbsTimeout; | ||
|
|
||
| LIBC_INLINE long wait(FutexWordType val, cpp::optional<Timeout> timeout, | ||
| bool /* is_shared */) { | ||
| // TODO(bojle): consider using OS_SYNC_WAIT_ON_ADDRESS_SHARED to sync | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So the document says:
This extra cost is expected and it is the reason why POSIX API also distinguish shared memory locks and normal private locks. |
||
| // betweeen processes. Catch: it is recommended to only be used by shared | ||
| // processes, not threads of a same process. | ||
|
|
||
| for (;;) { | ||
| if (this->load(cpp::MemoryOrder::RELAXED) != val) | ||
| return 0; | ||
| long ret = 0; | ||
| if (timeout) { | ||
| // Assuming, OS_CLOCK_MACH_ABSOLUTE_TIME is equivalent to CLOCK_REALTIME | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this assumption due to we have not port time conversion yet in this PR?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No. I couldn't find a reference stating what "absolute" means (in the macos context). Is it closer to CLOCK_REALTIME or CLOCK_MONOTONIC? The assumption is based on semantic similarity where I thought it could mean realtime more than monotonic. |
||
| uint64_t tnsec = timeout->get_timespec().tv_sec * 1000000000 + | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. try use existing unit conversion utilities if possible |
||
| timeout->get_timespec().tv_nsec; | ||
| ret = os_sync_wait_on_address_with_timeout( | ||
| reinterpret_cast<void *>(this), static_cast<uint64_t>(val), | ||
| sizeof(FutexWordType), OS_SYNC_WAIT_ON_ADDRESS_NONE, | ||
| OS_CLOCK_MACH_ABSOLUTE_TIME, tnsec); | ||
| } else { | ||
| ret = os_sync_wait_on_address( | ||
| reinterpret_cast<void *>(this), static_cast<uint64_t>(val), | ||
| sizeof(FutexWordType), OS_SYNC_WAIT_ON_ADDRESS_NONE); | ||
| } | ||
| if ((ret < 0) && (errno == ETIMEDOUT)) { | ||
| return -ETIMEDOUT; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please remove extra brace to conform LLVM's guide line. |
||
| } | ||
| // case when os_sync returns early with an error. retry. | ||
| if ((ret < 0) && ((errno == EINTR) || (errno == EFAULT))) { | ||
| continue; | ||
| } | ||
| return ret; | ||
| } | ||
| } | ||
|
|
||
| LIBC_INLINE long notify_one(bool /* is_shared */) { | ||
| // TODO(bojle): deal with is_shared | ||
| return os_sync_wake_by_address_any(reinterpret_cast<void *>(this), | ||
| sizeof(FutexWordType), | ||
| OS_SYNC_WAKE_BY_ADDRESS_NONE); | ||
| } | ||
|
|
||
| LIBC_INLINE long notify_all(bool /* is_shared */) { | ||
| // TODO(bojle): deal with is_shared | ||
| return os_sync_wake_by_address_all(reinterpret_cast<void *>(this), | ||
| sizeof(FutexWordType), | ||
| OS_SYNC_WAKE_BY_ADDRESS_NONE); | ||
| } | ||
| }; | ||
|
|
||
| } // namespace LIBC_NAMESPACE_DECL | ||
|
|
||
| #endif // LLVM_LIBC_SRC___SUPPORT_THREADS_DARWIN_FUTEX_UTILS_H | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| //===--- Implementation of a Darwin mutex class -----------*- C++-*-===// | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would prefer to lift POSIX/UNIX mutex to a shared design instead of making this platform specific, as most parts of the design should still be the same. If constants/headers are different, you can add platform macro guards.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Understood. I was uncertain as to the extent of generality we were looking for. I'll take this as a signal to make things as general as they can be (starting with moving mutex.h out of os folders) and into __support/thread/. |
||
| // | ||
| // 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_DARWIN_MUTEX_H | ||
| #define LLVM_LIBC_SRC___SUPPORT_THREADS_DARWIN_MUTEX_H | ||
|
|
||
| #include "src/__support/libc_assert.h" | ||
| #include "src/__support/macros/config.h" | ||
| #include "src/__support/threads/mutex_common.h" | ||
| #include "src/__support/threads/sleep.h" // For sleep_briefly | ||
| #include "src/__support/time/abs_timeout.h" | ||
|
|
||
| #include <mach/mach_init.h> // For mach_thread_self | ||
| #include <mach/mach_port.h> // For mach_port_t and MACH_PORT_NULL | ||
| #include <os/lock.h> // For os_unfair_lock | ||
| #include <time.h> // For clock_gettime | ||
|
|
||
| namespace LIBC_NAMESPACE_DECL { | ||
|
|
||
| // This file is an implementation of `LIBC_NAMESPACE::mutex` for Darwin-based | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I guess this implementation can be removed as futex-like lock is now ready to use? |
||
| // platforms. It is a wrapper around `os_unfair_lock`, which is a low-level, | ||
| // high-performance locking primitive provided by the kernel. | ||
| // | ||
| // `os_unfair_lock` is a non-recursive, thread-owned lock that blocks waiters | ||
| // efficiently in the kernel. As the name implies, it is "unfair," meaning | ||
| // it does not guarantee the order in which waiting threads acquire the lock. | ||
| // This trade-off allows for higher performance in contended scenarios. | ||
| // | ||
| // The lock must be unlocked from the same thread that locked it. Attempting | ||
| // to unlock from a different thread will result in a runtime error. | ||
| // | ||
| // This implementation is suitable for simple critical sections where fairness | ||
| // and reentrancy are not concerns. | ||
|
|
||
| class Mutex final { | ||
| os_unfair_lock_s lock_val = OS_UNFAIR_LOCK_INIT; | ||
| mach_port_t owner = MACH_PORT_NULL; | ||
|
|
||
| // API compatibility fields. | ||
| unsigned char timed; | ||
| unsigned char recursive; | ||
| unsigned char robust; | ||
| unsigned char pshared; | ||
|
|
||
| public: | ||
| LIBC_INLINE constexpr Mutex(bool is_timed, bool is_recursive, bool is_robust, | ||
| bool is_pshared) | ||
| : owner(MACH_PORT_NULL), timed(is_timed), recursive(is_recursive), | ||
| robust(is_robust), pshared(is_pshared) {} | ||
|
|
||
| LIBC_INLINE constexpr Mutex() | ||
| : owner(MACH_PORT_NULL), timed(0), recursive(0), robust(0), pshared(0) {} | ||
|
|
||
| LIBC_INLINE static MutexError init(Mutex *mutex, bool is_timed, bool is_recur, | ||
| bool is_robust, bool is_pshared) { | ||
| mutex->lock_val = OS_UNFAIR_LOCK_INIT; | ||
| mutex->owner = MACH_PORT_NULL; | ||
| mutex->timed = is_timed; | ||
| mutex->recursive = is_recur; | ||
| mutex->robust = is_robust; | ||
| mutex->pshared = is_pshared; | ||
| return MutexError::NONE; | ||
| } | ||
|
|
||
| LIBC_INLINE static MutexError destroy(Mutex *lock) { | ||
| LIBC_ASSERT(lock->owner == MACH_PORT_NULL && | ||
| "Mutex destroyed while locked."); | ||
| return MutexError::NONE; | ||
| } | ||
|
|
||
| LIBC_INLINE MutexError lock() { | ||
| os_unfair_lock_lock(&lock_val); | ||
| owner = mach_thread_self(); | ||
| return MutexError::NONE; | ||
| } | ||
|
|
||
| LIBC_INLINE MutexError timed_lock(internal::AbsTimeout abs_time) { | ||
| while (true) { | ||
| if (try_lock() == MutexError::NONE) { | ||
| return MutexError::NONE; | ||
| } | ||
|
|
||
| // Manually check if the timeout has expired. | ||
| struct timespec now; | ||
| // The clock used here must match the clock used to create the | ||
| // absolute timeout. | ||
| clock_gettime(abs_time.is_realtime() ? CLOCK_REALTIME : CLOCK_MONOTONIC, | ||
| &now); | ||
| const timespec &target_ts = abs_time.get_timespec(); | ||
|
|
||
| if (now.tv_sec > target_ts.tv_sec || (now.tv_sec == target_ts.tv_sec && | ||
| now.tv_nsec >= target_ts.tv_nsec)) { | ||
| // We might have acquired the lock between the last try_lock() and now. | ||
| // To avoid returning TIMEOUT incorrectly, we do one last try_lock(). | ||
| if (try_lock() == MutexError::NONE) | ||
| return MutexError::NONE; | ||
| return MutexError::TIMEOUT; | ||
| } | ||
|
|
||
| sleep_briefly(); | ||
| } | ||
| } | ||
|
|
||
| LIBC_INLINE MutexError unlock() { | ||
| // This check is crucial. It prevents both double-unlocks and unlocks | ||
| // by threads that do not own the mutex. | ||
| if (owner != mach_thread_self()) { | ||
| return MutexError::UNLOCK_WITHOUT_LOCK; | ||
| } | ||
| owner = MACH_PORT_NULL; | ||
| os_unfair_lock_unlock(&lock_val); | ||
| return MutexError::NONE; | ||
| } | ||
|
|
||
| LIBC_INLINE MutexError try_lock() { | ||
| if (os_unfair_lock_trylock(&lock_val)) { | ||
| owner = mach_thread_self(); | ||
| return MutexError::NONE; | ||
| } | ||
| return MutexError::BUSY; | ||
| } | ||
| }; | ||
|
|
||
| } // namespace LIBC_NAMESPACE_DECL | ||
|
|
||
| #endif // LLVM_LIBC_SRC___SUPPORT_THREADS_DARWIN_MUTEX_H | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
defined(__APPLE__)