[libc++] Utilities for implementing stop_token
authorHui <hui.xie0621@gmail.com>
Tue, 9 May 2023 15:51:39 +0000 (16:51 +0100)
committerHui <hui.xie0621@gmail.com>
Wed, 17 May 2023 06:21:40 +0000 (07:21 +0100)
This change contains three util classes that were out from D145183 to make incremental progress
- automic_unique_lock
- intrusive_list
- intrusive_shared_ptr

Differential Revision: https://reviews.llvm.org/D150205

libcxx/include/CMakeLists.txt
libcxx/include/__stop_token/atomic_unique_lock.h [new file with mode: 0644]
libcxx/include/__stop_token/intrusive_list_view.h [new file with mode: 0644]
libcxx/include/__stop_token/intrusive_shared_ptr.h [new file with mode: 0644]
libcxx/include/libcxx.imp
libcxx/include/module.modulemap.in
libcxx/test/libcxx/private_headers.verify.cpp
libcxx/test/libcxx/thread/thread.stoptoken/atomic_unique_lock.pass.cpp [new file with mode: 0644]
libcxx/test/libcxx/thread/thread.stoptoken/intrusive_list_view.pass.cpp [new file with mode: 0644]
libcxx/test/libcxx/thread/thread.stoptoken/intrusive_shared_ptr.pass.cpp [new file with mode: 0644]

index 190616a..6bf5af8 100644 (file)
@@ -629,6 +629,9 @@ set(files
   __ranges/zip_view.h
   __split_buffer
   __std_mbstate_t.h
+  __stop_token/atomic_unique_lock.h
+  __stop_token/intrusive_list_view.h
+  __stop_token/intrusive_shared_ptr.h
   __string/char_traits.h
   __string/constexpr_c_functions.h
   __string/extern_template_lists.h
diff --git a/libcxx/include/__stop_token/atomic_unique_lock.h b/libcxx/include/__stop_token/atomic_unique_lock.h
new file mode 100644 (file)
index 0000000..f21c742
--- /dev/null
@@ -0,0 +1,139 @@
+// -*- 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 _LIBCPP___STOP_TOKEN_ATOMIC_UNIQUE_GUARD_H
+#define _LIBCPP___STOP_TOKEN_ATOMIC_UNIQUE_GUARD_H
+
+#include <__bit/popcount.h>
+#include <__config>
+#include <atomic>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER >= 20
+
+// This class implements an RAII unique_lock without a mutex.
+// It uses std::atomic<State>,
+// where State contains a lock bit and might contain other data,
+// and LockedBit is the value of State when the lock bit is set, e.g  1 << 2
+template <class _State, _State _LockedBit>
+class __atomic_unique_lock {
+  static_assert(std::popcount(_LockedBit) == 1, "LockedBit must be an integer where only one bit is set");
+
+  std::atomic<_State>& __state_;
+  bool __is_locked_;
+
+public:
+  _LIBCPP_HIDE_FROM_ABI explicit __atomic_unique_lock(std::atomic<_State>& __state) noexcept
+      : __state_(__state), __is_locked_(true) {
+    __lock();
+  }
+
+  template <class _Pred>
+  _LIBCPP_HIDE_FROM_ABI __atomic_unique_lock(std::atomic<_State>& __state, _Pred&& __give_up_locking) noexcept
+      : __state_(__state), __is_locked_(false) {
+    __is_locked_ = __lock_impl(__give_up_locking, __set_locked_bit, std::memory_order_acquire);
+  }
+
+  template <class _Pred, class _UnaryFunction>
+  _LIBCPP_HIDE_FROM_ABI __atomic_unique_lock(
+      std::atomic<_State>& __state,
+      _Pred&& __give_up_locking,
+      _UnaryFunction&& __state_after_lock,
+      std::memory_order __locked_ordering) noexcept
+      : __state_(__state), __is_locked_(false) {
+    __is_locked_ = __lock_impl(__give_up_locking, __state_after_lock, __locked_ordering);
+  }
+
+  __atomic_unique_lock(const __atomic_unique_lock&)            = delete;
+  __atomic_unique_lock(__atomic_unique_lock&&)                 = delete;
+  __atomic_unique_lock& operator=(const __atomic_unique_lock&) = delete;
+  __atomic_unique_lock& operator=(__atomic_unique_lock&&)      = delete;
+
+  _LIBCPP_HIDE_FROM_ABI ~__atomic_unique_lock() {
+    if (__is_locked_) {
+      __unlock();
+    }
+  }
+
+  _LIBCPP_HIDE_FROM_ABI bool __owns_lock() const noexcept { return __is_locked_; }
+
+  _LIBCPP_HIDE_FROM_ABI void __lock() noexcept {
+    const auto __never_give_up_locking = [](_State) { return false; };
+    // std::memory_order_acquire because we'd like to make sure that all the read operations after the lock can read the
+    // up-to-date values.
+    __lock_impl(__never_give_up_locking, __set_locked_bit, std::memory_order_acquire);
+    __is_locked_ = true;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI void __unlock() noexcept {
+    // unset the _LockedBit. `memory_order_release` because we need to make sure all the write operations before calling
+    // `__unlock` will be made visible to other threads
+    __state_.fetch_and(static_cast<_State>(~_LockedBit), std::memory_order_release);
+    __state_.notify_all();
+    __is_locked_ = false;
+  }
+
+private:
+  template <class _Pred, class _UnaryFunction>
+  _LIBCPP_HIDE_FROM_ABI bool
+  __lock_impl(_Pred&& __give_up_locking, // while trying to lock the state, if the predicate returns true, give up
+                                         // locking and return
+              _UnaryFunction&& __state_after_lock,
+              std::memory_order __locked_ordering) noexcept {
+    // At this stage, until we exit the inner while loop, other than the atomic state, we are not reading any order
+    // dependent values that is written on other threads, or writing anything that needs to be seen on other threads.
+    // Therefore `memory_order_relaxed` is enough.
+    _State __current_state = __state_.load(std::memory_order_relaxed);
+    do {
+      while (true) {
+        if (__give_up_locking(__current_state)) {
+          // user provided early return condition. fail to lock
+          return false;
+        } else if ((__current_state & _LockedBit) != 0) {
+          // another thread has locked the state, we need to wait
+          __state_.wait(__current_state, std::memory_order_relaxed);
+          // when it is woken up by notifyAll or spuriously, the __state_
+          // might have changed. reload the state
+          // Note that the new state's _LockedBit may or may not equal to 0
+          __current_state = __state_.load(std::memory_order_relaxed);
+        } else {
+          // at least for now, it is not locked. we can try `compare_exchange_weak` to lock it.
+          // Note that the variable `__current_state`'s lock bit has to be 0 at this point.
+          break;
+        }
+      }
+    } while (!__state_.compare_exchange_weak(
+        __current_state, // if __state_ has the same value of __current_state, lock bit must be zero before exchange and
+                         // we are good to lock/exchange and return. If _state has a different value, because other
+                         // threads locked it between the `break` statement above and this statement, exchange will fail
+                         // and go back to the inner while loop above.
+        __state_after_lock(__current_state), // state after lock. Usually it should be __current_state | _LockedBit.
+                                             // Some use cases need to set other bits at the same time as an atomic
+                                             // operation therefore we accept a function
+        __locked_ordering,        // sucessful exchange order. Usually it should be std::memory_order_acquire.
+                                  // Some use cases need more strict ordering therefore we accept it as a parameter
+        std::memory_order_relaxed // fail to exchange order. We don't need any ordering as we are going back to the
+                                  // inner while loop
+        ));
+    return true;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI static constexpr auto __set_locked_bit = [](_State __state) { return __state | _LockedBit; };
+};
+
+#endif // _LIBCPP_STD_VER >= 20
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___STOP_TOKEN_ATOMIC_UNIQUE_GUARD_H
diff --git a/libcxx/include/__stop_token/intrusive_list_view.h b/libcxx/include/__stop_token/intrusive_list_view.h
new file mode 100644 (file)
index 0000000..88c8f7e
--- /dev/null
@@ -0,0 +1,85 @@
+// -*- 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 _LIBCPP___STOP_TOKEN_INTRUSIVE_LIST_VIEW_H
+#define _LIBCPP___STOP_TOKEN_INTRUSIVE_LIST_VIEW_H
+
+#include <__assert>
+#include <__config>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER >= 20
+
+template <class _Derived>
+struct __intrusive_node_base {
+  _Derived* __next_ = nullptr;
+  _Derived* __prev_ = nullptr;
+};
+
+// This class is a view of underlying double-linked list.
+// It does not own the nodes. It provides user-friendly
+// operations on the linked list.
+template <class _Node>
+struct __intrusive_list_view {
+  _LIBCPP_HIDE_FROM_ABI __intrusive_list_view()                                        = default;
+  _LIBCPP_HIDE_FROM_ABI __intrusive_list_view(__intrusive_list_view const&)            = default;
+  _LIBCPP_HIDE_FROM_ABI __intrusive_list_view(__intrusive_list_view&&)                 = default;
+  _LIBCPP_HIDE_FROM_ABI __intrusive_list_view& operator=(__intrusive_list_view const&) = default;
+  _LIBCPP_HIDE_FROM_ABI __intrusive_list_view& operator=(__intrusive_list_view&&)      = default;
+  _LIBCPP_HIDE_FROM_ABI ~__intrusive_list_view()                                       = default;
+
+  _LIBCPP_HIDE_FROM_ABI bool __empty() const noexcept { return __head_ == nullptr; }
+
+  _LIBCPP_HIDE_FROM_ABI void __push_front(_Node* __node) noexcept {
+    __node->__next_ = __head_;
+    if (__head_) {
+      __head_->__prev_ = __node;
+    }
+    __head_ = __node;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI _Node* __pop_front() noexcept {
+    _Node* __front = __head_;
+    __head_        = __head_->__next_;
+    if (__head_) {
+      __head_->__prev_ = nullptr;
+    }
+    // OK not to set __front->__next_ = nullptr as __front is not part of the list anymore
+    return __front;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI void __remove(_Node* __node) noexcept {
+    if (__node->__prev_) {
+      // prev exists, set its next to our next to skip __node
+      __node->__prev_->__next_ = __node->__next_;
+      if (__node->__next_) {
+        __node->__next_->__prev_ = __node->__prev_;
+      }
+    } else {
+      _LIBCPP_ASSERT(__node == __head_, "Node to be removed has no prev node, so it has to be the head");
+      __pop_front();
+    }
+  }
+
+  _LIBCPP_HIDE_FROM_ABI bool __is_head(_Node* __node) noexcept { return __node == __head_; }
+
+private:
+  _Node* __head_ = nullptr;
+};
+
+#endif // _LIBCPP_STD_VER >= 20
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___STOP_TOKEN_INTRUSIVE_LIST_VIEW_H
diff --git a/libcxx/include/__stop_token/intrusive_shared_ptr.h b/libcxx/include/__stop_token/intrusive_shared_ptr.h
new file mode 100644 (file)
index 0000000..68a57bf
--- /dev/null
@@ -0,0 +1,128 @@
+// -*- 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 _LIBCPP___STOP_TOKEN_INTRUSIVE_SHARED_PTR_H
+#define _LIBCPP___STOP_TOKEN_INTRUSIVE_SHARED_PTR_H
+
+#include <__atomic/memory_order.h>
+#include <__config>
+#include <__type_traits/is_reference.h>
+#include <__utility/move.h>
+#include <__utility/swap.h>
+#include <cstddef>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER >= 20
+
+// For intrusive_shared_ptr to work with a type T, specialize __intrusive_shared_ptr_traits<T> and implement
+// the following function:
+//
+// static std::atomic<U>& __get_atomic_ref_count(T&);
+//
+// where U must be an integral type representing the number of references to the object.
+template <class _Tp>
+struct __intrusive_shared_ptr_traits;
+
+// A reference counting shared_ptr for types whose reference counter
+// is stored inside the class _Tp itself.
+// When the reference count goes to zero, the destructor of _Tp will be called
+template <class _Tp>
+struct __intrusive_shared_ptr {
+  _LIBCPP_HIDE_FROM_ABI __intrusive_shared_ptr() = default;
+
+  _LIBCPP_HIDE_FROM_ABI explicit __intrusive_shared_ptr(_Tp* __raw_ptr) : __raw_ptr_(__raw_ptr) {
+    if (__raw_ptr_)
+      __increment_ref_count(*__raw_ptr_);
+  }
+
+  _LIBCPP_HIDE_FROM_ABI __intrusive_shared_ptr(const __intrusive_shared_ptr& __other) noexcept
+      : __raw_ptr_(__other.__raw_ptr_) {
+    if (__raw_ptr_)
+      __increment_ref_count(*__raw_ptr_);
+  }
+
+  _LIBCPP_HIDE_FROM_ABI __intrusive_shared_ptr(__intrusive_shared_ptr&& __other) noexcept
+      : __raw_ptr_(__other.__raw_ptr_) {
+    __other.__raw_ptr_ = nullptr;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI __intrusive_shared_ptr& operator=(const __intrusive_shared_ptr& __other) noexcept {
+    if (__other.__raw_ptr_ != __raw_ptr_) {
+      if (__other.__raw_ptr_) {
+        __increment_ref_count(*__other.__raw_ptr_);
+      }
+      if (__raw_ptr_) {
+        __decrement_ref_count(*__raw_ptr_);
+      }
+      __raw_ptr_ = __other.__raw_ptr_;
+    }
+    return *this;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI __intrusive_shared_ptr& operator=(__intrusive_shared_ptr&& __other) noexcept {
+    __intrusive_shared_ptr(std::move(__other)).swap(*this);
+    return *this;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI ~__intrusive_shared_ptr() {
+    if (__raw_ptr_) {
+      __decrement_ref_count(*__raw_ptr_);
+    }
+  }
+
+  _LIBCPP_HIDE_FROM_ABI _Tp* operator->() const noexcept { return __raw_ptr_; }
+  _LIBCPP_HIDE_FROM_ABI _Tp& operator*() const noexcept { return *__raw_ptr_; }
+  _LIBCPP_HIDE_FROM_ABI explicit operator bool() const noexcept { return __raw_ptr_ != nullptr; }
+
+  _LIBCPP_HIDE_FROM_ABI void swap(__intrusive_shared_ptr& __other) { std::swap(__raw_ptr_, __other.__raw_ptr_); }
+
+  _LIBCPP_HIDE_FROM_ABI friend void swap(__intrusive_shared_ptr& __lhs, __intrusive_shared_ptr& __rhs) {
+    __lhs.swap(__rhs);
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend bool constexpr
+  operator==(const __intrusive_shared_ptr&, const __intrusive_shared_ptr&) = default;
+
+  _LIBCPP_HIDE_FROM_ABI friend bool constexpr operator==(const __intrusive_shared_ptr& __ptr, std::nullptr_t) {
+    return __ptr.__raw_ptr_ == nullptr;
+  }
+
+private:
+  _Tp* __raw_ptr_ = nullptr;
+
+  // the memory order for increment/decrement the counter is the same for shared_ptr
+  // increment is relaxed and decrement is acq_rel
+  _LIBCPP_HIDE_FROM_ABI static void __increment_ref_count(_Tp& __obj) {
+    __get_atomic_ref_count(__obj).fetch_add(1, std::memory_order_relaxed);
+  }
+
+  _LIBCPP_HIDE_FROM_ABI static void __decrement_ref_count(_Tp& __obj) {
+    if (__get_atomic_ref_count(__obj).fetch_sub(1, std::memory_order_acq_rel) == 1) {
+      delete &__obj;
+    }
+  }
+
+  _LIBCPP_HIDE_FROM_ABI static decltype(auto) __get_atomic_ref_count(_Tp& __obj) {
+    using __ret_type = decltype(__intrusive_shared_ptr_traits<_Tp>::__get_atomic_ref_count(__obj));
+    static_assert(
+        std::is_reference_v<__ret_type>, "__get_atomic_ref_count should return a reference to the atomic counter");
+    return __intrusive_shared_ptr_traits<_Tp>::__get_atomic_ref_count(__obj);
+  }
+};
+
+#endif // _LIBCPP_STD_VER >= 20
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___STOP_TOKEN_INTRUSIVE_SHARED_PTR_H
index 8414773..f7decae 100644 (file)
@@ -41,6 +41,7 @@
   { include: [ "@<__pstl/.*>", "private", "<pstl>", "public" ] },
   { include: [ "@<__random/.*>", "private", "<random>", "public" ] },
   { include: [ "@<__ranges/.*>", "private", "<ranges>", "public" ] },
+  { include: [ "@<__stop_token/.*>", "private", "<stop_token>", "public" ] },
   { include: [ "@<__string/.*>", "private", "<string>", "public" ] },
   { include: [ "@<__support/.*>", "private", "<support>", "public" ] },
   { include: [ "@<__system_error/.*>", "private", "<system_error>", "public" ] },
index 7bde1be..2ea1ade 100644 (file)
@@ -1459,6 +1459,14 @@ module std [system] {
     header "stdexcept"
     export *
   }
+  module stop_token {
+    export *
+    module __stop_token {
+      module atomic_unique_lock   { private header "__stop_token/atomic_unique_lock.h" }
+      module intrusive_list_view  { private header "__stop_token/intrusive_list_view.h" }
+      module intrusive_shared_ptr { private header "__stop_token/intrusive_shared_ptr.h" }
+    }
+  }
   module streambuf {
     @requires_LIBCXX_ENABLE_LOCALIZATION@
     header "streambuf"
index 3ff91e1..9f96e9a 100644 (file)
@@ -626,6 +626,9 @@ END-SCRIPT
 #include <__ranges/zip_view.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/zip_view.h'}}
 #include <__split_buffer> // expected-error@*:* {{use of private header from outside its module: '__split_buffer'}}
 #include <__std_mbstate_t.h> // expected-error@*:* {{use of private header from outside its module: '__std_mbstate_t.h'}}
+#include <__stop_token/atomic_unique_lock.h> // expected-error@*:* {{use of private header from outside its module: '__stop_token/atomic_unique_lock.h'}}
+#include <__stop_token/intrusive_list_view.h> // expected-error@*:* {{use of private header from outside its module: '__stop_token/intrusive_list_view.h'}}
+#include <__stop_token/intrusive_shared_ptr.h> // expected-error@*:* {{use of private header from outside its module: '__stop_token/intrusive_shared_ptr.h'}}
 #include <__string/char_traits.h> // expected-error@*:* {{use of private header from outside its module: '__string/char_traits.h'}}
 #include <__string/constexpr_c_functions.h> // expected-error@*:* {{use of private header from outside its module: '__string/constexpr_c_functions.h'}}
 #include <__string/extern_template_lists.h> // expected-error@*:* {{use of private header from outside its module: '__string/extern_template_lists.h'}}
diff --git a/libcxx/test/libcxx/thread/thread.stoptoken/atomic_unique_lock.pass.cpp b/libcxx/test/libcxx/thread/thread.stoptoken/atomic_unique_lock.pass.cpp
new file mode 100644 (file)
index 0000000..b8ee91c
--- /dev/null
@@ -0,0 +1,96 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// UNSUPPORTED: no-threads
+
+// XFAIL: availability-synchronization_library-missing
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// ADDITIONAL_COMPILE_FLAGS: -Wno-private-header
+
+#include <__stop_token/atomic_unique_lock.h>
+#include <atomic>
+#include <cassert>
+#include <chrono>
+#include <thread>
+
+#include "make_test_thread.h"
+#include "test_macros.h"
+
+template <uint8_t LockBit>
+void test() {
+  using Lock = std::__atomic_unique_lock<uint8_t, LockBit>;
+
+  // lock on constructor
+  {
+    std::atomic<uint8_t> state{0};
+    Lock l(state);
+    assert(l.__owns_lock());
+  }
+
+  // always give up locking
+  {
+    std::atomic<uint8_t> state{0};
+    Lock l(state, [](auto const&) { return true; });
+    assert(!l.__owns_lock());
+  }
+
+  // test overload that has custom state after lock
+  {
+    std::atomic<uint8_t> state{0};
+    auto neverGiveUpLocking = [](auto const&) { return false; };
+    auto stateAfter         = [](auto) { return uint8_t{255}; };
+    Lock l(state, neverGiveUpLocking, stateAfter, std::memory_order_acq_rel);
+    assert(l.__owns_lock());
+    assert(state.load() == 255);
+  }
+
+  // lock and unlock
+  {
+    std::atomic<uint8_t> state{0};
+    Lock l(state);
+    assert(l.__owns_lock());
+
+    l.__unlock();
+    assert(!l.__owns_lock());
+
+    l.__lock();
+    assert(l.__owns_lock());
+  }
+
+  // lock blocking
+  {
+    std::atomic<uint8_t> state{0};
+    int i = 0;
+    Lock l1(state);
+
+    auto thread1 = support::make_test_thread([&] {
+      std::this_thread::sleep_for(std::chrono::milliseconds{10});
+      i = 5;
+      l1.__unlock();
+    });
+
+    Lock l2(state);
+    // l2's lock has to wait for l1's unlocking
+    assert(i == 5);
+
+    thread1.join();
+  }
+}
+
+int main(int, char**) {
+  test<1 << 0>();
+  test<1 << 1>();
+  test<1 << 2>();
+  test<1 << 3>();
+  test<1 << 4>();
+  test<1 << 5>();
+  test<1 << 6>();
+  test<1 << 7>();
+  return 0;
+}
diff --git a/libcxx/test/libcxx/thread/thread.stoptoken/intrusive_list_view.pass.cpp b/libcxx/test/libcxx/thread/thread.stoptoken/intrusive_list_view.pass.cpp
new file mode 100644 (file)
index 0000000..d8cd2fb
--- /dev/null
@@ -0,0 +1,108 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// ADDITIONAL_COMPILE_FLAGS: -Wno-private-header
+
+#include <__stop_token/intrusive_list_view.h>
+#include <cassert>
+
+#include "test_macros.h"
+
+struct Node : std::__intrusive_node_base<Node> {
+  int i;
+
+  Node(int ii) : i(ii) {}
+};
+
+using ListView = std::__intrusive_list_view<Node>;
+
+int main(int, char**) {
+  // empty
+  {
+    ListView list;
+    assert(list.__empty());
+  }
+
+  // push_front
+  {
+    ListView list;
+    Node n1(5);
+    list.__push_front(&n1);
+    assert(!list.__empty());
+  }
+
+  // pop_front
+  {
+    ListView list;
+    Node n1(5);
+    Node n2(6);
+    list.__push_front(&n1);
+    list.__push_front(&n2);
+
+    auto f1 = list.__pop_front();
+    assert(f1->i == 6);
+
+    auto f2 = list.__pop_front();
+    assert(f2->i == 5);
+
+    assert(list.__empty());
+  }
+
+  // remove head
+  {
+    ListView list;
+    Node n1(5);
+    Node n2(6);
+    list.__push_front(&n1);
+    list.__push_front(&n2);
+
+    list.__remove(&n2);
+
+    auto f = list.__pop_front();
+    assert(f->i == 5);
+
+    assert(list.__empty());
+  }
+
+  // remove non-head
+  {
+    ListView list;
+    Node n1(5);
+    Node n2(6);
+    Node n3(7);
+    list.__push_front(&n1);
+    list.__push_front(&n2);
+    list.__push_front(&n3);
+
+    list.__remove(&n2);
+
+    auto f1 = list.__pop_front();
+    assert(f1->i == 7);
+
+    auto f2 = list.__pop_front();
+    assert(f2->i == 5);
+
+    assert(list.__empty());
+  }
+
+  // is_head
+  {
+    ListView list;
+    Node n1(5);
+    Node n2(6);
+    list.__push_front(&n1);
+    list.__push_front(&n2);
+
+    assert(!list.__is_head(&n1));
+    assert(list.__is_head(&n2));
+  }
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/thread/thread.stoptoken/intrusive_shared_ptr.pass.cpp b/libcxx/test/libcxx/thread/thread.stoptoken/intrusive_shared_ptr.pass.cpp
new file mode 100644 (file)
index 0000000..99d4226
--- /dev/null
@@ -0,0 +1,88 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// ADDITIONAL_COMPILE_FLAGS: -Wno-private-header
+
+#include <__stop_token/intrusive_shared_ptr.h>
+#include <atomic>
+#include <cassert>
+#include <utility>
+
+#include "test_macros.h"
+
+struct Object {
+  std::atomic<int> counter = 0;
+};
+
+template <>
+struct std::__intrusive_shared_ptr_traits<Object> {
+  static std::atomic<int>& __get_atomic_ref_count(Object& obj) { return obj.counter; }
+};
+
+using Ptr = std::__intrusive_shared_ptr<Object>;
+
+int main(int, char**) {
+  // default
+  {
+    Ptr ptr;
+    assert(!ptr);
+  }
+
+  // raw ptr
+  {
+    auto object = new Object;
+    Ptr ptr(object);
+    assert(ptr->counter == 1);
+  }
+
+  // copy
+  {
+    auto object = new Object;
+    Ptr ptr(object);
+    auto ptr2 = ptr;
+    assert(ptr->counter == 2);
+    assert(ptr2->counter == 2);
+  }
+
+  // move
+  {
+    auto object = new Object;
+    Ptr ptr(object);
+    auto ptr2 = std::move(ptr);
+    assert(!ptr);
+    assert(ptr2->counter == 1);
+  }
+
+  // copy assign
+  {
+    auto object1 = new Object;
+    auto object2 = new Object;
+    Ptr ptr1(object1);
+    Ptr ptr2(object2);
+
+    ptr1 = ptr2;
+    assert(ptr1->counter == 2);
+    assert(ptr2->counter == 2);
+  }
+
+  // move assign
+  {
+    auto object1 = new Object;
+    auto object2 = new Object;
+    Ptr ptr1(object1);
+    Ptr ptr2(object2);
+
+    ptr1 = std::move(ptr2);
+    assert(ptr1->counter == 1);
+    assert(!ptr2);
+  }
+
+  return 0;
+}