[libc-commits] [libc] [libc] add internal rwlock implementation (PR #91142)
via libc-commits
libc-commits at lists.llvm.org
Sun May 5 14:14:54 PDT 2024
================
@@ -0,0 +1,412 @@
+//===--- 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 "src/__support/CPP/atomic.h"
+#include "src/__support/CPP/optional.h"
+#include "src/__support/libc_assert.h"
+#include "src/__support/macros/attributes.h"
+#include "src/__support/threads/linux/futex_word.h"
+
+#include "src/__support/threads/sleep.h"
+#include "src/errno/libc_errno.h"
+#include "src/time/clock_gettime.h"
+
+namespace LIBC_NAMESPACE {
+// A rwlock implementation using futexes.
+// Code is largely based on the Rust standard library implementation.
+// https://github.com/rust-lang/rust/blob/22a5267c83a3e17f2b763279eb24bb632c45dc6b/library/std/src/sys/sync/rwlock/futex.rs
+class RwLock {
+ // The state consists of a 30-bit reader counter, a 'readers waiting' flag,
+ // and a 'writers waiting' flag. Bits 0..30:
+ // 0: Unlocked
+ // 1..=0x3FFF_FFFE: Locked by N readers
+ // 0x3FFF_FFFF: Write locked
+ // Bit 30: Readers are waiting on this futex.
+ // Bit 31: Writers are waiting on the writer_notify futex.
+ cpp::Atomic<FutexWordType> state;
+ // The 'condition variable' to notify writers through.
+ // Incremented on every signal.
+ cpp::Atomic<FutexWordType> writer_notify;
+ // If the rwlock is shared between processes.
+ bool is_shared;
+
+ LIBC_INLINE_VAR static constexpr FutexWordType READ_LOCKED = 1u;
+ LIBC_INLINE_VAR static constexpr FutexWordType MASK = (1u << 30) - 1u;
+ LIBC_INLINE_VAR static constexpr FutexWordType WRITE_LOCKED = MASK;
+ LIBC_INLINE_VAR static constexpr FutexWordType MAX_READERS = MASK - 1u;
+ LIBC_INLINE_VAR static constexpr FutexWordType READERS_WAITING = 1u << 30u;
+ LIBC_INLINE_VAR static constexpr FutexWordType WRITERS_WAITING = 1u << 31u;
+ LIBC_INLINE_VAR static constexpr int_fast32_t SPIN_LIMIT = 100u;
+ LIBC_INLINE static constexpr bool is_unlocked(FutexWordType state) {
+ return (state & MASK) == 0u;
+ }
+ LIBC_INLINE static constexpr bool is_write_locked(FutexWordType state) {
+ return (state & MASK) == WRITE_LOCKED;
+ }
+ LIBC_INLINE static constexpr bool has_readers_waiting(FutexWordType state) {
+ return (state & READERS_WAITING) != 0u;
+ }
+ LIBC_INLINE static constexpr bool has_writers_waiting(FutexWordType state) {
+ return (state & WRITERS_WAITING) != 0u;
+ }
+ // This also returns false if the counter could overflow if we tried to read
+ // lock it.
+ //
+ // We don't allow read-locking if there's readers waiting, even if the lock is
+ // unlocked and there's no writers waiting. The only situation when this
+ // happens is after unlocking, at which point the unlocking thread might be
+ // waking up writers, which have priority over readers. The unlocking thread
+ // will clear the readers waiting bit and wake up readers, if necessary.
+ LIBC_INLINE static constexpr bool is_read_lockable(FutexWordType state) {
+ return (state & MASK) < MAX_READERS && !has_readers_waiting(state) &&
+ !has_writers_waiting(state);
+ }
+ LIBC_INLINE static constexpr bool
+ has_reached_max_readers(FutexWordType state) {
+ return (state & MASK) == MAX_READERS;
+ }
+
+ // Convert a relative timeout to an absolute timespec.
+ LIBC_INLINE static void abs_timeout(cpp::optional<timespec> &timeout) {
+ if (!timeout)
+ return;
+
+ int errno_backup = libc_errno;
+ timespec now;
+ // if we failed to get time, we move timeout to infinity by setting it to
+ // nullopt.
+ if (LIBC_NAMESPACE::clock_gettime(CLOCK_MONOTONIC, &now) != 0) {
+ timeout = cpp::nullopt;
+ libc_errno = errno_backup;
+ return;
+ }
+ if (__builtin_add_overflow(now.tv_sec, timeout->tv_sec, &timeout->tv_sec)) {
+ timeout = cpp::nullopt;
+ return;
+ }
+ if (__builtin_add_overflow(now.tv_nsec, timeout->tv_nsec,
+ &timeout->tv_nsec)) {
+ timeout = cpp::nullopt;
+ return;
+ }
+ if (now.tv_nsec >= 1'000'000'000) {
+ if (__builtin_add_overflow(now.tv_sec, 1, &timeout->tv_sec)) {
+ timeout = cpp::nullopt;
+ return;
+ }
+ timeout->tv_nsec -= 1'000'000'000;
+ }
+ }
+
+ template <typename F>
+ LIBC_INLINE bool fetch_update(cpp::Atomic<FutexWordType> &__restrict word,
+ FutexWordType &__restrict prev,
+ cpp::MemoryOrder set_order,
+ cpp::MemoryOrder fetch_order, F &&f) {
+ prev = word.load(fetch_order);
+ // It is okay to have spurious failures here as we are in a loop.
+ while (cpp::optional<FutexWordType> new_val = f(prev))
+ if (word.compare_exchange_weak(prev, *new_val, set_order, fetch_order))
+ return true;
+
+ return false;
+ }
+
+ template <typename F>
+ LIBC_INLINE FutexWordType spin_until(int_fast32_t spin, F &&f) {
+ for (;;) {
+ FutexWordType state = this->state.load(cpp::MemoryOrder::RELAXED);
+ if (f(state) || spin == 0)
+ return state;
+ // pause the pipeline to avoid extra memory loads due to speculation
+ sleep_briefly();
+ --spin;
+ }
+ }
+
+ LIBC_INLINE static bool
+ is_timeout(const cpp::optional<timespec> &abs_timeout) {
+ if (!abs_timeout)
+ return false;
+ timespec now;
+ LIBC_NAMESPACE::clock_gettime(CLOCK_MONOTONIC, &now);
+ if (now.tv_sec > abs_timeout->tv_sec ||
+ (now.tv_sec == abs_timeout->tv_sec &&
+ now.tv_nsec >= abs_timeout->tv_nsec))
+ return true;
+ return false;
+ }
+
+ LIBC_INLINE FutexWordType spin_read(int_fast32_t spin) {
+ // Stop spinning when it's unlocked or read locked, or when there's waiting
+ // threads.
+ return spin_until(spin, [&](FutexWordType state) -> bool {
+ return !is_write_locked(state) || has_readers_waiting(state) ||
+ has_writers_waiting(state) || is_unlocked(state);
----------------
QuarticCat wrote:
The `is_unlocked(state)` here seems redundant as `!is_write_locked(state)` implies either unlocked or read locked.
https://github.com/llvm/llvm-project/pull/91142
More information about the libc-commits
mailing list