[libcxx-commits] [libcxx] [libc++] P2530R3: Implementation of std::hazard_pointer (PR #218218)
Nikita Grivin via libcxx-commits
libcxx-commits at lists.llvm.org
Fri Aug 28 02:16:57 PDT 2026
https://github.com/MindSpectre updated https://github.com/llvm/llvm-project/pull/218218
>From 5b1c332b8a8f3e74f881c6b743029b60327d07c4 Mon Sep 17 00:00:00 2001
From: Nikita Grivin <neuronspectrelin at gmail.com>
Date: Mon, 24 Aug 2026 00:49:56 +0300
Subject: [PATCH] [libc++] Implement P2530R3: Hazard Pointers for C++26
Implements <hazard_pointer>: std::hazard_pointer, std::hazard_pointer_obj_base,
std::make_hazard_pointer and __cpp_lib_hazard_pointer.
The hazard pointer domain (records, per-thread record caches, sharded retired
lists and the reclamation pass) lives in the built library behind three
exported entry points (__hazard_pointer_acquire/release/retire); the class
templates in the headers are inline wrappers around them, so the facility is
availability-marked and requires the LLVM 24 dylib. The engine is a port of
Folly's hazptr (untagged path) reduced to the single default domain the
standard specifies: reclamation runs inline in retire() once
max(1000, 2 * records) objects are pending or two seconds have elapsed, and
deleters never run on a background thread.
RFC: https://discourse.llvm.org/t/rfc-implementing-hazard-pointer-p2530-in-libc/91587
Closes #105362
---
libcxx/docs/FeatureTestMacroTable.rst | 2 +-
libcxx/docs/ReleaseNotes/24.rst | 1 +
libcxx/docs/Status/Cxx26Papers.csv | 2 +-
libcxx/include/CMakeLists.txt | 4 +
libcxx/include/__configuration/availability.h | 13 +
libcxx/include/__hazard_pointer/domain.h | 93 +++
.../include/__hazard_pointer/hazard_pointer.h | 133 +++
.../hazard_pointer_obj_base.h | 96 +++
libcxx/include/hazard_pointer | 74 ++
libcxx/include/module.modulemap.in | 9 +
libcxx/include/version | 4 +-
libcxx/lib/abi/CHANGELOG.TXT | 20 +
...bcxxabi.v1.stable.exceptions.nonew.abilist | 5 +-
...bcxxabi.v1.stable.exceptions.nonew.abilist | 5 +-
...bcxxabi.v1.stable.exceptions.nonew.abilist | 7 +-
...bcxxabi.v1.stable.exceptions.nonew.abilist | 7 +-
...bcxxabi.v1.stable.exceptions.nonew.abilist | 5 +-
...bcxxabi.v1.stable.exceptions.nonew.abilist | 5 +-
...bcxxabi.v1.stable.exceptions.nonew.abilist | 7 +-
...bcxxabi.v1.stable.exceptions.nonew.abilist | 7 +-
...xxabi.v1.stable.noexceptions.nonew.abilist | 7 +-
libcxx/modules/std.compat.cppm.in | 3 -
libcxx/modules/std.cppm.in | 4 +-
libcxx/modules/std/hazard_pointer.inc | 15 +-
libcxx/src/CMakeLists.txt | 1 +
libcxx/src/hazard_pointer.cpp | 789 ++++++++++++++++++
.../test/benchmarks/hazard_pointer.bench.cpp | 140 ++++
.../saferecl/saferecl.hp/abi.compile.pass.cpp | 46 +
.../saferecl.hp/assert.protect.pass.cpp | 32 +
.../assert.reset_protection.pass.cpp | 36 +
.../saferecl.hp/assert.retire.pass.cpp | 34 +
.../saferecl.hp/assert.try_protect.pass.cpp | 34 +
.../saferecl.hp/concurrent_retire.pass.cpp | 79 ++
.../saferecl.hp/hazard_protectable.verify.cpp | 101 +++
.../saferecl.hp/no_specializations.verify.cpp | 29 +
.../saferecl/saferecl.hp/nodiscard.verify.cpp | 31 +
.../saferecl/saferecl.hp/reclamation.pass.cpp | 130 +++
.../saferecl.hp/static_destruction.pass.cpp | 43 +
.../saferecl/saferecl.hp/thread_exit.pass.cpp | 76 ++
.../thread_exit_late_release.pass.cpp | 59 ++
libcxx/test/libcxx/transitive_includes.gen.py | 1 +
.../test/libcxx/transitive_includes/cxx03.csv | 1 +
.../test/libcxx/transitive_includes/cxx11.csv | 1 +
.../test/libcxx/transitive_includes/cxx14.csv | 1 +
.../test/libcxx/transitive_includes/cxx17.csv | 1 +
.../test/libcxx/transitive_includes/cxx20.csv | 1 +
.../test/libcxx/transitive_includes/cxx23.csv | 1 +
.../test/libcxx/transitive_includes/cxx26.csv | 7 +
.../test/libcxx/transitive_includes/cxx29.csv | 7 +
.../hazard_pointer.version.compile.pass.cpp | 85 ++
.../version.version.compile.pass.cpp | 8 +-
.../saferecl/saferecl.hp/protection.pass.cpp | 103 +++
.../saferecl.hp/readers_writers.pass.cpp | 88 ++
.../incomplete_type.compile.pass.cpp | 39 +
.../nonzero_offset_base.pass.cpp | 104 +++
.../saferecl.hp.base/retire.pass.cpp | 115 +++
.../saferecl.hp.base/special_members.pass.cpp | 93 +++
.../saferecl.hp.base/types.compile.pass.cpp | 51 ++
.../moved_between_threads.pass.cpp | 82 ++
.../assign.move.pass.cpp | 79 ++
.../copy.delete.compile.pass.cpp | 24 +
.../saferecl.hp.holder.ctor/default.pass.cpp | 28 +
.../saferecl.hp.holder.ctor/dtor.pass.cpp | 51 ++
.../saferecl.hp.holder.ctor/move.pass.cpp | 52 ++
.../saferecl.hp.holder.mem/empty.pass.cpp | 32 +
.../saferecl.hp.holder.mem/protect.pass.cpp | 67 ++
.../reset_protection.pass.cpp | 42 +
.../swap.member.pass.cpp | 60 ++
.../try_protect.pass.cpp | 66 ++
.../make_hazard_pointer.pass.cpp | 44 +
.../swap.free.pass.cpp | 37 +
.../saferecl.hp.holder/types.compile.pass.cpp | 52 ++
.../generate_feature_test_macro_components.py | 8 +-
libcxx/utils/libcxx/header_information.py | 1 -
.../libcxx/test/features/availability.py | 17 +-
75 files changed, 3601 insertions(+), 36 deletions(-)
create mode 100644 libcxx/include/__hazard_pointer/domain.h
create mode 100644 libcxx/include/__hazard_pointer/hazard_pointer.h
create mode 100644 libcxx/include/__hazard_pointer/hazard_pointer_obj_base.h
create mode 100644 libcxx/include/hazard_pointer
create mode 100644 libcxx/src/hazard_pointer.cpp
create mode 100644 libcxx/test/benchmarks/hazard_pointer.bench.cpp
create mode 100644 libcxx/test/libcxx/thread/saferecl/saferecl.hp/abi.compile.pass.cpp
create mode 100644 libcxx/test/libcxx/thread/saferecl/saferecl.hp/assert.protect.pass.cpp
create mode 100644 libcxx/test/libcxx/thread/saferecl/saferecl.hp/assert.reset_protection.pass.cpp
create mode 100644 libcxx/test/libcxx/thread/saferecl/saferecl.hp/assert.retire.pass.cpp
create mode 100644 libcxx/test/libcxx/thread/saferecl/saferecl.hp/assert.try_protect.pass.cpp
create mode 100644 libcxx/test/libcxx/thread/saferecl/saferecl.hp/concurrent_retire.pass.cpp
create mode 100644 libcxx/test/libcxx/thread/saferecl/saferecl.hp/hazard_protectable.verify.cpp
create mode 100644 libcxx/test/libcxx/thread/saferecl/saferecl.hp/no_specializations.verify.cpp
create mode 100644 libcxx/test/libcxx/thread/saferecl/saferecl.hp/nodiscard.verify.cpp
create mode 100644 libcxx/test/libcxx/thread/saferecl/saferecl.hp/reclamation.pass.cpp
create mode 100644 libcxx/test/libcxx/thread/saferecl/saferecl.hp/static_destruction.pass.cpp
create mode 100644 libcxx/test/libcxx/thread/saferecl/saferecl.hp/thread_exit.pass.cpp
create mode 100644 libcxx/test/libcxx/thread/saferecl/saferecl.hp/thread_exit_late_release.pass.cpp
create mode 100644 libcxx/test/std/language.support/support.limits/support.limits.general/hazard_pointer.version.compile.pass.cpp
create mode 100644 libcxx/test/std/thread/saferecl/saferecl.hp/protection.pass.cpp
create mode 100644 libcxx/test/std/thread/saferecl/saferecl.hp/readers_writers.pass.cpp
create mode 100644 libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.base/incomplete_type.compile.pass.cpp
create mode 100644 libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.base/nonzero_offset_base.pass.cpp
create mode 100644 libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.base/retire.pass.cpp
create mode 100644 libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.base/special_members.pass.cpp
create mode 100644 libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.base/types.compile.pass.cpp
create mode 100644 libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/moved_between_threads.pass.cpp
create mode 100644 libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.ctor/assign.move.pass.cpp
create mode 100644 libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.ctor/copy.delete.compile.pass.cpp
create mode 100644 libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.ctor/default.pass.cpp
create mode 100644 libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.ctor/dtor.pass.cpp
create mode 100644 libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.ctor/move.pass.cpp
create mode 100644 libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.mem/empty.pass.cpp
create mode 100644 libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.mem/protect.pass.cpp
create mode 100644 libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.mem/reset_protection.pass.cpp
create mode 100644 libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.mem/swap.member.pass.cpp
create mode 100644 libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.mem/try_protect.pass.cpp
create mode 100644 libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.nonmem/make_hazard_pointer.pass.cpp
create mode 100644 libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.nonmem/swap.free.pass.cpp
create mode 100644 libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/types.compile.pass.cpp
diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index caf67d012db98..c15a840ee461a 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -488,7 +488,7 @@ Status
---------------------------------------------------------- -----------------
``__cpp_lib_generate_random`` *unimplemented*
---------------------------------------------------------- -----------------
- ``__cpp_lib_hazard_pointer`` *unimplemented*
+ ``__cpp_lib_hazard_pointer`` ``202306L``
---------------------------------------------------------- -----------------
``__cpp_lib_inplace_vector`` *unimplemented*
---------------------------------------------------------- -----------------
diff --git a/libcxx/docs/ReleaseNotes/24.rst b/libcxx/docs/ReleaseNotes/24.rst
index 9cf08b5732970..44839f207f304 100644
--- a/libcxx/docs/ReleaseNotes/24.rst
+++ b/libcxx/docs/ReleaseNotes/24.rst
@@ -47,6 +47,7 @@ Implemented Papers
- P3961R1: Less double indirection in ``function_ref`` (RU-220) (`Github <https://llvm.org/PR189606>`__)
- P1901R2: Enabling the Use of ``weak_ptr`` as Keys in Unordered Associative Containers (`Github <https://llvm.org/PR105372>`__)
- P0528R3: The Curious Case of Padding Bits, Featuring Atomic Compare-and-Exchange (`Github <https://llvm.org/PR76180>`__)
+- P2530R3: Hazard Pointers for C++26 (`Github <https://llvm.org/PR105362>`__)
Improvements and New Features
-----------------------------
diff --git a/libcxx/docs/Status/Cxx26Papers.csv b/libcxx/docs/Status/Cxx26Papers.csv
index bbc6310beeb63..82b08df94c67a 100644
--- a/libcxx/docs/Status/Cxx26Papers.csv
+++ b/libcxx/docs/Status/Cxx26Papers.csv
@@ -4,7 +4,7 @@
"`P2587R3 <https://wg21.link/P2587R3>`__","``to_string`` or not ``to_string``","2023-06 (Varna)","","","`#105359 <https://github.com/llvm/llvm-project/issues/105359>`__",""
"`P2562R1 <https://wg21.link/P2562R1>`__","``constexpr`` Stable Sorting","2023-06 (Varna)","|Complete|","21","`#105360 <https://github.com/llvm/llvm-project/issues/105360>`__",""
"`P2545R4 <https://wg21.link/P2545R4>`__","Read-Copy Update (RCU)","2023-06 (Varna)","","","`#105361 <https://github.com/llvm/llvm-project/issues/105361>`__",""
-"`P2530R3 <https://wg21.link/P2530R3>`__","Hazard Pointers for C++26","2023-06 (Varna)","","","`#105362 <https://github.com/llvm/llvm-project/issues/105362>`__",""
+"`P2530R3 <https://wg21.link/P2530R3>`__","Hazard Pointers for C++26","2023-06 (Varna)","|Complete|","24","`#105362 <https://github.com/llvm/llvm-project/issues/105362>`__",""
"`P2538R1 <https://wg21.link/P2538R1>`__","ADL-proof ``std::projected``","2023-06 (Varna)","|Complete|","18","`#105363 <https://github.com/llvm/llvm-project/issues/105363>`__",""
"`P2495R3 <https://wg21.link/P2495R3>`__","Interfacing ``stringstream``\s with ``string_view``","2023-06 (Varna)","|Complete|","19","`#105364 <https://github.com/llvm/llvm-project/issues/105364>`__",""
"`P2510R3 <https://wg21.link/P2510R3>`__","Formatting pointers","2023-06 (Varna)","|Complete|","17","`#105365 <https://github.com/llvm/llvm-project/issues/105365>`__","Implemented as a DR against C++20."
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index f0caccdc94194..783df7775aacd 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -490,6 +490,9 @@ set(files
__fwd/variant.h
__fwd/vector.h
__hash_table
+ __hazard_pointer/domain.h
+ __hazard_pointer/hazard_pointer.h
+ __hazard_pointer/hazard_pointer_obj_base.h
__ios/fpos.h
__iterator/access.h
__iterator/advance.h
@@ -1070,6 +1073,7 @@ set(files
fstream
functional
future
+ hazard_pointer
initializer_list
iomanip
ios
diff --git a/libcxx/include/__configuration/availability.h b/libcxx/include/__configuration/availability.h
index db6c38ddedb95..9dad855fe6e29 100644
--- a/libcxx/include/__configuration/availability.h
+++ b/libcxx/include/__configuration/availability.h
@@ -39,6 +39,9 @@
// in all versions of the library are available.
#if !_LIBCPP_HAS_VENDOR_AVAILABILITY_ANNOTATIONS
+# define _LIBCPP_INTRODUCED_IN_LLVM_24 1
+# define _LIBCPP_INTRODUCED_IN_LLVM_24_ATTRIBUTE /* nothing */
+
# define _LIBCPP_INTRODUCED_IN_LLVM_23 1
# define _LIBCPP_INTRODUCED_IN_LLVM_23_ATTRIBUTE /* nothing */
@@ -73,6 +76,11 @@
// clang-format off
+// LLVM 24
+// TODO: Fill this in
+# define _LIBCPP_INTRODUCED_IN_LLVM_24 0
+# define _LIBCPP_INTRODUCED_IN_LLVM_24_ATTRIBUTE __attribute__((unavailable))
+
// LLVM 23
// TODO: Fill this in
# define _LIBCPP_INTRODUCED_IN_LLVM_23 0
@@ -253,6 +261,11 @@
#endif
+// This controls the availability of the C++26 <hazard_pointer> facility. The hazard pointer
+// domain (records, retired lists, reclamation) lives in the built library.
+#define _LIBCPP_AVAILABILITY_HAS_HAZARD_POINTER _LIBCPP_INTRODUCED_IN_LLVM_24
+#define _LIBCPP_AVAILABILITY_HAZARD_POINTER _LIBCPP_INTRODUCED_IN_LLVM_24_ATTRIBUTE
+
// This determines whether we assume that the internal std::__bad_variant_access_with_msg class
// (which carries a message describing the cause of the failure in bad_variant_access::what())
// provides a key function in the dylib. This allows centralizing its vtable and typeinfo instead
diff --git a/libcxx/include/__hazard_pointer/domain.h b/libcxx/include/__hazard_pointer/domain.h
new file mode 100644
index 0000000000000..0341b147497f9
--- /dev/null
+++ b/libcxx/include/__hazard_pointer/domain.h
@@ -0,0 +1,93 @@
+// -*- 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___HAZARD_POINTER_DOMAIN_H
+#define _LIBCPP___HAZARD_POINTER_DOMAIN_H
+
+#include <__atomic/atomic.h>
+#include <__atomic/fence.h>
+#include <__atomic/memory_order.h>
+#include <__config>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+#endif
+
+#if _LIBCPP_STD_VER >= 26 && _LIBCPP_HAS_THREADS
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+// ABI contract between <hazard_pointer> and the built library (frozen since LLVM 24)
+//
+// - __hazard_pointer_slot is the first base of every hazard-pointer record owned by the library. It is
+// the only part of a record the headers know: try_protect() and reset_protection() store into
+// __value_ inline. sizeof(__hazard_pointer_slot) == sizeof(void*).
+// - __hazard_pointer_obj_node is the private base of hazard_pointer_obj_base<T, D>. Retired lists are
+// intrusive, so the node lives inside every hazard-protectable object and its layout is part of the
+// user's object layout: {__next_, __reclaim_}, 2 * sizeof(void*). __next_ == this means "not retired".
+// - __hazard_pointer_acquire(), __hazard_pointer_release() and __hazard_pointer_retire() are the only
+// entry points. acquire() returns an unassociated slot (__value_ == nullptr) which stays valid for the
+// whole process lifetime (records are never freed). release() ends the epoch itself (release-store of
+// nullptr) and returns the slot to the pool; the headers never touch a slot after handing it back.
+// retire() takes a node whose __reclaim_ is set, issues the retire-side seq_cst fence, publishes the
+// node and may run reclamation (and thus deleters) synchronously on the calling thread. Deleters are
+// never invoked from acquire()/release() and never on a background thread. All three entry points are
+// callable during static destruction and thread teardown.
+// - Reader protocol (v1, inline in the headers): reset_protection(p) is a release-store of the address
+// of p's node into __value_; try_protect() is: store, __hazard_pointer_reader_fence() (seq_cst),
+// acquire-reload of src, compare. The library issues a seq_cst fence before publishing retired objects
+// and before scanning slots, and loads slots with acquire ordering (or relaxed followed by an acquire
+// fence). A future asymmetric-fence scheme replaces __hazard_pointer_reader_fence() under a new
+// _LIBCPP_AVAILABILITY_HAS_HAZARD_POINTER_ASYMMETRIC_FENCE macro; the v1 protocol stays correct
+// against any library that keeps the obligations above.
+
+struct __hazard_pointer_slot {
+ atomic<const void*> __value_;
+};
+
+struct __hazard_pointer_obj_node {
+ using __reclaim_fn _LIBCPP_NODEBUG = void (*)(__hazard_pointer_obj_node*) noexcept;
+
+ __hazard_pointer_obj_node* __next_;
+ __reclaim_fn __reclaim_;
+
+ // Every construction yields a fresh, not-retired node and assignment never touches the node: a copy
+ // of a retired-but-still-protected object must itself be a fresh object.
+ _LIBCPP_HIDE_FROM_ABI constexpr __hazard_pointer_obj_node() noexcept : __next_(this), __reclaim_(nullptr) {}
+ _LIBCPP_HIDE_FROM_ABI constexpr __hazard_pointer_obj_node(const __hazard_pointer_obj_node&) noexcept
+ : __next_(this), __reclaim_(nullptr) {}
+ _LIBCPP_HIDE_FROM_ABI constexpr __hazard_pointer_obj_node& operator=(const __hazard_pointer_obj_node&) noexcept {
+ return *this;
+ }
+ _LIBCPP_HIDE_FROM_ABI ~__hazard_pointer_obj_node() = default;
+};
+
+// The reader-side fence of try_protect(): the one place to change when asymmetric fences arrive.
+_LIBCPP_HIDE_FROM_ABI inline void __hazard_pointer_reader_fence() noexcept {
+ std::atomic_thread_fence(memory_order_seq_cst);
+}
+
+_LIBCPP_BEGIN_EXPLICIT_ABI_ANNOTATIONS
+
+[[__gnu__::__returns_nonnull__]] _LIBCPP_AVAILABILITY_HAZARD_POINTER _LIBCPP_EXPORTED_FROM_ABI __hazard_pointer_slot*
+__hazard_pointer_acquire();
+
+_LIBCPP_AVAILABILITY_HAZARD_POINTER _LIBCPP_EXPORTED_FROM_ABI void
+__hazard_pointer_release([[__gnu__::__nonnull__]] __hazard_pointer_slot* __slot) noexcept;
+
+_LIBCPP_AVAILABILITY_HAZARD_POINTER _LIBCPP_EXPORTED_FROM_ABI void
+__hazard_pointer_retire([[__gnu__::__nonnull__]] __hazard_pointer_obj_node* __node) noexcept;
+
+_LIBCPP_END_EXPLICIT_ABI_ANNOTATIONS
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER >= 26 && _LIBCPP_HAS_THREADS
+
+#endif // _LIBCPP___HAZARD_POINTER_DOMAIN_H
diff --git a/libcxx/include/__hazard_pointer/hazard_pointer.h b/libcxx/include/__hazard_pointer/hazard_pointer.h
new file mode 100644
index 0000000000000..43f5d48c26f8e
--- /dev/null
+++ b/libcxx/include/__hazard_pointer/hazard_pointer.h
@@ -0,0 +1,133 @@
+// -*- 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___HAZARD_POINTER_HAZARD_POINTER_H
+#define _LIBCPP___HAZARD_POINTER_HAZARD_POINTER_H
+
+#include <__assert>
+#include <__atomic/atomic.h>
+#include <__atomic/memory_order.h>
+#include <__config>
+#include <__cstddef/nullptr_t.h>
+#include <__hazard_pointer/domain.h>
+#include <__hazard_pointer/hazard_pointer_obj_base.h>
+#include <__memory/addressof.h>
+#include <__utility/exchange.h>
+#include <__utility/swap.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+#if _LIBCPP_STD_VER >= 26 && _LIBCPP_HAS_THREADS
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+class _LIBCPP_AVAILABILITY_HAZARD_POINTER hazard_pointer {
+ __hazard_pointer_slot* __slot_ = nullptr; // empty <=> nullptr
+
+ friend _LIBCPP_HIDE_FROM_ABI hazard_pointer make_hazard_pointer();
+
+ _LIBCPP_HIDE_FROM_ABI explicit hazard_pointer(__hazard_pointer_slot* __slot) noexcept : __slot_(__slot) {}
+
+ // The address a hazard pointer holds for *__ptr: that of its (private) node base. Deduces D through the
+ // derived-to-base conversion; hazard_pointer is a friend of hazard_pointer_obj_base, so the conversion to
+ // the private base is accessible here. A null T* yields a null node.
+ template <class _Tp, class _Dp>
+ _LIBCPP_HIDE_FROM_ABI static const __hazard_pointer_obj_node*
+ __node_of(const hazard_pointer_obj_base<_Tp, _Dp>* __ptr) noexcept {
+ return __ptr;
+ }
+
+public:
+ _LIBCPP_HIDE_FROM_ABI hazard_pointer() noexcept = default;
+
+ _LIBCPP_HIDE_FROM_ABI hazard_pointer(hazard_pointer&& __other) noexcept
+ : __slot_(std::exchange(__other.__slot_, nullptr)) {}
+
+ _LIBCPP_HIDE_FROM_ABI hazard_pointer& operator=(hazard_pointer&& __other) noexcept {
+ if (this != std::addressof(__other)) {
+ if (__slot_ != nullptr)
+ std::__hazard_pointer_release(__slot_);
+ __slot_ = std::exchange(__other.__slot_, nullptr);
+ }
+ return *this;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI ~hazard_pointer() {
+ if (__slot_ != nullptr)
+ std::__hazard_pointer_release(__slot_);
+ }
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI bool empty() const noexcept { return __slot_ == nullptr; }
+
+ template <class _Tp>
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI _Tp* protect(const atomic<_Tp*>& __src) noexcept {
+ _Tp* __ptr = __src.load(memory_order_relaxed);
+ while (!try_protect(__ptr, __src)) {
+ }
+ return __ptr;
+ }
+
+ template <class _Tp>
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI bool try_protect(_Tp*& __ptr, const atomic<_Tp*>& __src) noexcept {
+ static_assert(
+ __hazard_protectable<_Tp>,
+ "hazard_pointer::try_protect(): T must be a hazard-protectable type (a class with exactly one public, "
+ "non-virtual hazard_pointer_obj_base<T, D> base)");
+ _Tp* __old = __ptr;
+ reset_protection(__old);
+ std::__hazard_pointer_reader_fence();
+ __ptr = __src.load(memory_order_acquire);
+ if (__old != __ptr) {
+ reset_protection();
+ return false;
+ }
+ return true;
+ }
+
+ template <class _Tp>
+ _LIBCPP_HIDE_FROM_ABI void reset_protection(const _Tp* __ptr) noexcept {
+ static_assert(__hazard_protectable<_Tp>,
+ "hazard_pointer::reset_protection(): T must be a hazard-protectable type (a class with exactly one "
+ "public, non-virtual hazard_pointer_obj_base<T, D> base)");
+ _LIBCPP_ASSERT_NON_NULL(__slot_ != nullptr, "hazard_pointer::reset_protection(): hazard_pointer is empty");
+ __slot_->__value_.store(__node_of(__ptr), memory_order_release);
+ }
+
+ _LIBCPP_HIDE_FROM_ABI void reset_protection(nullptr_t = nullptr) noexcept {
+ _LIBCPP_ASSERT_NON_NULL(__slot_ != nullptr, "hazard_pointer::reset_protection(): hazard_pointer is empty");
+ __slot_->__value_.store(nullptr, memory_order_release);
+ }
+
+ _LIBCPP_HIDE_FROM_ABI void swap(hazard_pointer& __other) noexcept { std::swap(__slot_, __other.__slot_); }
+};
+
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_HAZARD_POINTER inline hazard_pointer make_hazard_pointer() {
+ return hazard_pointer(std::__hazard_pointer_acquire());
+}
+
+// The availability attribute is required, not just informative: where it expands to
+// __attribute__((unavailable)), naming hazard_pointer in a declaration that is not itself unavailable is an
+// error even inside a system header.
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_HAZARD_POINTER inline void
+swap(hazard_pointer& __a, hazard_pointer& __b) noexcept {
+ __a.swap(__b);
+}
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER >= 26 && _LIBCPP_HAS_THREADS
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___HAZARD_POINTER_HAZARD_POINTER_H
diff --git a/libcxx/include/__hazard_pointer/hazard_pointer_obj_base.h b/libcxx/include/__hazard_pointer/hazard_pointer_obj_base.h
new file mode 100644
index 0000000000000..e6a1bb0658b9b
--- /dev/null
+++ b/libcxx/include/__hazard_pointer/hazard_pointer_obj_base.h
@@ -0,0 +1,96 @@
+// -*- 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___HAZARD_POINTER_HAZARD_POINTER_OBJ_BASE_H
+#define _LIBCPP___HAZARD_POINTER_HAZARD_POINTER_OBJ_BASE_H
+
+#include <__assert>
+#include <__concepts/same_as.h>
+#include <__config>
+#include <__hazard_pointer/domain.h>
+#include <__memory/unique_ptr.h>
+#include <__type_traits/is_assignable.h>
+#include <__type_traits/is_class.h>
+#include <__type_traits/is_constructible.h>
+#include <__utility/move.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+#if _LIBCPP_STD_VER >= 26 && _LIBCPP_HAS_THREADS
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+class hazard_pointer;
+
+template <class _Tp, class _Dp = default_delete<_Tp>>
+class _LIBCPP_NO_SPECIALIZATIONS hazard_pointer_obj_base;
+
+// Deduction-only helper (declared, never defined): given a T*, find *the* hazard_pointer_obj_base base
+// through a derived-to-base conversion. Deduction fails when there is no such base or more than one
+// (ambiguous), the return type is ill-formed when the base is virtual, and the conversion is inaccessible
+// when the base is not public.
+template <class _Tp, class _Dp>
+auto __hazard_pointer_protectable_check(hazard_pointer_obj_base<_Tp, _Dp>* __base)
+ -> decltype(static_cast<_Tp*>(__base));
+
+// [saferecl.hp.general]/2: a class type T is hazard-protectable if it has exactly one base class of type
+// hazard_pointer_obj_base<T, D> for some D, that base is public and non-virtual, and it has no base classes
+// of type hazard_pointer_obj_base<T2, D2> for any other combination T2, D2.
+template <class _Tp>
+concept __hazard_protectable = is_class_v<_Tp> && requires(_Tp* __p) {
+ { std::__hazard_pointer_protectable_check(__p) } -> same_as<_Tp*>;
+};
+
+template <class _Tp, class _Dp>
+class _LIBCPP_AVAILABILITY_HAZARD_POINTER _LIBCPP_NO_SPECIALIZATIONS hazard_pointer_obj_base
+ : private __hazard_pointer_obj_node {
+ static_assert(is_default_constructible_v<_Dp> && is_move_assignable_v<_Dp>,
+ "hazard_pointer_obj_base<T, D>: D must be Cpp17DefaultConstructible and Cpp17MoveAssignable");
+
+ // hazard_pointer::__node_of performs the derived-to-private-base conversion.
+ friend class hazard_pointer;
+
+ _LIBCPP_NO_UNIQUE_ADDRESS _Dp __deleter_ = _Dp();
+
+public:
+ _LIBCPP_HIDE_FROM_ABI void retire(_Dp __d = _Dp()) noexcept {
+ static_assert(__hazard_protectable<_Tp>,
+ "hazard_pointer_obj_base<T, D>::retire(): T must be a hazard-protectable type (a class with exactly "
+ "one public, non-virtual hazard_pointer_obj_base<T, D> base)");
+ _LIBCPP_ASSERT_VALID_DEALLOCATION(
+ this->__next_ == this, "hazard_pointer_obj_base::retire(): object has already been retired");
+ __deleter_ = std::move(__d);
+ this->__reclaim_ = [](__hazard_pointer_obj_node* __node) noexcept {
+ hazard_pointer_obj_base* __base = static_cast<hazard_pointer_obj_base*>(__node);
+ __base->__deleter_(static_cast<_Tp*>(__base));
+ };
+ std::__hazard_pointer_retire(this);
+ }
+
+protected:
+ _LIBCPP_HIDE_FROM_ABI hazard_pointer_obj_base() = default;
+ _LIBCPP_HIDE_FROM_ABI hazard_pointer_obj_base(const hazard_pointer_obj_base&) = default;
+ _LIBCPP_HIDE_FROM_ABI hazard_pointer_obj_base(hazard_pointer_obj_base&&) = default;
+ _LIBCPP_HIDE_FROM_ABI hazard_pointer_obj_base& operator=(const hazard_pointer_obj_base&) = default;
+ _LIBCPP_HIDE_FROM_ABI hazard_pointer_obj_base& operator=(hazard_pointer_obj_base&&) = default;
+ _LIBCPP_HIDE_FROM_ABI ~hazard_pointer_obj_base() = default;
+};
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER >= 26 && _LIBCPP_HAS_THREADS
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___HAZARD_POINTER_HAZARD_POINTER_OBJ_BASE_H
diff --git a/libcxx/include/hazard_pointer b/libcxx/include/hazard_pointer
new file mode 100644
index 0000000000000..46788e8f9ca6e
--- /dev/null
+++ b/libcxx/include/hazard_pointer
@@ -0,0 +1,74 @@
+// -*- 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_HAZARD_POINTER
+#define _LIBCPP_HAZARD_POINTER
+
+/*
+ hazard_pointer synopsis
+
+namespace std {
+ // [saferecl.hp.base], class template hazard_pointer_obj_base
+ template<class T, class D = default_delete<T>>
+ class hazard_pointer_obj_base { // since C++26
+ public:
+ void retire(D d = D()) noexcept;
+ protected:
+ hazard_pointer_obj_base() = default;
+ hazard_pointer_obj_base(const hazard_pointer_obj_base&) = default;
+ hazard_pointer_obj_base(hazard_pointer_obj_base&&) = default;
+ hazard_pointer_obj_base& operator=(const hazard_pointer_obj_base&) = default;
+ hazard_pointer_obj_base& operator=(hazard_pointer_obj_base&&) = default;
+ ~hazard_pointer_obj_base() = default;
+ private:
+ D deleter; // exposition only
+ };
+
+ // [saferecl.hp.holder], class hazard_pointer
+ class hazard_pointer { // since C++26
+ public:
+ hazard_pointer() noexcept;
+ hazard_pointer(hazard_pointer&&) noexcept;
+ hazard_pointer& operator=(hazard_pointer&&) noexcept;
+ ~hazard_pointer();
+
+ bool empty() const noexcept;
+ template<class T> T* protect(const atomic<T*>& src) noexcept;
+ template<class T> bool try_protect(T*& ptr, const atomic<T*>& src) noexcept;
+ template<class T> void reset_protection(const T* ptr) noexcept;
+ void reset_protection(nullptr_t = nullptr) noexcept;
+ void swap(hazard_pointer&) noexcept;
+ };
+
+ // [saferecl.hp.holder.nonmem], non-member functions
+ hazard_pointer make_hazard_pointer(); // since C++26
+ void swap(hazard_pointer&, hazard_pointer&) noexcept; // since C++26
+}
+
+*/
+
+#if __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS)
+# include <__cxx03/__config>
+#else
+# include <__config>
+
+# if _LIBCPP_STD_VER >= 26 && _LIBCPP_HAS_THREADS
+# include <__hazard_pointer/domain.h>
+# include <__hazard_pointer/hazard_pointer.h>
+# include <__hazard_pointer/hazard_pointer_obj_base.h>
+# endif
+
+# include <version>
+
+# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+# endif
+#endif // __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS)
+
+#endif // _LIBCPP_HAZARD_POINTER
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 9ce0c49b8b2b3..fcc21a9453956 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -1489,6 +1489,15 @@ module std {
export *
}
+ module hazard_pointer {
+ module domain { header "__hazard_pointer/domain.h" }
+ module hazard_pointer { header "__hazard_pointer/hazard_pointer.h" }
+ module hazard_pointer_obj_base { header "__hazard_pointer/hazard_pointer_obj_base.h" }
+
+ header "hazard_pointer"
+ export *
+ }
+
module initializer_list {
header "initializer_list"
export *
diff --git a/libcxx/include/version b/libcxx/include/version
index a477e9c2802f6..65b1412e0d8e2 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -600,7 +600,9 @@ __cpp_lib_void_t 201411L <type_traits>
# endif
# define __cpp_lib_function_ref 202603L
// # define __cpp_lib_generate_random 202403L
-// # define __cpp_lib_hazard_pointer 202306L
+# if _LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_HAZARD_POINTER
+# define __cpp_lib_hazard_pointer 202306L
+# endif
// # define __cpp_lib_inplace_vector 202406L
# undef __cpp_lib_integer_sequence
# define __cpp_lib_integer_sequence 202511L
diff --git a/libcxx/lib/abi/CHANGELOG.TXT b/libcxx/lib/abi/CHANGELOG.TXT
index 30bb3d6254965..6eba5af1876b8 100644
--- a/libcxx/lib/abi/CHANGELOG.TXT
+++ b/libcxx/lib/abi/CHANGELOG.TXT
@@ -16,6 +16,26 @@ New entries should be added directly below the "Version" header.
Version 24.0
------------
+* [libc++] Implement P2530R3: Hazard Pointers for C++26
+
+ <hazard_pointer> keeps its domain (hazard pointer records, retired lists, reclamation) in the built
+ library, behind three exported functions used by the inline hazard_pointer/hazard_pointer_obj_base.
+
+ All platforms
+ -------------
+ Symbol added: _ZNSt3__124__hazard_pointer_acquireEv
+ Symbol added: _ZNSt3__124__hazard_pointer_releaseEPNS_21__hazard_pointer_slotE
+ Symbol added: _ZNSt3__123__hazard_pointer_retireEPNS_25__hazard_pointer_obj_nodeE
+
+ x86_64-unknown-linux-gnu, x86_64-unknown-freebsd, powerpc-ibm-aix, powerpc64-ibm-aix
+ -----------------------------------------------------------------------------------
+ The hazard pointer domain allocates with nothrow new, which the built library did not use before.
+ Where libc++abi is a separate library this adds two undefined symbols resolved from it at load time.
+ (The Apple lists already carried these imports; on Android they are defined in libc++_shared.so.)
+
+ Symbol added: _ZnwmRKSt9nothrow_t
+ Symbol added: _ZnamRKSt9nothrow_t
+
* [libc++] Remove locale::id::__next_id from the ABI
`__next_id` is only ever used inside the dylib, so we can avoid making it public.
diff --git a/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist
index fad3441d59ab7..da9dde7d7c2cd 100644
--- a/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist
+++ b/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist
@@ -1577,8 +1577,11 @@
{'is_defined': True, 'name': '__ZNSt3__123__cxx_atomic_notify_allEPVKv', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__123__cxx_atomic_notify_oneEPVKNS_17__cxx_atomic_implIxNS_22__cxx_atomic_base_implIxEEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__123__cxx_atomic_notify_oneEPVKv', 'type': 'FUNC'}
+{'is_defined': True, 'name': '__ZNSt3__123__hazard_pointer_retireEPNS_25__hazard_pointer_obj_nodeE', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__123__libcpp_atomic_monitorEPVKNS_17__cxx_atomic_implIxNS_22__cxx_atomic_base_implIxEEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__123__libcpp_atomic_monitorEPVKv', 'type': 'FUNC'}
+{'is_defined': True, 'name': '__ZNSt3__124__hazard_pointer_acquireEv', 'type': 'FUNC'}
+{'is_defined': True, 'name': '__ZNSt3__124__hazard_pointer_releaseEPNS_21__hazard_pointer_slotE', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__126__atomic_notify_all_nativeILm8EEEvPKv', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__126__atomic_notify_one_nativeILm8EEEvPKv', 'type': 'FUNC'}
@@ -2655,4 +2658,4 @@
{'is_defined': True, 'name': '___cxa_vec_new2', 'type': 'I'}
{'is_defined': True, 'name': '___cxa_vec_new3', 'type': 'I'}
{'is_defined': True, 'name': '___dynamic_cast', 'type': 'I'}
-{'is_defined': True, 'name': '___gxx_personality_v0', 'type': 'I'}
\ No newline at end of file
+{'is_defined': True, 'name': '___gxx_personality_v0', 'type': 'I'}
diff --git a/libcxx/lib/abi/i686-linux-android23.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/i686-linux-android23.libcxxabi.v1.stable.exceptions.nonew.abilist
index 4a16243a17501..b78d7342bb567 100644
--- a/libcxx/lib/abi/i686-linux-android23.libcxxabi.v1.stable.exceptions.nonew.abilist
+++ b/libcxx/lib/abi/i686-linux-android23.libcxxabi.v1.stable.exceptions.nonew.abilist
@@ -1214,8 +1214,11 @@
{'is_defined': True, 'name': '_ZNSt6__ndk123__cxx_atomic_notify_allEPVKv', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk123__cxx_atomic_notify_oneEPVKNS_17__cxx_atomic_implIiNS_22__cxx_atomic_base_implIiEEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk123__cxx_atomic_notify_oneEPVKv', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt6__ndk123__hazard_pointer_retireEPNS_25__hazard_pointer_obj_nodeE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk123__libcpp_atomic_monitorEPVKNS_17__cxx_atomic_implIiNS_22__cxx_atomic_base_implIiEEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk123__libcpp_atomic_monitorEPVKv', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt6__ndk124__hazard_pointer_acquireEv', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt6__ndk124__hazard_pointer_releaseEPNS_21__hazard_pointer_slotE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk126__atomic_notify_all_nativeILj4EEEvPKv', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk126__atomic_notify_one_nativeILj4EEEvPKv', 'type': 'FUNC'}
@@ -2341,4 +2344,4 @@
{'is_defined': True, 'name': '__cxa_vec_dtor', 'type': 'FUNC'}
{'is_defined': True, 'name': '__cxa_vec_new', 'type': 'FUNC'}
{'is_defined': True, 'name': '__cxa_vec_new2', 'type': 'FUNC'}
-{'is_defined': True, 'name': '__cxa_vec_new3', 'type': 'FUNC'}
\ No newline at end of file
+{'is_defined': True, 'name': '__cxa_vec_new3', 'type': 'FUNC'}
diff --git a/libcxx/lib/abi/powerpc-ibm-aix.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/powerpc-ibm-aix.libcxxabi.v1.stable.exceptions.nonew.abilist
index 65984090a5246..be1108da395f8 100644
--- a/libcxx/lib/abi/powerpc-ibm-aix.libcxxabi.v1.stable.exceptions.nonew.abilist
+++ b/libcxx/lib/abi/powerpc-ibm-aix.libcxxabi.v1.stable.exceptions.nonew.abilist
@@ -569,8 +569,11 @@
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__123__cxx_atomic_notify_allEPVKv', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__123__cxx_atomic_notify_oneEPVKNS_17__cxx_atomic_implIiNS_22__cxx_atomic_base_implIiEEEE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__123__cxx_atomic_notify_oneEPVKv', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
+{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__123__hazard_pointer_retireEPNS_25__hazard_pointer_obj_nodeE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKNS_17__cxx_atomic_implIiNS_22__cxx_atomic_base_implIiEEEE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKv', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
+{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__124__hazard_pointer_acquireEv', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
+{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__124__hazard_pointer_releaseEPNS_21__hazard_pointer_slotE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__126__atomic_wait_global_tableEPKvi', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__131__arrive_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseEh', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
@@ -2030,6 +2033,8 @@
{'import_export': 'wIMP', 'is_defined': False, 'name': '_ZdlPvm', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'wIMP', 'is_defined': False, 'name': '_ZdlPvmSt11align_val_t', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'wIMP', 'is_defined': False, 'name': '_Znam', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
+{'import_export': 'wIMP', 'is_defined': False, 'name': '_ZnamRKSt9nothrow_t', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'wIMP', 'is_defined': False, 'name': '_ZnamSt11align_val_t', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'wIMP', 'is_defined': False, 'name': '_Znwm', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
-{'import_export': 'wIMP', 'is_defined': False, 'name': '_ZnwmSt11align_val_t', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
\ No newline at end of file
+{'import_export': 'wIMP', 'is_defined': False, 'name': '_ZnwmRKSt9nothrow_t', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
+{'import_export': 'wIMP', 'is_defined': False, 'name': '_ZnwmSt11align_val_t', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
diff --git a/libcxx/lib/abi/powerpc64-ibm-aix.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/powerpc64-ibm-aix.libcxxabi.v1.stable.exceptions.nonew.abilist
index 96396605a8412..eb542c2a6abfd 100644
--- a/libcxx/lib/abi/powerpc64-ibm-aix.libcxxabi.v1.stable.exceptions.nonew.abilist
+++ b/libcxx/lib/abi/powerpc64-ibm-aix.libcxxabi.v1.stable.exceptions.nonew.abilist
@@ -569,8 +569,11 @@
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__123__cxx_atomic_notify_allEPVKv', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__123__cxx_atomic_notify_oneEPVKNS_17__cxx_atomic_implIlNS_22__cxx_atomic_base_implIlEEEE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__123__cxx_atomic_notify_oneEPVKv', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
+{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__123__hazard_pointer_retireEPNS_25__hazard_pointer_obj_nodeE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKNS_17__cxx_atomic_implIlNS_22__cxx_atomic_base_implIlEEEE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKv', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
+{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__124__hazard_pointer_acquireEv', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
+{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__124__hazard_pointer_releaseEPNS_21__hazard_pointer_slotE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__126__atomic_wait_global_tableEPKvl', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__131__arrive_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseEh', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
@@ -2030,6 +2033,8 @@
{'import_export': 'wIMP', 'is_defined': False, 'name': '_ZdlPvm', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'wIMP', 'is_defined': False, 'name': '_ZdlPvmSt11align_val_t', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'wIMP', 'is_defined': False, 'name': '_Znam', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
+{'import_export': 'wIMP', 'is_defined': False, 'name': '_ZnamRKSt9nothrow_t', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'wIMP', 'is_defined': False, 'name': '_ZnamSt11align_val_t', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
{'import_export': 'wIMP', 'is_defined': False, 'name': '_Znwm', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
-{'import_export': 'wIMP', 'is_defined': False, 'name': '_ZnwmSt11align_val_t', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
\ No newline at end of file
+{'import_export': 'wIMP', 'is_defined': False, 'name': '_ZnwmRKSt9nothrow_t', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
+{'import_export': 'wIMP', 'is_defined': False, 'name': '_ZnwmSt11align_val_t', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
diff --git a/libcxx/lib/abi/x86_64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/x86_64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist
index be1d6382a7983..70e8725ea357c 100644
--- a/libcxx/lib/abi/x86_64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist
+++ b/libcxx/lib/abi/x86_64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist
@@ -1573,8 +1573,11 @@
{'is_defined': True, 'name': '__ZNSt3__123__cxx_atomic_notify_allEPVKv', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__123__cxx_atomic_notify_oneEPVKNS_17__cxx_atomic_implIxNS_22__cxx_atomic_base_implIxEEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__123__cxx_atomic_notify_oneEPVKv', 'type': 'FUNC'}
+{'is_defined': True, 'name': '__ZNSt3__123__hazard_pointer_retireEPNS_25__hazard_pointer_obj_nodeE', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__123__libcpp_atomic_monitorEPVKNS_17__cxx_atomic_implIxNS_22__cxx_atomic_base_implIxEEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__123__libcpp_atomic_monitorEPVKv', 'type': 'FUNC'}
+{'is_defined': True, 'name': '__ZNSt3__124__hazard_pointer_acquireEv', 'type': 'FUNC'}
+{'is_defined': True, 'name': '__ZNSt3__124__hazard_pointer_releaseEPNS_21__hazard_pointer_slotE', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__127__from_chars_floating_pointEPKcS1_RdNS_12chars_formatE', 'type': 'FUNC'}
{'is_defined': True, 'name': '__ZNSt3__127__from_chars_floating_pointEPKcS1_RfNS_12chars_formatE', 'type': 'FUNC'}
@@ -2674,4 +2677,4 @@
{'is_defined': True, 'name': '___cxa_vec_new2', 'type': 'I'}
{'is_defined': True, 'name': '___cxa_vec_new3', 'type': 'I'}
{'is_defined': True, 'name': '___dynamic_cast', 'type': 'I'}
-{'is_defined': True, 'name': '___gxx_personality_v0', 'type': 'I'}
\ No newline at end of file
+{'is_defined': True, 'name': '___gxx_personality_v0', 'type': 'I'}
diff --git a/libcxx/lib/abi/x86_64-linux-android23.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/x86_64-linux-android23.libcxxabi.v1.stable.exceptions.nonew.abilist
index 979c5b9d98509..26c670f55b9f9 100644
--- a/libcxx/lib/abi/x86_64-linux-android23.libcxxabi.v1.stable.exceptions.nonew.abilist
+++ b/libcxx/lib/abi/x86_64-linux-android23.libcxxabi.v1.stable.exceptions.nonew.abilist
@@ -1214,8 +1214,11 @@
{'is_defined': True, 'name': '_ZNSt6__ndk123__cxx_atomic_notify_allEPVKv', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk123__cxx_atomic_notify_oneEPVKNS_17__cxx_atomic_implIiNS_22__cxx_atomic_base_implIiEEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk123__cxx_atomic_notify_oneEPVKv', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt6__ndk123__hazard_pointer_retireEPNS_25__hazard_pointer_obj_nodeE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk123__libcpp_atomic_monitorEPVKNS_17__cxx_atomic_implIiNS_22__cxx_atomic_base_implIiEEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk123__libcpp_atomic_monitorEPVKv', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt6__ndk124__hazard_pointer_acquireEv', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt6__ndk124__hazard_pointer_releaseEPNS_21__hazard_pointer_slotE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk126__atomic_notify_all_nativeILm4EEEvPKv', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt6__ndk126__atomic_notify_one_nativeILm4EEEvPKv', 'type': 'FUNC'}
@@ -2335,4 +2338,4 @@
{'is_defined': True, 'name': '__cxa_vec_dtor', 'type': 'FUNC'}
{'is_defined': True, 'name': '__cxa_vec_new', 'type': 'FUNC'}
{'is_defined': True, 'name': '__cxa_vec_new2', 'type': 'FUNC'}
-{'is_defined': True, 'name': '__cxa_vec_new3', 'type': 'FUNC'}
\ No newline at end of file
+{'is_defined': True, 'name': '__cxa_vec_new3', 'type': 'FUNC'}
diff --git a/libcxx/lib/abi/x86_64-unknown-freebsd.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/x86_64-unknown-freebsd.libcxxabi.v1.stable.exceptions.nonew.abilist
index cb77431bc4f50..6a06b331e1422 100644
--- a/libcxx/lib/abi/x86_64-unknown-freebsd.libcxxabi.v1.stable.exceptions.nonew.abilist
+++ b/libcxx/lib/abi/x86_64-unknown-freebsd.libcxxabi.v1.stable.exceptions.nonew.abilist
@@ -41,8 +41,10 @@
{'is_defined': False, 'name': '_ZdlPvm', 'type': 'FUNC'}
{'is_defined': False, 'name': '_ZdlPvmSt11align_val_t', 'type': 'FUNC'}
{'is_defined': False, 'name': '_Znam', 'type': 'FUNC'}
+{'is_defined': False, 'name': '_ZnamRKSt9nothrow_t', 'type': 'FUNC'}
{'is_defined': False, 'name': '_ZnamSt11align_val_t', 'type': 'FUNC'}
{'is_defined': False, 'name': '_Znwm', 'type': 'FUNC'}
+{'is_defined': False, 'name': '_ZnwmRKSt9nothrow_t', 'type': 'FUNC'}
{'is_defined': False, 'name': '_ZnwmSt11align_val_t', 'type': 'FUNC'}
{'is_defined': False, 'name': '__cxa_allocate_exception', 'type': 'FUNC'}
{'is_defined': False, 'name': '__cxa_atexit at FBSD_1.0', 'type': 'FUNC'}
@@ -1227,8 +1229,11 @@
{'is_defined': True, 'name': '_ZNSt3__123__cxx_atomic_notify_allEPVKv', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__123__cxx_atomic_notify_oneEPVKNS_17__cxx_atomic_implIlNS_22__cxx_atomic_base_implIlEEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__123__cxx_atomic_notify_oneEPVKv', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt3__123__hazard_pointer_retireEPNS_25__hazard_pointer_obj_nodeE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKNS_17__cxx_atomic_implIlNS_22__cxx_atomic_base_implIlEEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKv', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt3__124__hazard_pointer_acquireEv', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt3__124__hazard_pointer_releaseEPNS_21__hazard_pointer_slotE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__126__atomic_notify_all_nativeILm8EEEvPKv', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__126__atomic_notify_one_nativeILm8EEEvPKv', 'type': 'FUNC'}
@@ -2034,4 +2039,4 @@
{'is_defined': True, 'name': '_ZTv0_n24_NSt3__114basic_iostreamIcNS_11char_traitsIcEEED0Ev', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZTv0_n24_NSt3__114basic_iostreamIcNS_11char_traitsIcEEED1Ev', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZTv0_n24_NSt3__19strstreamD0Ev', 'type': 'FUNC'}
-{'is_defined': True, 'name': '_ZTv0_n24_NSt3__19strstreamD1Ev', 'type': 'FUNC'}
\ No newline at end of file
+{'is_defined': True, 'name': '_ZTv0_n24_NSt3__19strstreamD1Ev', 'type': 'FUNC'}
diff --git a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist
index c727c028d21e6..77b44b3664c54 100644
--- a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist
+++ b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist
@@ -41,8 +41,10 @@
{'is_defined': False, 'name': '_ZdlPvm', 'type': 'FUNC'}
{'is_defined': False, 'name': '_ZdlPvmSt11align_val_t', 'type': 'FUNC'}
{'is_defined': False, 'name': '_Znam', 'type': 'FUNC'}
+{'is_defined': False, 'name': '_ZnamRKSt9nothrow_t', 'type': 'FUNC'}
{'is_defined': False, 'name': '_ZnamSt11align_val_t', 'type': 'FUNC'}
{'is_defined': False, 'name': '_Znwm', 'type': 'FUNC'}
+{'is_defined': False, 'name': '_ZnwmRKSt9nothrow_t', 'type': 'FUNC'}
{'is_defined': False, 'name': '_ZnwmSt11align_val_t', 'type': 'FUNC'}
{'is_defined': False, 'name': '__cxa_allocate_exception', 'type': 'FUNC'}
{'is_defined': False, 'name': '__cxa_begin_catch', 'type': 'FUNC'}
@@ -1225,8 +1227,11 @@
{'is_defined': True, 'name': '_ZNSt3__123__cxx_atomic_notify_allEPVKv', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__123__cxx_atomic_notify_oneEPVKNS_17__cxx_atomic_implIiNS_22__cxx_atomic_base_implIiEEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__123__cxx_atomic_notify_oneEPVKv', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt3__123__hazard_pointer_retireEPNS_25__hazard_pointer_obj_nodeE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKNS_17__cxx_atomic_implIiNS_22__cxx_atomic_base_implIiEEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKv', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt3__124__hazard_pointer_acquireEv', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt3__124__hazard_pointer_releaseEPNS_21__hazard_pointer_slotE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__126__atomic_notify_all_nativeILm4EEEvPKv', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__126__atomic_notify_one_nativeILm4EEEvPKv', 'type': 'FUNC'}
@@ -2033,4 +2038,4 @@
{'is_defined': True, 'name': '_ZTv0_n24_NSt3__114basic_iostreamIcNS_11char_traitsIcEEED0Ev', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZTv0_n24_NSt3__114basic_iostreamIcNS_11char_traitsIcEEED1Ev', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZTv0_n24_NSt3__19strstreamD0Ev', 'type': 'FUNC'}
-{'is_defined': True, 'name': '_ZTv0_n24_NSt3__19strstreamD1Ev', 'type': 'FUNC'}
\ No newline at end of file
+{'is_defined': True, 'name': '_ZTv0_n24_NSt3__19strstreamD1Ev', 'type': 'FUNC'}
diff --git a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.noexceptions.nonew.abilist b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.noexceptions.nonew.abilist
index 86c548d5aa627..165279e97d7a1 100644
--- a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.noexceptions.nonew.abilist
+++ b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.noexceptions.nonew.abilist
@@ -20,8 +20,10 @@
{'is_defined': False, 'name': '_ZdlPvm', 'type': 'FUNC'}
{'is_defined': False, 'name': '_ZdlPvmSt11align_val_t', 'type': 'FUNC'}
{'is_defined': False, 'name': '_Znam', 'type': 'FUNC'}
+{'is_defined': False, 'name': '_ZnamRKSt9nothrow_t', 'type': 'FUNC'}
{'is_defined': False, 'name': '_ZnamSt11align_val_t', 'type': 'FUNC'}
{'is_defined': False, 'name': '_Znwm', 'type': 'FUNC'}
+{'is_defined': False, 'name': '_ZnwmRKSt9nothrow_t', 'type': 'FUNC'}
{'is_defined': False, 'name': '_ZnwmSt11align_val_t', 'type': 'FUNC'}
{'is_defined': False, 'name': '__cxa_current_primary_exception', 'type': 'FUNC'}
{'is_defined': False, 'name': '__cxa_decrement_exception_refcount', 'type': 'FUNC'}
@@ -1196,8 +1198,11 @@
{'is_defined': True, 'name': '_ZNSt3__123__cxx_atomic_notify_allEPVKv', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__123__cxx_atomic_notify_oneEPVKNS_17__cxx_atomic_implIiNS_22__cxx_atomic_base_implIiEEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__123__cxx_atomic_notify_oneEPVKv', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt3__123__hazard_pointer_retireEPNS_25__hazard_pointer_obj_nodeE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKNS_17__cxx_atomic_implIiNS_22__cxx_atomic_base_implIiEEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__123__libcpp_atomic_monitorEPVKv', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt3__124__hazard_pointer_acquireEv', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt3__124__hazard_pointer_releaseEPNS_21__hazard_pointer_slotE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__125notify_all_at_thread_exitERNS_18condition_variableENS_11unique_lockINS_5mutexEEE', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__126__atomic_notify_all_nativeILm4EEEvPKv', 'type': 'FUNC'}
{'is_defined': True, 'name': '_ZNSt3__126__atomic_notify_one_nativeILm4EEEvPKv', 'type': 'FUNC'}
@@ -2004,4 +2009,4 @@
{'is_defined': True, 'name': '_ZTv0_n24_NSt3__19strstreamD1Ev', 'type': 'FUNC'}
{'name': '_ZTINSt3__113basic_filebufIwNS_11char_traitsIwEEEE', 'size': 24, 'type': 'OBJECT', 'is_defined': True}
{'name': '_ZTSNSt3__113basic_filebufIwNS_11char_traitsIwEEEE', 'size': 47, 'type': 'OBJECT', 'is_defined': True}
-{'name': '_ZTVNSt3__113basic_filebufIwNS_11char_traitsIwEEEE', 'size': 128, 'type': 'OBJECT', 'is_defined': True}
\ No newline at end of file
+{'name': '_ZTVNSt3__113basic_filebufIwNS_11char_traitsIwEEEE', 'size': 128, 'type': 'OBJECT', 'is_defined': True}
diff --git a/libcxx/modules/std.compat.cppm.in b/libcxx/modules/std.compat.cppm.in
index ab08125c4c4e4..1ad9520cd15b2 100644
--- a/libcxx/modules/std.compat.cppm.in
+++ b/libcxx/modules/std.compat.cppm.in
@@ -54,9 +54,6 @@ module;
# if __has_include(<generator>)
# error "please update the header information for <generator> in headers_not_available in utils/libcxx/header_information.py"
# endif // __has_include(<generator>)
-# if __has_include(<hazard_pointer>)
-# error "please update the header information for <hazard_pointer> in headers_not_available in utils/libcxx/header_information.py"
-# endif // __has_include(<hazard_pointer>)
# if __has_include(<inplace_vector>)
# error "please update the header information for <inplace_vector> in headers_not_available in utils/libcxx/header_information.py"
# endif // __has_include(<inplace_vector>)
diff --git a/libcxx/modules/std.cppm.in b/libcxx/modules/std.cppm.in
index b9a491653659a..0b8711319e651 100644
--- a/libcxx/modules/std.cppm.in
+++ b/libcxx/modules/std.cppm.in
@@ -65,6 +65,7 @@ module;
#include <fstream>
#include <functional>
#include <future>
+#include <hazard_pointer>
#include <initializer_list>
#include <iomanip>
#include <ios>
@@ -137,9 +138,6 @@ module;
# if __has_include(<generator>)
# error "please update the header information for <generator> in headers_not_available in utils/libcxx/header_information.py"
# endif // __has_include(<generator>)
-# if __has_include(<hazard_pointer>)
-# error "please update the header information for <hazard_pointer> in headers_not_available in utils/libcxx/header_information.py"
-# endif // __has_include(<hazard_pointer>)
# if __has_include(<inplace_vector>)
# error "please update the header information for <inplace_vector> in headers_not_available in utils/libcxx/header_information.py"
# endif // __has_include(<inplace_vector>)
diff --git a/libcxx/modules/std/hazard_pointer.inc b/libcxx/modules/std/hazard_pointer.inc
index e56f4d0bf4363..0f9b833cc6e2b 100644
--- a/libcxx/modules/std/hazard_pointer.inc
+++ b/libcxx/modules/std/hazard_pointer.inc
@@ -8,16 +8,15 @@
//===----------------------------------------------------------------------===//
export namespace std {
-#if 0
-# if _LIBCPP_STD_VER >= 23
- // 4.1.3, class template hazard_pointer_obj_base
+#if _LIBCPP_STD_VER >= 26 && _LIBCPP_HAS_THREADS
+ // [saferecl.hp.base], class template hazard_pointer_obj_base
using std::hazard_pointer_obj_base;
- // 4.1.4, class hazard_pointer
+
+ // [saferecl.hp.holder], class hazard_pointer
using std::hazard_pointer;
- // 4.1.5, Construct non-empty hazard_pointer
+
+ // [saferecl.hp.holder.nonmem], non-member functions
using std::make_hazard_pointer;
- // 4.1.6, Hazard pointer swap
using std::swap;
-# endif // _LIBCPP_STD_VER >= 23
-#endif
+#endif // _LIBCPP_STD_VER >= 26 && _LIBCPP_HAS_THREADS
} // namespace std
diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt
index e029f9f29d5ea..d9bacf812a5ac 100644
--- a/libcxx/src/CMakeLists.txt
+++ b/libcxx/src/CMakeLists.txt
@@ -67,6 +67,7 @@ if (LIBCXX_ENABLE_THREADS)
condition_variable_destructor.cpp
condition_variable.cpp
future.cpp
+ hazard_pointer.cpp
mutex_destructor.cpp
mutex.cpp
shared_mutex.cpp
diff --git a/libcxx/src/hazard_pointer.cpp b/libcxx/src/hazard_pointer.cpp
new file mode 100644
index 0000000000000..16041b7c46093
--- /dev/null
+++ b/libcxx/src/hazard_pointer.cpp
@@ -0,0 +1,789 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 <__hazard_pointer/domain.h>
+
+#include <__assert>
+#include <__atomic/atomic.h>
+#include <__atomic/fence.h>
+#include <__atomic/memory_order.h>
+#include <__bit/bit_ceil.h>
+#include <__chrono/duration.h>
+#include <__chrono/steady_clock.h>
+#include <__mutex/once_flag.h>
+#include <__thread/support.h>
+#include <cstddef>
+#include <cstdint>
+#include <new>
+
+// The engine behind <hazard_pointer>: a port of Menagerie's Folly-baseline hazard pointer domain
+// (itself a port of folly::hazptr, untagged path only), reduced to the single default domain the
+// Standard specifies. Nothing in this file is ABI: only the three functions at the bottom, and the
+// two structs in <__hazard_pointer/domain.h> they take/return, are.
+//
+// Terminology: a *record* is a hazard pointer (its slot holds the protected node address); a *node* is
+// the header embedded in every hazard-protectable object; the *available list* holds free records; each
+// thread keeps a small cache of free records; retired nodes wait in eight sharded lists until a
+// reclamation pass -- run inline by whichever thread's retire() crosses the threshold -- deletes the
+// unprotected ones.
+
+#if defined(__SANITIZE_THREAD__)
+# define _LIBCPP_HAZARD_POINTER_TSAN 1
+#elif defined(__has_feature)
+# if __has_feature(thread_sanitizer)
+# define _LIBCPP_HAZARD_POINTER_TSAN 1
+# endif
+#endif
+#ifndef _LIBCPP_HAZARD_POINTER_TSAN
+# define _LIBCPP_HAZARD_POINTER_TSAN 0
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+namespace {
+
+// Configuration
+
+constexpr int __retired_count_threshold = 1000; // a pass runs once this many objects are retired ...
+constexpr int __threshold_multiplier = 2; // ... or twice the record count, whichever is larger ...
+constexpr uintptr_t __sync_time_period_s = 2; // ... or two seconds after the previous pass
+constexpr int __shard_count = 8;
+constexpr int __shard_mask = __shard_count - 1;
+constexpr int __ignored_low_bits = 8;
+constexpr uintptr_t __lock_bit = 1;
+constexpr uint8_t __thread_cache_capacity = 9;
+// Records are laid out one per cache line (see __hazard_pointer_record). Where over-aligned allocation is
+// not available, std::__libcpp_allocate can only promise fundamental alignment, so fall back to that.
+#if !_LIBCPP_HAS_ALIGNED_ALLOCATION
+constexpr size_t __cache_line_size = alignof(max_align_t);
+#elif defined(__GCC_DESTRUCTIVE_SIZE)
+constexpr size_t __cache_line_size = __GCC_DESTRUCTIVE_SIZE;
+#else
+constexpr size_t __cache_line_size = 64;
+#endif
+
+// 64-bit mixer shared by the shard function and the protected set.
+inline size_t __mix_pointer(uintptr_t __x) noexcept {
+ if constexpr (sizeof(uintptr_t) == 8) {
+ __x ^= __x >> 32;
+ __x *= 0x9E3779B97F4A7C15ULL;
+ __x ^= __x >> 29;
+ } else {
+ __x ^= __x >> 16;
+ __x *= 0x9E3779B1U;
+ __x ^= __x >> 15;
+ }
+ return static_cast<size_t>(__x);
+}
+
+// Record
+
+// One cache line per record: records of different threads must not share a line (every protect() writes
+// to it); targets without over-aligned allocation fall back to fundamental alignment. Records are created
+// by the domain's grow array and never destroyed.
+struct alignas(__cache_line_size) __hazard_pointer_record : __hazard_pointer_slot {
+ __hazard_pointer_record* __next_available_ = nullptr; // available-list link; meaningful only while free
+ __hazard_pointer_record() noexcept : __hazard_pointer_slot{nullptr} {}
+};
+
+// Grow-only array (port of Menagerie's AtomicGrowArray / folly::atomic_grow_array)
+
+// Concurrent grow-only array with wait-free indexed access and reference stability. operator[](i) is
+// valid for every i: if i >= __size() the array grows to bit_ceil(i + 1), default-constructing every
+// element up to the new capacity. Growth is a race decided by one CAS; losers destroy what they built.
+// Each growth round is one allocation holding {header}{_Tp* list[capacity]}{_Tp slab[capacity - base]};
+// older rounds stay alive on a chain, so element addresses never change. __size() is *not* monotonic -- a
+// delayed store from a smaller round can lower it after a larger one published -- but it is always a lower
+// bound on what has been constructed, which is all operator[]'s fast path needs. Never destroyed (the domain
+// is immortal). _Tp must be nothrow default constructible.
+template <class _Tp>
+class __grow_array {
+ struct __round {
+ __round* __next_ = nullptr; // the previous (smaller) round; owns elements [0, __next_->__size_)
+ size_t __size_ = 0; // > __next_->__size_
+ _Tp** __list() noexcept { return reinterpret_cast<_Tp**>(reinterpret_cast<byte*>(this) + sizeof(__round)); }
+ };
+
+ atomic<size_t> __size_{0};
+ atomic<__round*> __array_{nullptr};
+
+ static constexpr size_t __alignment() noexcept {
+ return alignof(_Tp) > alignof(max_align_t) ? alignof(_Tp) : alignof(max_align_t);
+ }
+ static constexpr size_t __ceil_to(size_t __value, size_t __alignment) noexcept {
+ return (__value + __alignment - 1) / __alignment * __alignment;
+ }
+ static constexpr size_t __bytes(size_t __capacity, size_t __base) noexcept {
+ return __ceil_to(sizeof(__round) + __capacity * sizeof(_Tp*), __alignment()) +
+ __ceil_to((__capacity - __base) * sizeof(_Tp), __alignment());
+ }
+ static _Tp* __slab(__round* __curr) noexcept {
+ uintptr_t __list_end = reinterpret_cast<uintptr_t>(__curr->__list() + __curr->__size_);
+ return reinterpret_cast<_Tp*>(__ceil_to(static_cast<size_t>(__list_end), __alignment()));
+ }
+
+ // Builds a round of __capacity elements on top of __next; returns nullptr (and updates __next) when
+ // another thread published a round meanwhile. May throw bad_alloc.
+ __round* __new_round(size_t __capacity, __round*& __next) {
+ size_t __base = __next ? __next->__size_ : 0;
+ void* __raw = std::__libcpp_allocate<byte>(__element_count(__bytes(__capacity, __base)), __alignment());
+ __round* __curr = ::new (__raw) __round{};
+ __curr->__next_ = __next;
+ __curr->__size_ = __capacity;
+ _Tp* __slab_ptr = __slab(__curr);
+ _Tp** __list = __curr->__list();
+ for (size_t __i = 0; __i < __base; ++__i)
+ __list[__i] = __next->__list()[__i];
+ for (size_t __i = __base; __i < __capacity; ++__i)
+ __list[__i] = nullptr;
+ for (size_t __i = __base; __i < __capacity; ++__i) {
+ __round* __seen = __array_.load(memory_order_acquire);
+ if (__seen != __next) { // race lost early: stop building
+ __next = __seen;
+ __del_round(__curr);
+ return nullptr;
+ }
+ __list[__i] = ::new (static_cast<void*>(&__slab_ptr[__i - __base])) _Tp();
+ }
+ return __curr;
+ }
+
+ void __del_round(__round* __curr) noexcept {
+ size_t __built = __curr->__size_;
+ __round* __next = __curr->__next_;
+ size_t __base = __next ? __next->__size_ : 0;
+ size_t __size = __bytes(__curr->__size_, __base);
+ _Tp** __list = __curr->__list();
+ while (__built > __base && __list[__built - 1] == nullptr) // never-constructed tail
+ --__built;
+ for (size_t __i = 0; __i < __built - __base; ++__i)
+ __list[__built - 1 - __i]->~_Tp();
+ std::__libcpp_deallocate<byte>(reinterpret_cast<byte*>(__curr), __element_count(__size), __alignment());
+ }
+
+ __round* __at_slow(size_t __index) {
+ // Optimistic concurrency: no mutex, so element constructors may themselves use the array.
+ __round* __p = __array_.load(memory_order_acquire);
+ size_t __capacity = std::__bit_ceil(__index + 1);
+ while (true) {
+ if (__p && __index < __p->__size_)
+ return __p;
+ __round* __q = __new_round(__capacity, __p); // __p is updated when the race is lost early
+ if (__q == nullptr)
+ continue;
+ if (__array_.compare_exchange_strong(__p, __q, memory_order_acq_rel, memory_order_acquire)) {
+ __size_.store(__capacity, memory_order_release);
+ return __q;
+ }
+ __del_round(__q);
+ }
+ }
+
+public:
+ constexpr __grow_array() noexcept = default;
+ __grow_array(const __grow_array&) = delete;
+ __grow_array& operator=(const __grow_array&) = delete;
+
+ size_t __size() const noexcept { return __size_.load(memory_order_acquire); }
+
+ // Reference to element __index, growing the array if needed. May throw bad_alloc.
+ _Tp& operator[](size_t __index) {
+ bool __fast = __index < __size_.load(memory_order_acquire);
+ __round* __p = __fast ? __array_.load(memory_order_acquire) : __at_slow(__index);
+ return *__p->__list()[__index];
+ }
+
+ // Pointers to the first min(__count, size) elements constructed so far, in index order; __count is
+ // clamped. Returns nullptr when the array is still empty.
+ _Tp* const* __pointers(size_t& __count) const noexcept {
+ __round* __p = __array_.load(memory_order_acquire);
+ if (__p == nullptr) {
+ __count = 0;
+ return nullptr;
+ }
+ if (__count > __p->__size_)
+ __count = __p->__size_;
+ return __p->__list();
+ }
+};
+
+// Per-thread cache of free records
+
+// A per-thread stack of up to 9 free records. It sits in front of the domain's available list:
+// __hazard_pointer_acquire() pops from it and __hazard_pointer_release() pushes to it, so the steady state
+// costs no atomics on the domain's shared state (the call_once fast path and __libcpp_tls_get remain).
+// Reached through a pthread key (see __hazard_pointer_domain::__thread_cache).
+struct __hazard_pointer_thread_cache {
+ __hazard_pointer_record* __entries_[__thread_cache_capacity] = {};
+ uint8_t __count_ = 0;
+
+ __hazard_pointer_record* __try_get() noexcept { return __count_ > 0 ? __entries_[--__count_] : nullptr; }
+
+ bool __try_put(__hazard_pointer_record* __record) noexcept {
+ if (__count_ < __thread_cache_capacity) {
+ __entries_[__count_++] = __record;
+ return true;
+ }
+ return false;
+ }
+};
+
+// Sentinel stored in the TLS key by __at_thread_exit once a thread's cache has been torn down. While it is
+// in the key, __thread_cache() reports "no cache", so a hazard_pointer destroyed by a destructor that runs
+// after ours releases its record straight to the domain instead of touching the freed cache.
+//
+// The sentinel is armed when the cache is torn down. POSIX clears a TSD value before each destructor
+// round, so a later round sees null again, not the sentinel. We deliberately do not re-arm it: re-arming
+// would keep this thread in destructor rounds after sanitizer runtimes have already finalized it.
+// Consequently, a make_hazard_pointer() called from a foreign TSD destructor in a later round may build a
+// fresh cache that is never evicted -- a bounded leak of that cache object plus up to
+// __thread_cache_capacity records. Records are immortal, so the cache object is the actual leak.
+__hazard_pointer_thread_cache __dead_thread_cache;
+
+// Intrusive lists of retired nodes
+
+using __node = __hazard_pointer_obj_node;
+
+// A (head, tail, count) list of nodes; single-threaded.
+struct __node_list {
+ __node* __head_ = nullptr;
+ __node* __tail_ = nullptr;
+ int __count_ = 0;
+
+ bool __empty() const noexcept { return __head_ == nullptr; }
+
+ void __push(__node* __n) noexcept {
+ __n->__next_ = nullptr;
+ if (__tail_ != nullptr)
+ __tail_->__next_ = __n;
+ else
+ __head_ = __n;
+ __tail_ = __n;
+ ++__count_;
+ }
+
+ void __clear() noexcept {
+ __head_ = nullptr;
+ __tail_ = nullptr;
+ __count_ = 0;
+ }
+};
+
+// A head-only list shared between threads: lock-free push of a whole __node_list, wait-free pop-all.
+struct __shared_node_list {
+ atomic<__node*> __head_{nullptr};
+
+ void __push(__node_list& __list) noexcept { // prepends the whole list, kept in order
+ if (__list.__empty())
+ return;
+ __node* __old_head = __head_.load(memory_order_acquire);
+ do {
+ __list.__tail_->__next_ = __old_head;
+ } while (!__head_.compare_exchange_weak(__old_head, __list.__head_, memory_order_acq_rel, memory_order_acquire));
+ __list.__clear();
+ }
+
+ __node* __pop_all() noexcept { return __head_.exchange(nullptr, memory_order_acq_rel); }
+
+ bool __empty() const noexcept { return __head_.load(memory_order_acquire) == nullptr; }
+};
+
+// The set of protected addresses built by every reclamation pass
+
+// Open addressing, power-of-two table, __mix_pointer as the hash. Storage comes from nothrow new;
+// __insert() returns false when the table cannot grow, in which case the pass falls back to matching
+// each retired node against the records directly (see __hazard_pointer_domain::__match_reclaim).
+class __protected_set {
+ uintptr_t* __slots_ = nullptr; // 0 = empty; only non-null addresses are inserted
+ size_t __capacity_ = 0;
+ size_t __mask_ = 0;
+ size_t __size_ = 0;
+
+ void __insert_no_grow(uintptr_t __key) noexcept {
+ size_t __i = __mix_pointer(__key) & __mask_;
+ while (__slots_[__i] != 0) {
+ if (__slots_[__i] == __key)
+ return;
+ __i = (__i + 1) & __mask_;
+ }
+ __slots_[__i] = __key;
+ ++__size_;
+ }
+
+ bool __grow() noexcept {
+ size_t __new_capacity = __capacity_ == 0 ? 16 : __capacity_ * 2;
+ uintptr_t* __new_slots = ::new (nothrow) uintptr_t[__new_capacity](); // zero-initialised
+ if (__new_slots == nullptr)
+ return false;
+ uintptr_t* __old = __slots_;
+ size_t __old_capacity = __capacity_;
+ __slots_ = __new_slots;
+ __capacity_ = __new_capacity;
+ __mask_ = __new_capacity - 1;
+ __size_ = 0;
+ for (size_t __i = 0; __i < __old_capacity; ++__i)
+ if (__old[__i] != 0)
+ __insert_no_grow(__old[__i]);
+ delete[] __old;
+ return true;
+ }
+
+public:
+ __protected_set() = default;
+ __protected_set(const __protected_set&) = delete;
+ __protected_set& operator=(const __protected_set&) = delete;
+ ~__protected_set() { delete[] __slots_; }
+
+ bool __insert(const void* __p) noexcept {
+ if ((__size_ + 1) * 2 > __capacity_ && !__grow())
+ return false;
+ __insert_no_grow(reinterpret_cast<uintptr_t>(__p));
+ return true;
+ }
+
+ bool __contains(const void* __p) const noexcept {
+ if (__size_ == 0)
+ return false;
+ uintptr_t __key = reinterpret_cast<uintptr_t>(__p);
+ size_t __i = __mix_pointer(__key) & __mask_;
+ while (__slots_[__i] != 0) {
+ if (__slots_[__i] == __key)
+ return true;
+ __i = (__i + 1) & __mask_;
+ }
+ return false;
+ }
+};
+
+// The domain
+
+class __hazard_pointer_domain {
+ using __record = __hazard_pointer_record;
+
+ // Records: every record ever created (never destroyed) and the free ones
+ __grow_array<__record> __records_;
+ atomic<int> __record_count_{0}; // records handed out so far = the prefix of __records_ the scan reads
+ atomic<uintptr_t> __available_{0}; // head of the free list; low bit = lock
+
+ // Per-thread caches
+ once_flag __tls_once_;
+ __libcpp_tls_key __tls_key_ = {};
+ bool __tls_ok_ = false;
+
+ // Retired objects
+ __shared_node_list __retired_[__shard_count]; // sharded by node address to spread contention
+ atomic<int> __retired_count_{0}; // signed: transiently negative during a pass (see below)
+ // steady_clock seconds, kept word-sized: a 64-bit atomic is not lock-free on every 32-bit target, and
+ // 2^32 seconds of uptime is over a century, so seconds never wrap. A pass also runs when now >= due.
+ // Starting at 0 means the very first retire() in the process is always due and runs a (nearly empty)
+ // pass, which is what arms the real due time.
+ atomic<uintptr_t> __due_time_{0};
+
+public:
+ constexpr __hazard_pointer_domain() noexcept = default;
+ __hazard_pointer_domain(const __hazard_pointer_domain&) = delete;
+ __hazard_pointer_domain& operator=(const __hazard_pointer_domain&) = delete;
+
+ // Entry points
+
+ // A free, unassociated record for the calling thread. May throw bad_alloc.
+ __hazard_pointer_slot* __acquire_slot() {
+ if (__hazard_pointer_thread_cache* __cache = __thread_cache(/*__create=*/true))
+ if (__record* __rec = __cache->__try_get())
+ return __rec;
+ return __acquire_records(1);
+ }
+
+ // Ends the epoch of __slot and returns its record to the calling thread's cache, if it has one,
+ // else to the domain. Never allocates a cache: a thread that only ever releases (e.g. a
+ // hazard_pointer moved in from another thread, or one destroyed during thread teardown) must not
+ // leave records stranded in a cache nothing will evict.
+ void __release_slot(__hazard_pointer_slot* __slot) noexcept {
+ __record* __rec = static_cast<__record*>(__slot);
+ __rec->__value_.store(nullptr, memory_order_release);
+ if (__hazard_pointer_thread_cache* __cache = __thread_cache(/*__create=*/false))
+ if (__cache->__try_put(__rec))
+ return;
+ __push_available(__rec, __rec);
+ }
+
+ // Retires __node: the tail of hazard_pointer_obj_base::retire(). May run a reclamation pass (and thus
+ // deleters) synchronously.
+ void __retire(__node* __n) noexcept {
+ // Retire-side fence: orders the user's store to `src` (sequenced before this call) before the
+ // reclaimer's scan, even when the reclaimer is another thread. See <__hazard_pointer/domain.h>.
+ std::atomic_thread_fence(memory_order_seq_cst);
+ __node_list __list;
+ __list.__push(__n);
+ __retired_[__shard_of(__n)].__push(__list);
+ __retired_count_.fetch_add(1, memory_order_release);
+ __check_threshold_and_reclaim();
+ }
+
+private:
+ // Trigger
+
+ static uintptr_t __now_s() noexcept {
+ return static_cast<uintptr_t>(
+ chrono::duration_cast<chrono::seconds>(chrono::steady_clock::now().time_since_epoch()).count());
+ }
+
+ static size_t __shard_of(const __node* __n) noexcept {
+ return (__mix_pointer(reinterpret_cast<uintptr_t>(__n)) >> __ignored_low_bits) & __shard_mask;
+ }
+
+ int __threshold() const noexcept {
+ int __records = __record_count_.load(memory_order_relaxed);
+ int __scaled = __threshold_multiplier * __records;
+ return __scaled > __retired_count_threshold ? __scaled : __retired_count_threshold;
+ }
+
+ // If the retired count reached the threshold, claims it (resets it to 0) and returns it; else 0.
+ int __check_count_threshold() noexcept {
+ int __count = __retired_count_.load(memory_order_acquire);
+ while (__count >= __threshold()) {
+ if (__retired_count_.compare_exchange_weak(__count, 0, memory_order_acq_rel, memory_order_relaxed)) {
+ __due_time_.store(__now_s() + __sync_time_period_s, memory_order_release);
+ return __count;
+ }
+ }
+ return 0;
+ }
+
+ // If the due time has passed, claims the current count (may be small) and returns it; else 0.
+ int __check_due_time() noexcept {
+ uintptr_t __time = __now_s();
+ uintptr_t __due = __due_time_.load(memory_order_acquire);
+ if (__time < __due || !__due_time_.compare_exchange_strong(
+ __due, __time + __sync_time_period_s, memory_order_acq_rel, memory_order_relaxed))
+ return 0;
+ int __count = __retired_count_.exchange(0, memory_order_acq_rel);
+ if (__count < 0) {
+ __retired_count_.fetch_add(__count, memory_order_release);
+ return 0;
+ }
+ return __count;
+ }
+
+ void __check_threshold_and_reclaim() noexcept {
+ int __count = __check_count_threshold();
+ if (__count == 0) {
+ __count = __check_due_time();
+ if (__count == 0)
+ return;
+ }
+ __do_reclamation(__count);
+ }
+
+ // Reclamation pass
+
+ bool __retired_empty() const noexcept {
+ for (const __shared_node_list& __shard : __retired_)
+ if (!__shard.__empty())
+ return false;
+ return true;
+ }
+
+ // Pops every shard into __extracted; returns the number of nodes taken.
+ int __extract_retired(__node* __extracted[__shard_count]) noexcept {
+ int __taken = 0;
+ for (int __s = 0; __s < __shard_count; ++__s) {
+ __extracted[__s] = __retired_[__s].__pop_all();
+ for (__node* __n = __extracted[__s]; __n != nullptr; __n = __n->__next_)
+ ++__taken;
+ }
+ return __taken;
+ }
+
+ // Loads every non-null slot value into __set. Returns false when the set could not be built (allocation
+ // failure); the caller then matches linearly.
+ bool __load_protected(__protected_set& __set) noexcept {
+ int __handed_out = __record_count_.load(memory_order_relaxed);
+ size_t __count = __handed_out > 0 ? static_cast<size_t>(__handed_out) : 0;
+ __record* const* __recs = __records_.__pointers(__count); // clamps __count to what exists
+#if _LIBCPP_HAZARD_POINTER_TSAN
+ constexpr memory_order __order = memory_order_acquire; // TSan does not model fences
+#else
+ constexpr memory_order __order = memory_order_relaxed; // the fence below provides the acquire ordering
+#endif
+ // Load a small batch up front so the branches below do not form one long dependency chain.
+ constexpr size_t __chunk = 8;
+ size_t __i = 0;
+ for (; __i + __chunk <= __count; __i += __chunk) {
+ const void* __values[__chunk];
+ for (size_t __j = 0; __j < __chunk; ++__j)
+ __values[__j] = __recs[__i + __j]->__value_.load(__order);
+ for (const void* __v : __values)
+ if (__v != nullptr && !__set.__insert(__v))
+ return false;
+ }
+ for (; __i < __count; ++__i) {
+ const void* __v = __recs[__i]->__value_.load(__order);
+ if (__v != nullptr && !__set.__insert(__v))
+ return false;
+ }
+ std::atomic_thread_fence(memory_order_acquire);
+ return true;
+ }
+
+ // Allocation-free fallback: is __n currently held by any record? O(records).
+ bool __protected_linear(const __node* __n) const noexcept {
+ int __handed_out = __record_count_.load(memory_order_relaxed);
+ size_t __count = __handed_out > 0 ? static_cast<size_t>(__handed_out) : 0;
+ __record* const* __recs = __records_.__pointers(__count);
+ for (size_t __i = 0; __i < __count; ++__i)
+ if (__recs[__i]->__value_.load(memory_order_acquire) == static_cast<const void*>(__n))
+ return true;
+ return false;
+ }
+
+ // Reclaims the unprotected extracted nodes and pushes the protected ones back. Returns the number
+ // pushed back; sets __done = false when new nodes were retired meanwhile (e.g. by a deleter).
+ int __match_reclaim(__node* __retired[__shard_count], const __protected_set* __set, bool& __done) noexcept {
+ __done = true;
+ __node_list __not_reclaimed[__shard_count];
+ __node* __heads[__shard_count];
+ for (int __s = 0; __s < __shard_count; ++__s)
+ __heads[__s] = __retired[__s];
+ for (bool __more = true; __more;) { // walk the shards in lock-step (instruction-level parallelism)
+ __more = false;
+ for (int __s = 0; __s < __shard_count; ++__s) {
+ __node* __n = __heads[__s];
+ if (__n == nullptr)
+ continue;
+ __more = true;
+ __heads[__s] = __n->__next_; // read before the deleter may free __n
+ bool __is_protected = __set != nullptr ? __set->__contains(__n) : __protected_linear(__n);
+ if (__is_protected)
+ __not_reclaimed[__s].__push(__n);
+ else
+ __n->__reclaim_(__n);
+ }
+ }
+ if (!__retired_empty())
+ __done = false;
+ int __residue = 0;
+ for (int __s = 0; __s < __shard_count; ++__s) {
+ __residue += __not_reclaimed[__s].__count_;
+ __retired_[__s].__push(__not_reclaimed[__s]);
+ }
+ return __residue;
+ }
+
+ // The bulk reclamation pass. __count is the number of retirements claimed by the trigger. The claim is
+ // settled against what the pass actually extracted before any deleter runs, so that from then on
+ // __retired_count_ again counts the nodes sitting in the shards (transiently negative when nodes retired
+ // concurrently were extracted before their retire() counted them); the protected residue is added back
+ // when it is returned to the shards.
+ void __do_reclamation(int __count) noexcept {
+ while (true) {
+ __node* __retired[__shard_count];
+ bool __done = true;
+ if (int __taken = __extract_retired(__retired)) {
+ // Settling now rather than after the deleters matters when two passes overlap: the credit a pass
+ // claimed for nodes another pass then extracted would otherwise sit in the counter, above the
+ // threshold, for the whole duration of that other pass, and every retire() in between would run
+ // a fruitless full pass.
+ __count -= __taken;
+ if (__count != 0)
+ __retired_count_.fetch_add(__count, memory_order_release);
+ // Reclaimer-side fence: pairs with the readers' fence in try_protect() and the retirers' fence in
+ // __retire(). See <__hazard_pointer/domain.h>.
+ std::atomic_thread_fence(memory_order_seq_cst);
+ __protected_set __set;
+ const __protected_set* __set_ptr = __load_protected(__set) ? &__set : nullptr;
+ // Residue bound: residue <= records < max(1000, 2 * records) == __threshold(), because a node
+ // survives a pass only while some record protects it and distinct nodes have distinct addresses.
+ if (int __residue = __match_reclaim(__retired, __set_ptr, __done))
+ __retired_count_.fetch_add(__residue, memory_order_release);
+ } else if (__count != 0) {
+ // Nothing to hold the claim against (another pass took the nodes): give it back and return
+ // rather than claiming it again; the owning pass has settled or will settle its own extract.
+ __retired_count_.fetch_add(__count, memory_order_release);
+ }
+ if (__done)
+ break;
+ __count = __check_count_threshold();
+ }
+ }
+
+public:
+ static void _LIBCPP_TLS_DESTRUCTOR_CC __at_thread_exit(void* __p);
+
+private:
+ // Available list (lock-bit stack of free records)
+
+ uintptr_t __load_available() const noexcept { return __available_.load(memory_order_acquire); }
+ void __store_available(uintptr_t __value) noexcept { __available_.store(__value, memory_order_release); }
+ bool __cas_available(uintptr_t& __expected, uintptr_t __desired) noexcept {
+ return __available_.compare_exchange_weak(__expected, __desired, memory_order_acq_rel, memory_order_acquire);
+ }
+
+ // Pops up to __count records; returns the head of a __next_available_-linked chain and sets __popped.
+ // __count is only ever 1 today; the batch capability is kept for a future batch-acquire entry point
+ // (P3428R4's hazard_pointer_array).
+ __record* __try_pop_available(uint8_t __count, uint8_t& __popped) noexcept {
+ while (true) {
+ uintptr_t __available = __load_available();
+ if (__available == 0) {
+ __popped = 0;
+ return nullptr;
+ }
+ if ((__available & __lock_bit) == 0) {
+ if (__cas_available(__available, __available | __lock_bit)) { // locked
+ __record* __head = reinterpret_cast<__record*>(__available);
+ __popped = __pop_available_release_lock(__count, __head);
+ return __head;
+ }
+ } else {
+ std::__libcpp_thread_yield();
+ }
+ }
+ }
+
+ // Lock already held: detaches up to __count records starting at __head, then stores the new head
+ // (which releases the lock).
+ uint8_t __pop_available_release_lock(uint8_t __count, __record* __head) noexcept {
+ __record* __tail = __head;
+ uint8_t __taken = 1;
+ __record* __next = __tail->__next_available_;
+ while (__next != nullptr && __taken < __count) {
+ __tail = __next;
+ __next = __tail->__next_available_;
+ ++__taken;
+ }
+ _LIBCPP_ASSERT_INTERNAL(
+ (reinterpret_cast<uintptr_t>(__next) & __lock_bit) == 0, "hazard pointer record misaligned");
+ __store_available(reinterpret_cast<uintptr_t>(__next)); // releases the lock
+ __tail->__next_available_ = nullptr;
+ return __taken;
+ }
+
+ // Pushes the chain [__head, __tail] (tail's link is overwritten) onto the available list.
+ void __push_available(__record* __head, __record* __tail) noexcept {
+ uintptr_t __new_head = reinterpret_cast<uintptr_t>(__head);
+ _LIBCPP_ASSERT_INTERNAL((__new_head & __lock_bit) == 0, "hazard pointer record misaligned");
+ while (true) {
+ uintptr_t __available = __load_available();
+ if ((__available & __lock_bit) == 0) {
+ __tail->__next_available_ = reinterpret_cast<__record*>(__available);
+ if (__cas_available(__available, __new_head))
+ return;
+ } else {
+ std::__libcpp_thread_yield();
+ }
+ }
+ }
+
+ // Record creation
+
+ __record* __create_record() { // may throw bad_alloc
+ // Any scan that must see the new record sees this increment and the grow-array round holding the
+ // record: acquiring a hazard pointer precedes the reader's seq_cst fence in try_protect(), and when
+ // that fence precedes the reclaimer's seq_cst fence in the fences' total order, the scan's loads observe
+ // every write that happens-before the reader's fence -- including the round-publishing CAS of another
+ // thread that this one merely observed (C++20 [atomics.order]/4.4, P0668; the same cumulativity RC11
+ // and every hardware mapping we target provide, and that folly::hazptr rests on).
+ //
+ // The count is bumped before the element exists. If __records_[] throws, the domain has counted one
+ // record more than it built; the scan clamps to what the grow array actually holds (see __pointers),
+ // so the over-count only makes __threshold() marginally larger.
+ int __index = __record_count_.fetch_add(1, memory_order_relaxed);
+ return &__records_[static_cast<size_t>(__index)];
+ }
+
+ // Pops __count records from the available list, creating the shortfall. Returns the head of a
+ // __next_available_-linked chain of exactly __count records. As with __try_pop_available, __count is
+ // always 1 today; batching is kept for a future batch-acquire entry point (P3428R4).
+ __record* __acquire_records(uint8_t __count) {
+ uint8_t __popped = 0;
+ __record* __head = __try_pop_available(__count, __popped);
+ for (; __popped < __count; ++__popped) {
+ __record* __created = __create_record();
+ __created->__next_available_ = __head;
+ __head = __created;
+ }
+ return __head;
+ }
+
+ // Thread cache plumbing
+
+ void __init_tls() noexcept {
+ std::call_once(__tls_once_, [this] {
+ __tls_ok_ = std::__libcpp_tls_create(&__tls_key_, &__hazard_pointer_domain::__at_thread_exit) == 0;
+ });
+ }
+
+ // The calling thread's cache, or nullptr when the thread has none (TLS unavailable, allocation failed,
+ // or its cache was already torn down). __create: allocate one on first use.
+ __hazard_pointer_thread_cache* __thread_cache(bool __create) noexcept {
+ __init_tls();
+ if (!__tls_ok_)
+ return nullptr;
+ void* __p = std::__libcpp_tls_get(__tls_key_);
+ if (__p == static_cast<void*>(&__dead_thread_cache))
+ return nullptr;
+ if (__p == nullptr && __create) {
+ __p = ::new (nothrow) __hazard_pointer_thread_cache();
+ if (__p != nullptr && std::__libcpp_tls_set(__tls_key_, __p) != 0) {
+ delete static_cast<__hazard_pointer_thread_cache*>(__p);
+ __p = nullptr;
+ }
+ }
+ return static_cast<__hazard_pointer_thread_cache*>(__p);
+ }
+
+ // Returns every cached record to the available list.
+ void __evict(__hazard_pointer_thread_cache& __cache) noexcept {
+ if (__cache.__count_ == 0)
+ return;
+ __record* __head = nullptr;
+ __record* __tail = nullptr;
+ while (__cache.__count_ > 0) {
+ __record* __rec = __cache.__entries_[--__cache.__count_];
+ __rec->__next_available_ = __head;
+ __head = __rec;
+ if (__tail == nullptr)
+ __tail = __rec;
+ }
+ __push_available(__head, __tail);
+ }
+};
+
+constinit __hazard_pointer_domain __domain;
+
+// The TSD destructor of the thread cache. POSIX clears the key's value before each destructor round, so
+// round 1 arrives with the cache -- return its records to the domain, free it, and arm the sentinel -- and
+// round 2 arrives with the sentinel and does nothing, which leaves the key null for good. Deliberately no
+// re-arming: see the note above __dead_thread_cache.
+void _LIBCPP_TLS_DESTRUCTOR_CC __hazard_pointer_domain::__at_thread_exit(void* __p) {
+ if (__p == nullptr || __p == static_cast<void*>(&__dead_thread_cache))
+ return;
+ __hazard_pointer_thread_cache* __cache = static_cast<__hazard_pointer_thread_cache*>(__p);
+ __domain.__evict(*__cache);
+ delete __cache;
+ std::__libcpp_tls_set(__domain.__tls_key_, &__dead_thread_cache);
+}
+
+} // namespace
+
+_LIBCPP_BEGIN_EXPLICIT_ABI_ANNOTATIONS
+
+_LIBCPP_EXPORTED_FROM_ABI __hazard_pointer_slot* __hazard_pointer_acquire() { return __domain.__acquire_slot(); }
+
+_LIBCPP_EXPORTED_FROM_ABI void __hazard_pointer_release(__hazard_pointer_slot* __slot) noexcept {
+ __domain.__release_slot(__slot);
+}
+
+_LIBCPP_EXPORTED_FROM_ABI void __hazard_pointer_retire(__hazard_pointer_obj_node* __n) noexcept {
+ __domain.__retire(__n);
+}
+
+_LIBCPP_END_EXPLICIT_ABI_ANNOTATIONS
+
+_LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/test/benchmarks/hazard_pointer.bench.cpp b/libcxx/test/benchmarks/hazard_pointer.bench.cpp
new file mode 100644
index 0000000000000..78fed17d597ad
--- /dev/null
+++ b/libcxx/test/benchmarks/hazard_pointer.bench.cpp
@@ -0,0 +1,140 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+#include <hazard_pointer>
+#include <atomic>
+#include <cstddef>
+#include <memory>
+#include <vector>
+
+#include "benchmark/benchmark.h"
+
+namespace {
+
+struct Node : std::hazard_pointer_obj_base<Node> {
+ std::size_t value = 42;
+};
+
+// make + destroy a nonempty hazard_pointer per iteration (thread-cache hit in the steady state).
+void BM_make_destroy(benchmark::State& state) {
+ for (auto _ : state) {
+ std::hazard_pointer h = std::make_hazard_pointer();
+ benchmark::DoNotOptimize(h);
+ }
+}
+
+// protect() per iteration with one hazard_pointer made once; the protection is left in place.
+void BM_protect_combined(benchmark::State& state) {
+ auto own = std::make_unique<Node>();
+ std::atomic<Node*> src{own.get()};
+ benchmark::DoNotOptimize(src);
+ std::hazard_pointer h = std::make_hazard_pointer();
+ std::size_t sum = 0;
+ for (auto _ : state) {
+ Node* p = h.protect(src);
+ benchmark::DoNotOptimize(p->value);
+ sum += p->value;
+ }
+ h.reset_protection();
+ benchmark::DoNotOptimize(sum);
+}
+
+// protect() + reset_protection() per iteration.
+void BM_protect_separate(benchmark::State& state) {
+ auto own = std::make_unique<Node>();
+ std::atomic<Node*> src{own.get()};
+ benchmark::DoNotOptimize(src);
+ std::hazard_pointer h = std::make_hazard_pointer();
+ std::size_t sum = 0;
+ for (auto _ : state) {
+ Node* p = h.protect(src);
+ benchmark::DoNotOptimize(p->value);
+ sum += p->value;
+ h.reset_protection();
+ }
+ benchmark::DoNotOptimize(sum);
+}
+
+// make + protect + destroy per iteration.
+void BM_make_protect(benchmark::State& state) {
+ auto own = std::make_unique<Node>();
+ std::atomic<Node*> src{own.get()};
+ benchmark::DoNotOptimize(src);
+ std::size_t sum = 0;
+ for (auto _ : state) {
+ std::hazard_pointer h = std::make_hazard_pointer();
+ Node* p = h.protect(src);
+ benchmark::DoNotOptimize(p->value);
+ sum += p->value;
+ }
+ benchmark::DoNotOptimize(sum);
+}
+
+// retire() of a pre-allocated object per iteration (allocation happens in untimed batches); reclamation
+// runs inline every ~1000 retirements and is part of the measurement, as it is in real use.
+void BM_retire(benchmark::State& state) {
+ constexpr std::size_t kBatch = std::size_t{1} << 16;
+ std::vector<std::unique_ptr<Node>> objs;
+ std::size_t next = kBatch;
+ for (auto _ : state) {
+ if (next == kBatch) {
+ state.PauseTiming();
+ objs.clear();
+ objs.reserve(kBatch);
+ for (std::size_t i = 0; i < kBatch; ++i)
+ objs.push_back(std::make_unique<Node>());
+ next = 0;
+ state.ResumeTiming();
+ }
+ Node* n = objs[next++].release();
+ n->retire();
+ }
+}
+
+// Multi-threaded: every thread makes and destroys hazard pointers. Deliberately the same body as
+// BM_make_destroy, so the two are directly comparable: the difference is the cost of contention.
+void BM_mt_make_destroy(benchmark::State& state) {
+ for (auto _ : state) {
+ std::hazard_pointer h = std::make_hazard_pointer();
+ benchmark::DoNotOptimize(h);
+ }
+}
+
+// Multi-threaded: every thread allocates and retires objects (contended retired lists + reclamation).
+void BM_mt_retire(benchmark::State& state) {
+ for (auto _ : state) {
+ Node* n = new Node;
+ n->retire();
+ }
+}
+
+} // namespace
+
+BENCHMARK(BM_make_destroy)->Name("std::make_hazard_pointer() (make + destroy)");
+BENCHMARK(BM_protect_combined)->Name("std::hazard_pointer::protect(const atomic<T*>&) (protection kept)");
+BENCHMARK(BM_protect_separate)->Name("std::hazard_pointer::protect(const atomic<T*>&) (+ reset_protection())");
+BENCHMARK(BM_make_protect)->Name("std::make_hazard_pointer() (+ protect(), destroy)");
+BENCHMARK(BM_retire)->Name("std::hazard_pointer_obj_base<T>::retire()");
+BENCHMARK(BM_mt_make_destroy)
+ ->Name("std::make_hazard_pointer() (make + destroy, contended)")
+ ->Threads(1)
+ ->Threads(2)
+ ->Threads(4)
+ ->Threads(8);
+BENCHMARK(BM_mt_retire)
+ ->Name("std::hazard_pointer_obj_base<T>::retire() (contended)")
+ ->Threads(1)
+ ->Threads(2)
+ ->Threads(4)
+ ->Threads(8);
+
+BENCHMARK_MAIN();
diff --git a/libcxx/test/libcxx/thread/saferecl/saferecl.hp/abi.compile.pass.cpp b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/abi.compile.pass.cpp
new file mode 100644
index 0000000000000..b7724891a990e
--- /dev/null
+++ b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/abi.compile.pass.cpp
@@ -0,0 +1,46 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// Ensure that we never change the size or alignment of the ABI-visible hazard pointer types.
+// hazard_pointer is one pointer to a slot; the per-object node is {next, reclaim}; the deleter of
+// hazard_pointer_obj_base uses [[no_unique_address]] so an empty deleter costs nothing.
+
+#include <hazard_pointer>
+#include <memory>
+
+#include "test_macros.h"
+
+struct Node : std::hazard_pointer_obj_base<Node> {};
+
+struct StatefulDeleter {
+ void* state;
+ void operator()(struct Big*) const noexcept {}
+};
+struct Big : std::hazard_pointer_obj_base<Big, StatefulDeleter> {};
+
+static_assert(sizeof(std::hazard_pointer) == sizeof(void*));
+static_assert(alignof(std::hazard_pointer) == alignof(void*));
+
+static_assert(sizeof(std::__hazard_pointer_slot) == sizeof(void*));
+static_assert(alignof(std::__hazard_pointer_slot) == alignof(void*));
+
+static_assert(sizeof(std::__hazard_pointer_obj_node) == 2 * sizeof(void*));
+static_assert(alignof(std::__hazard_pointer_obj_node) == alignof(void*));
+
+static_assert(sizeof(std::hazard_pointer_obj_base<Node>) == 2 * sizeof(void*));
+static_assert(alignof(std::hazard_pointer_obj_base<Node>) == alignof(void*));
+static_assert(sizeof(Node) == 2 * sizeof(void*));
+
+static_assert(sizeof(std::hazard_pointer_obj_base<Big, StatefulDeleter>) == 3 * sizeof(void*));
+static_assert(alignof(std::hazard_pointer_obj_base<Big, StatefulDeleter>) == alignof(void*));
diff --git a/libcxx/test/libcxx/thread/saferecl/saferecl.hp/assert.protect.pass.cpp b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/assert.protect.pass.cpp
new file mode 100644
index 0000000000000..1ee0faf26e20b
--- /dev/null
+++ b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/assert.protect.pass.cpp
@@ -0,0 +1,32 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// REQUIRES: has-unix-headers
+// REQUIRES: libcpp-hardening-mode={{extensive|debug}}
+// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
+
+// <hazard_pointer>
+// template<class T> T* protect(const atomic<T*>& src) noexcept; Preconditions: *this is not empty.
+
+#include <hazard_pointer>
+#include <atomic>
+
+#include "check_assertion.h"
+
+struct Node : std::hazard_pointer_obj_base<Node> {};
+
+int main(int, char**) {
+ std::hazard_pointer empty;
+ std::atomic<Node*> src{nullptr};
+ TEST_LIBCPP_ASSERT_FAILURE((void)empty.protect(src), "hazard_pointer::reset_protection(): hazard_pointer is empty");
+ return 0;
+}
diff --git a/libcxx/test/libcxx/thread/saferecl/saferecl.hp/assert.reset_protection.pass.cpp b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/assert.reset_protection.pass.cpp
new file mode 100644
index 0000000000000..69710f8da729e
--- /dev/null
+++ b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/assert.reset_protection.pass.cpp
@@ -0,0 +1,36 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// REQUIRES: has-unix-headers
+// REQUIRES: libcpp-hardening-mode={{extensive|debug}}
+// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
+
+// <hazard_pointer>
+// template<class T> void reset_protection(const T* ptr) noexcept;
+// void reset_protection(nullptr_t = nullptr) noexcept; Preconditions: *this is not empty.
+
+#include <hazard_pointer>
+
+#include "check_assertion.h"
+
+struct Node : std::hazard_pointer_obj_base<Node> {};
+
+int main(int, char**) {
+ std::hazard_pointer empty;
+ Node node;
+ TEST_LIBCPP_ASSERT_FAILURE(
+ empty.reset_protection(&node), "hazard_pointer::reset_protection(): hazard_pointer is empty");
+ TEST_LIBCPP_ASSERT_FAILURE(empty.reset_protection(), "hazard_pointer::reset_protection(): hazard_pointer is empty");
+ TEST_LIBCPP_ASSERT_FAILURE(
+ empty.reset_protection(nullptr), "hazard_pointer::reset_protection(): hazard_pointer is empty");
+ return 0;
+}
diff --git a/libcxx/test/libcxx/thread/saferecl/saferecl.hp/assert.retire.pass.cpp b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/assert.retire.pass.cpp
new file mode 100644
index 0000000000000..ed793994624a6
--- /dev/null
+++ b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/assert.retire.pass.cpp
@@ -0,0 +1,34 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// REQUIRES: has-unix-headers
+// REQUIRES: libcpp-hardening-mode={{extensive|debug}}
+// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
+
+// <hazard_pointer>
+// void retire(D d = D()) noexcept; Preconditions: *this is not retired.
+
+#include <hazard_pointer>
+
+#include "check_assertion.h"
+
+struct Noop {
+ void operator()(struct Node*) const noexcept {}
+};
+struct Node : std::hazard_pointer_obj_base<Node, Noop> {};
+
+int main(int, char**) {
+ Node node; // never reclaimed with a real deleter, so retiring it twice is observable and harmless here
+ node.retire();
+ TEST_LIBCPP_ASSERT_FAILURE(node.retire(), "hazard_pointer_obj_base::retire(): object has already been retired");
+ return 0;
+}
diff --git a/libcxx/test/libcxx/thread/saferecl/saferecl.hp/assert.try_protect.pass.cpp b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/assert.try_protect.pass.cpp
new file mode 100644
index 0000000000000..ef60061aa015d
--- /dev/null
+++ b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/assert.try_protect.pass.cpp
@@ -0,0 +1,34 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// REQUIRES: has-unix-headers
+// REQUIRES: libcpp-hardening-mode={{extensive|debug}}
+// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
+
+// <hazard_pointer>
+// template<class T> bool try_protect(T*& ptr, const atomic<T*>& src) noexcept; Preconditions: *this is not empty.
+
+#include <hazard_pointer>
+#include <atomic>
+
+#include "check_assertion.h"
+
+struct Node : std::hazard_pointer_obj_base<Node> {};
+
+int main(int, char**) {
+ std::hazard_pointer empty;
+ std::atomic<Node*> src{nullptr};
+ Node* p = nullptr;
+ TEST_LIBCPP_ASSERT_FAILURE(
+ (void)empty.try_protect(p, src), "hazard_pointer::reset_protection(): hazard_pointer is empty");
+ return 0;
+}
diff --git a/libcxx/test/libcxx/thread/saferecl/saferecl.hp/concurrent_retire.pass.cpp b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/concurrent_retire.pass.cpp
new file mode 100644
index 0000000000000..6f77867654e42
--- /dev/null
+++ b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/concurrent_retire.pass.cpp
@@ -0,0 +1,79 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// Many threads retiring concurrently, none of them protecting a retired object: reclamation passes race
+// with each other and with retire() on the shared retired-object count, and the accounting must stay
+// consistent. libc++ runs a pass inline in retire() once max(1000, 2 * <records>) objects are pending, so
+// once every thread has finished, the number of objects still pending is bounded by that threshold plus a
+// claim in flight per thread, and one more burst of retirements from a single thread drains them all.
+// Under ASan/TSan this is also a use-after-free / data-race detector for overlapping passes.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <cassert>
+#include <thread>
+#include <vector>
+
+#include "make_test_thread.h"
+#include "test_macros.h"
+
+// Namespace scope: deleters run on whichever thread's retire() happens to trigger the pass.
+std::atomic<int> deleted{0};
+struct Node : std::hazard_pointer_obj_base<Node> {
+ ~Node() { ++deleted; }
+};
+
+// Never retired: gives every pass some real protections to scan past.
+struct Live : std::hazard_pointer_obj_base<Live> {};
+
+int main(int, char**) {
+#if defined(TEST_IS_EXECUTED_IN_A_SLOW_ENVIRONMENT)
+ const int threads = 4, per_thread = 5000;
+#else
+ const int threads = 8, per_thread = 50000;
+#endif
+ constexpr int threshold = 1000; // records stay well below 500, so the threshold is the fixed floor
+
+ std::atomic<bool> go{false};
+ std::vector<std::thread> ts;
+ for (int t = 0; t < threads; ++t) {
+ ts.push_back(support::make_test_thread([&] {
+ Live live;
+ std::atomic<Live*> src{&live};
+ std::hazard_pointer hps[4];
+ for (std::hazard_pointer& h : hps) {
+ h = std::make_hazard_pointer();
+ (void)h.protect(src);
+ }
+ while (!go.load(std::memory_order_acquire)) {
+ }
+ for (int i = 0; i < per_thread; ++i)
+ (new Node)->retire();
+ }));
+ }
+ go.store(true, std::memory_order_release);
+ for (std::thread& t : ts)
+ t.join();
+
+ const int retired = threads * per_thread;
+ const int pending = retired - deleted.load();
+ assert(pending >= 0); // nothing reclaimed twice
+ assert(pending <= threshold * (threads + 1)); // bounded: no credit leaked out of the counter
+
+ // Everything retired above is unprotected; the first pass triggered here extracts and reclaims it all.
+ for (int i = 0; i < 3 * threshold; ++i)
+ (new Node)->retire();
+ assert(deleted.load() >= retired);
+ return 0;
+}
diff --git a/libcxx/test/libcxx/thread/saferecl/saferecl.hp/hazard_protectable.verify.cpp b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/hazard_protectable.verify.cpp
new file mode 100644
index 0000000000000..adb4d61b3fc2a
--- /dev/null
+++ b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/hazard_protectable.verify.cpp
@@ -0,0 +1,101 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// The Mandates of try_protect/reset_protection/retire: T must be hazard-protectable, i.e. a class type
+// with exactly one hazard_pointer_obj_base<T, D> base, public and non-virtual, and no other
+// hazard_pointer_obj_base<T2, D2> base. Also checks the D requirements static_assert.
+
+#include <hazard_pointer>
+#include <atomic>
+
+// Positive cases: no diagnostics.
+struct Good : std::hazard_pointer_obj_base<Good> {};
+static_assert(std::__hazard_protectable<Good>);
+struct GoodDeleter {
+ void operator()(struct GoodD*) const noexcept {}
+};
+struct GoodD : std::hazard_pointer_obj_base<GoodD, GoodDeleter> {};
+static_assert(std::__hazard_protectable<GoodD>);
+struct Other {};
+struct GoodMulti : Other, std::hazard_pointer_obj_base<GoodMulti> {}; // an unrelated base is fine
+static_assert(std::__hazard_protectable<GoodMulti>);
+
+// Negative cases for the concept.
+struct NoBase {};
+static_assert(!std::__hazard_protectable<NoBase>);
+struct PrivateBase : private std::hazard_pointer_obj_base<PrivateBase> {};
+static_assert(!std::__hazard_protectable<PrivateBase>);
+struct ProtectedBase : protected std::hazard_pointer_obj_base<ProtectedBase> {};
+static_assert(!std::__hazard_protectable<ProtectedBase>);
+struct VirtualBase : virtual std::hazard_pointer_obj_base<VirtualBase> {};
+static_assert(!std::__hazard_protectable<VirtualBase>);
+struct TwoBases : std::hazard_pointer_obj_base<TwoBases>, std::hazard_pointer_obj_base<Good> {};
+static_assert(!std::__hazard_protectable<TwoBases>);
+struct Base : std::hazard_pointer_obj_base<Base> {};
+struct Derived : Base {}; // Derived's obj_base base is hazard_pointer_obj_base<Base>, not <Derived>
+static_assert(!std::__hazard_protectable<Derived>);
+static_assert(!std::__hazard_protectable<const Good>); // strict reading of [saferecl.hp.general]/2
+static_assert(!std::__hazard_protectable<int>);
+
+void diagnostics() {
+ std::hazard_pointer h;
+ std::atomic<NoBase*> src{nullptr};
+ // The static_assert is the diagnostic under test; the follow-on errors from the body are expected too:
+ // try_protect() calls reset_protection(), whose own Mandate fires, and __node_of() has no viable
+ // derived-to-base conversion when T has no hazard_pointer_obj_base base at all.
+ (void)h.protect(src); // expected-error@*:* 2 {{static assertion failed}}
+ // expected-error@*:* {{no matching function for call to '__node_of'}}
+ std::atomic<Derived*> src2{nullptr};
+ Derived* p = nullptr;
+ // Derived converts to hazard_pointer_obj_base<Base, D>, so __node_of() itself is fine here.
+ (void)h.try_protect(p, src2); // expected-error@*:* 2 {{static assertion failed}}
+ h.reset_protection(static_cast<const int*>(nullptr)); // expected-error@*:* {{static assertion failed}}
+ // expected-error@*:* {{no matching function for call to '__node_of'}}
+
+ // protect() rejects a T that isn't itself hazard-protectable, even when T is merely const-qualified:
+ // const Good has no hazard_pointer_obj_base<const Good, D> base.
+ std::atomic<const Good*> csrc{nullptr};
+ (void)h.protect(csrc); // expected-error@*:* {{static assertion failed}}
+
+ // retire() is inherited unambiguously from Base; the instantiated specialization is
+ // hazard_pointer_obj_base<Base, D>, and Base -- unlike Derived -- IS hazard-protectable, so this is
+ // well-formed: no diagnostic.
+ Derived d;
+ d.retire();
+}
+
+// retire()'s Mandate fires when the hazard_pointer_obj_base<T, D> specialization itself names a T that
+// is not hazard-protectable (as opposed to the most-derived object being retired); the downcast in the
+// reclamation function then fails as well.
+struct Mismatched : std::hazard_pointer_obj_base<NoBase> {};
+void bad_retire() {
+ Mismatched m;
+ m.retire(); // expected-error@*:* {{static assertion failed}}
+ // expected-error@*:* {{not related by inheritance}}
+}
+
+// D requirements.
+struct NotDefaultConstructible {
+ NotDefaultConstructible(int);
+ void operator()(struct BadD*) const noexcept {}
+};
+struct BadD : std::hazard_pointer_obj_base<BadD, NotDefaultConstructible> {
+}; // expected-error@*:* {{static assertion failed}}
+struct NotMoveAssignable {
+ NotMoveAssignable() = default;
+ NotMoveAssignable& operator=(NotMoveAssignable&&) = delete;
+ void operator()(struct BadMoveD*) const noexcept {}
+};
+struct BadMoveD : std::hazard_pointer_obj_base<BadMoveD, NotMoveAssignable> {
+}; // expected-error@*:* {{static assertion failed}}
diff --git a/libcxx/test/libcxx/thread/saferecl/saferecl.hp/no_specializations.verify.cpp b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/no_specializations.verify.cpp
new file mode 100644
index 0000000000000..551dd99289ce2
--- /dev/null
+++ b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/no_specializations.verify.cpp
@@ -0,0 +1,29 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+
+// <hazard_pointer>
+
+// Check that user-specializations of hazard_pointer_obj_base are diagnosed. A program-defined
+// specialization cannot meet the requirements of the original template ([namespace.std]/2): retire()
+// must hand the object to the library's hazard pointer domain.
+
+#include <hazard_pointer>
+
+#include "test_macros.h"
+
+#if !__has_warning("-Winvalid-specialization")
+// expected-no-diagnostics
+#else
+struct S {};
+
+template <>
+class std::hazard_pointer_obj_base<S>; // expected-error {{cannot be specialized}}
+#endif
diff --git a/libcxx/test/libcxx/thread/saferecl/saferecl.hp/nodiscard.verify.cpp b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/nodiscard.verify.cpp
new file mode 100644
index 0000000000000..c0fea4e89601f
--- /dev/null
+++ b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/nodiscard.verify.cpp
@@ -0,0 +1,31 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// [[nodiscard]] is applied as a libc++ extension to hazard_pointer::empty(), protect(), try_protect() and
+// make_hazard_pointer(): discarding these results is almost certainly a bug.
+
+#include <hazard_pointer>
+#include <atomic>
+
+struct Node : std::hazard_pointer_obj_base<Node> {};
+
+void test() {
+ std::hazard_pointer h;
+ std::atomic<Node*> src{nullptr};
+ Node* p = nullptr;
+ h.empty(); // expected-warning {{ignoring return value of function}}
+ h.protect(src); // expected-warning {{ignoring return value of function}}
+ h.try_protect(p, src); // expected-warning {{ignoring return value of function}}
+ std::make_hazard_pointer(); // expected-warning {{ignoring return value of function}}
+}
diff --git a/libcxx/test/libcxx/thread/saferecl/saferecl.hp/reclamation.pass.cpp b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/reclamation.pass.cpp
new file mode 100644
index 0000000000000..e0a4b8d435c40
--- /dev/null
+++ b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/reclamation.pass.cpp
@@ -0,0 +1,130 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// Implementation-specific reclamation behaviour: libc++ runs a reclamation pass inline in retire()
+// once max(1000, 2 * <number of hazard pointer records>) objects have been retired since the previous
+// pass, so retiring a few thousand objects from a thread with a handful of hazard pointers guarantees
+// passes. Numbers below leave generous slack so tuning the constants does not break the test.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <cassert>
+#include <vector>
+
+#include "test_macros.h"
+
+// Namespace-scope counters: a deleter may run well after the block that retired the object has
+// exited (the engine leaves up to `threshold` retirements pending at any time), so no deleter may
+// write through a pointer to an automatic-storage-duration object.
+std::atomic<int> dummies_deleted{0};
+struct Dummy : std::hazard_pointer_obj_base<Dummy> {
+ ~Dummy() { ++dummies_deleted; }
+};
+
+std::atomic<bool> node_deleted{false};
+struct Node : std::hazard_pointer_obj_base<Node> {
+ ~Node() { node_deleted.store(true); }
+};
+
+// Retire enough objects to guarantee at least two passes.
+void force_passes() {
+ for (int i = 0; i < 5000; ++i)
+ (new Dummy)->retire();
+}
+
+// A deleter that itself retires another object and uses hazard pointers (re-entrancy).
+std::atomic<int> nested_deleted{0};
+struct Nested : std::hazard_pointer_obj_base<Nested> {
+ int depth;
+ explicit Nested(int d) : depth(d) {}
+ ~Nested() {
+ ++nested_deleted;
+ if (depth > 0)
+ (new Nested(depth - 1))->retire();
+ std::hazard_pointer h = std::make_hazard_pointer(); // acquire/release inside a deleter
+ std::atomic<Nested*> src{nullptr};
+ (void)h.protect(src);
+ }
+};
+
+struct Noop {
+ void operator()(struct StackObj*) const noexcept {}
+};
+struct StackObj : std::hazard_pointer_obj_base<StackObj, Noop> {};
+
+int main(int, char**) {
+ {
+ // Unprotected objects get reclaimed: after 5000 retirements at most ~1000 (+ slack) are still pending.
+ int before = dummies_deleted.load();
+ force_passes();
+ assert(dummies_deleted.load() - before >= 3000);
+ }
+ {
+ // A protected object survives passes and is reclaimed once unprotected and another pass runs.
+ node_deleted = false;
+ Node* n = new Node();
+ std::hazard_pointer h = std::make_hazard_pointer();
+ h.reset_protection(n);
+ n->retire();
+ force_passes();
+ assert(!node_deleted.load());
+ h.reset_protection();
+ force_passes();
+ assert(node_deleted.load());
+ }
+ {
+ // ~hazard_pointer() ends the epoch as well.
+ node_deleted = false;
+ Node* n = new Node();
+ {
+ std::hazard_pointer h = std::make_hazard_pointer();
+ h.reset_protection(n);
+ n->retire();
+ force_passes();
+ assert(!node_deleted.load());
+ }
+ force_passes();
+ assert(node_deleted.load());
+ }
+ {
+ // Move-assignment over a nonempty hazard_pointer ends its epoch.
+ node_deleted = false;
+ Node* n = new Node();
+ std::hazard_pointer h = std::make_hazard_pointer();
+ h.reset_protection(n);
+ n->retire();
+ h = std::hazard_pointer(); // releases the associated hazard pointer
+ force_passes();
+ assert(node_deleted.load());
+ }
+ {
+ // Deleters may retire and use hazard pointers (reclamation is re-entrant).
+ (new Nested(3))->retire();
+ force_passes();
+ force_passes();
+ assert(nested_deleted.load() >= 1);
+ // Drain: everything retired so far is unprotected, so a couple more passes reclaim the chain.
+ force_passes();
+ force_passes();
+ assert(nested_deleted.load() == 4);
+ }
+ {
+ // Objects with automatic storage and a no-op deleter: retire, force a pass, then they may go away.
+ std::vector<StackObj> objs(100);
+ for (StackObj& o : objs)
+ o.retire();
+ force_passes(); // reclaims (no-op deleter) and unlinks them: destroying `objs` is now safe
+ }
+ return 0;
+}
diff --git a/libcxx/test/libcxx/thread/saferecl/saferecl.hp/static_destruction.pass.cpp b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/static_destruction.pass.cpp
new file mode 100644
index 0000000000000..030cf1f61f00c
--- /dev/null
+++ b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/static_destruction.pass.cpp
@@ -0,0 +1,43 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// hazard_pointer objects with static storage duration are destroyed during program termination, and
+// objects may be retired from destructors of static objects: the domain must outlive them (it is never
+// destroyed).
+
+#include <hazard_pointer>
+#include <atomic>
+#include <cassert>
+
+#include "test_macros.h"
+
+struct Node : std::hazard_pointer_obj_base<Node> {};
+
+std::hazard_pointer global_hp; // released during static destruction
+struct RetiresAtExit {
+ Node* node = new Node;
+ ~RetiresAtExit() {
+ node->retire(); // retire during static destruction
+ std::hazard_pointer late = std::make_hazard_pointer(); // and acquire/release
+ assert(!late.empty());
+ }
+} retires_at_exit;
+
+int main(int, char**) {
+ global_hp = std::make_hazard_pointer();
+ Node n;
+ global_hp.reset_protection(&n);
+ global_hp.reset_protection();
+ return 0;
+}
diff --git a/libcxx/test/libcxx/thread/saferecl/saferecl.hp/thread_exit.pass.cpp b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/thread_exit.pass.cpp
new file mode 100644
index 0000000000000..ad8293828641a
--- /dev/null
+++ b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/thread_exit.pass.cpp
@@ -0,0 +1,76 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// The per-thread record cache is returned to the domain when a thread exits. A hazard_pointer owned by a
+// thread_local is destroyed during thread teardown, before or after that cache is gone depending on the
+// platform's ordering of thread_local destructors vs. TSD key destructors (on glibc: before); both orders
+// must be crash-free. The "cache already gone" path is exercised deterministically by
+// thread_exit_late_release.pass.cpp. Also exercises many threads acquiring/releasing to churn the caches
+// and the available list. Nothing here is timing dependent; ASan/TSan make it a real detector.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <cassert>
+#include <thread>
+#include <vector>
+
+#include "make_test_thread.h"
+#include "test_macros.h"
+
+struct Node : std::hazard_pointer_obj_base<Node> {};
+
+// A thread_local whose destructor runs during thread teardown and destroys a hazard_pointer then.
+struct LateHolder {
+ std::hazard_pointer hp;
+ ~LateHolder() {
+ // Acquire another one during teardown too: acquire must also work when the cache is dead/absent.
+ std::hazard_pointer late = std::make_hazard_pointer();
+ assert(!late.empty());
+ Node n;
+ late.reset_protection(&n);
+ late.reset_protection();
+ }
+};
+
+void thread_body(int rounds) {
+ static thread_local LateHolder late;
+ late.hp = std::make_hazard_pointer(); // hazard_pointer released after the cache's own destructor
+ for (int i = 0; i < rounds; ++i) {
+ std::hazard_pointer hps[20]; // more than the cache capacity (9): overflow into the domain
+ for (std::hazard_pointer& h : hps)
+ h = std::make_hazard_pointer();
+ for (std::hazard_pointer& h : hps)
+ assert(!h.empty());
+ }
+}
+
+int main(int, char**) {
+#if defined(TEST_IS_EXECUTED_IN_A_SLOW_ENVIRONMENT)
+ const int threads = 8, rounds = 200;
+#else
+ const int threads = 32, rounds = 2000;
+#endif
+ for (int wave = 0; wave < 3; ++wave) {
+ std::vector<std::thread> ts;
+ for (int i = 0; i < threads; ++i)
+ ts.push_back(support::make_test_thread(thread_body, rounds));
+ for (std::thread& t : ts)
+ t.join();
+ }
+ // Records left over from exited threads are reusable by the main thread.
+ std::vector<std::hazard_pointer> hps;
+ for (int i = 0; i < 1000; ++i)
+ hps.push_back(std::make_hazard_pointer());
+ return 0;
+}
diff --git a/libcxx/test/libcxx/thread/saferecl/saferecl.hp/thread_exit_late_release.pass.cpp b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/thread_exit_late_release.pass.cpp
new file mode 100644
index 0000000000000..a654d60141589
--- /dev/null
+++ b/libcxx/test/libcxx/thread/saferecl/saferecl.hp/thread_exit_late_release.pass.cpp
@@ -0,0 +1,59 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+// REQUIRES: has-unix-headers
+
+// <hazard_pointer>
+
+// A pthread key destructor that destroys (and creates) hazard pointers: this may run after libc++'s own
+// per-thread record cache has been torn down, exercising the path where release/acquire bypass the cache.
+
+#include <hazard_pointer>
+#include <cassert>
+#include <pthread.h>
+#include <thread>
+
+#include "make_test_thread.h"
+#include "test_macros.h"
+
+struct Box {
+ std::hazard_pointer hp;
+};
+
+pthread_key_t key;
+
+void destroy_box(void* p) {
+ Box* box = static_cast<Box*>(p);
+ std::hazard_pointer late = std::make_hazard_pointer(); // acquire after the cache may be gone
+ assert(!late.empty());
+ delete box; // release after the cache may be gone
+}
+
+int main(int, char**) {
+ {
+ std::hazard_pointer first = std::make_hazard_pointer();
+ } // create libc++'s key before ours
+ int created = pthread_key_create(&key, &destroy_box);
+ assert(created == 0);
+ for (int i = 0; i < 64; ++i) {
+ std::thread t = support::make_test_thread([] {
+ Box* box = new Box;
+ box->hp = std::make_hazard_pointer();
+ int set = pthread_setspecific(key, box);
+ assert(set == 0);
+ std::hazard_pointer cached[12]; // fill the cache so eviction has work to do
+ for (std::hazard_pointer& h : cached)
+ h = std::make_hazard_pointer();
+ });
+ t.join();
+ }
+ return 0;
+}
diff --git a/libcxx/test/libcxx/transitive_includes.gen.py b/libcxx/test/libcxx/transitive_includes.gen.py
index 20d5e981ccba4..1b542a7e6c02e 100644
--- a/libcxx/test/libcxx/transitive_includes.gen.py
+++ b/libcxx/test/libcxx/transitive_includes.gen.py
@@ -39,6 +39,7 @@
"experimental/utility": "// UNSUPPORTED: c++03",
"filesystem": "// UNSUPPORTED: no-filesystem, c++03, c++11, c++14",
"future": "// UNSUPPORTED: no-threads, c++03",
+ "hazard_pointer": "// UNSUPPORTED: no-threads, c++03, c++11, c++14, c++17, c++20, c++23",
"latch": "// UNSUPPORTED: no-threads, c++03, c++11, c++14, c++17",
"mutex": "// UNSUPPORTED: no-threads, c++03",
"print": "// UNSUPPORTED: no-filesystem, c++03, c++11, c++14, c++17, c++20, availability-fp_to_chars-missing", # TODO PRINT investigate
diff --git a/libcxx/test/libcxx/transitive_includes/cxx03.csv b/libcxx/test/libcxx/transitive_includes/cxx03.csv
index ad2f90062c078..1ed35ba3b1ce1 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx03.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx03.csv
@@ -316,6 +316,7 @@ future string_view
future tuple
future typeinfo
future version
+hazard_pointer version
initializer_list version
iomanip cctype
iomanip cerrno
diff --git a/libcxx/test/libcxx/transitive_includes/cxx11.csv b/libcxx/test/libcxx/transitive_includes/cxx11.csv
index 672df0a488c9e..1e9ecec1bbfe7 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx11.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx11.csv
@@ -316,6 +316,7 @@ future string_view
future tuple
future typeinfo
future version
+hazard_pointer version
initializer_list version
iomanip cctype
iomanip cerrno
diff --git a/libcxx/test/libcxx/transitive_includes/cxx14.csv b/libcxx/test/libcxx/transitive_includes/cxx14.csv
index f496a00270386..5f21d8fc7f6f8 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx14.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx14.csv
@@ -320,6 +320,7 @@ future string_view
future tuple
future typeinfo
future version
+hazard_pointer version
initializer_list version
iomanip cctype
iomanip cerrno
diff --git a/libcxx/test/libcxx/transitive_includes/cxx17.csv b/libcxx/test/libcxx/transitive_includes/cxx17.csv
index e0a92259202c4..f44f72425ad2f 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx17.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx17.csv
@@ -361,6 +361,7 @@ future string_view
future tuple
future typeinfo
future version
+hazard_pointer version
initializer_list version
iomanip cctype
iomanip cerrno
diff --git a/libcxx/test/libcxx/transitive_includes/cxx20.csv b/libcxx/test/libcxx/transitive_includes/cxx20.csv
index 44b5fe1f1577f..98f87bc0690df 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx20.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv
@@ -425,6 +425,7 @@ future string_view
future tuple
future typeinfo
future version
+hazard_pointer version
initializer_list version
iomanip cctype
iomanip cerrno
diff --git a/libcxx/test/libcxx/transitive_includes/cxx23.csv b/libcxx/test/libcxx/transitive_includes/cxx23.csv
index a199c610e9f57..34b49674c7107 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx23.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv
@@ -444,6 +444,7 @@ future string_view
future tuple
future typeinfo
future version
+hazard_pointer version
initializer_list version
iomanip cctype
iomanip cerrno
diff --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv
index bd9227ca685d0..0e14db9de19aa 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx26.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv
@@ -439,6 +439,13 @@ future string_view
future tuple
future typeinfo
future version
+hazard_pointer climits
+hazard_pointer cstdint
+hazard_pointer cstring
+hazard_pointer ctime
+hazard_pointer limits
+hazard_pointer ratio
+hazard_pointer version
initializer_list version
iomanip cctype
iomanip cerrno
diff --git a/libcxx/test/libcxx/transitive_includes/cxx29.csv b/libcxx/test/libcxx/transitive_includes/cxx29.csv
index bd9227ca685d0..0e14db9de19aa 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx29.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx29.csv
@@ -439,6 +439,13 @@ future string_view
future tuple
future typeinfo
future version
+hazard_pointer climits
+hazard_pointer cstdint
+hazard_pointer cstring
+hazard_pointer ctime
+hazard_pointer limits
+hazard_pointer ratio
+hazard_pointer version
initializer_list version
iomanip cctype
iomanip cerrno
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/hazard_pointer.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/hazard_pointer.version.compile.pass.cpp
new file mode 100644
index 0000000000000..7a3f9fd2227a7
--- /dev/null
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/hazard_pointer.version.compile.pass.cpp
@@ -0,0 +1,85 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// WARNING: This test was generated by generate_feature_test_macro_components.py
+// and should not be edited manually.
+
+// UNSUPPORTED: no-threads
+
+// <hazard_pointer>
+
+// Test the feature test macros defined by <hazard_pointer>
+
+// clang-format off
+
+#include <hazard_pointer>
+#include "test_macros.h"
+
+#if TEST_STD_VER < 14
+
+# ifdef __cpp_lib_hazard_pointer
+# error "__cpp_lib_hazard_pointer should not be defined before c++26"
+# endif
+
+#elif TEST_STD_VER == 14
+
+# ifdef __cpp_lib_hazard_pointer
+# error "__cpp_lib_hazard_pointer should not be defined before c++26"
+# endif
+
+#elif TEST_STD_VER == 17
+
+# ifdef __cpp_lib_hazard_pointer
+# error "__cpp_lib_hazard_pointer should not be defined before c++26"
+# endif
+
+#elif TEST_STD_VER == 20
+
+# ifdef __cpp_lib_hazard_pointer
+# error "__cpp_lib_hazard_pointer should not be defined before c++26"
+# endif
+
+#elif TEST_STD_VER == 23
+
+# ifdef __cpp_lib_hazard_pointer
+# error "__cpp_lib_hazard_pointer should not be defined before c++26"
+# endif
+
+#elif TEST_STD_VER == 26
+
+# if !defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_HAZARD_POINTER)
+# ifndef __cpp_lib_hazard_pointer
+# error "__cpp_lib_hazard_pointer should be defined in c++26"
+# endif
+# if __cpp_lib_hazard_pointer != 202306L
+# error "__cpp_lib_hazard_pointer should have the value 202306L in c++26"
+# endif
+# else
+# ifdef __cpp_lib_hazard_pointer
+# error "__cpp_lib_hazard_pointer should not be defined when the requirement '!defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_HAZARD_POINTER)' is not met!"
+# endif
+# endif
+
+#elif TEST_STD_VER > 26
+
+# if !defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_HAZARD_POINTER)
+# ifndef __cpp_lib_hazard_pointer
+# error "__cpp_lib_hazard_pointer should be defined in c++29"
+# endif
+# if __cpp_lib_hazard_pointer != 202306L
+# error "__cpp_lib_hazard_pointer should have the value 202306L in c++29"
+# endif
+# else
+# ifdef __cpp_lib_hazard_pointer
+# error "__cpp_lib_hazard_pointer should not be defined when the requirement '!defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_HAZARD_POINTER)' is not met!"
+# endif
+# endif
+
+#endif // TEST_STD_VER > 26
+
+// clang-format on
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
index b7ac29dec3a22..7f22abf5c924c 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -7271,7 +7271,7 @@
# error "__cpp_lib_has_unique_object_representations should have the value 201606L in c++26"
# endif
-# if !defined(_LIBCPP_VERSION)
+# if !defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_HAZARD_POINTER)
# ifndef __cpp_lib_hazard_pointer
# error "__cpp_lib_hazard_pointer should be defined in c++26"
# endif
@@ -7280,7 +7280,7 @@
# endif
# else
# ifdef __cpp_lib_hazard_pointer
-# error "__cpp_lib_hazard_pointer should not be defined because it is unimplemented in libc++!"
+# error "__cpp_lib_hazard_pointer should not be defined when the requirement '!defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_HAZARD_POINTER)' is not met!"
# endif
# endif
@@ -9294,7 +9294,7 @@
# error "__cpp_lib_has_unique_object_representations should have the value 201606L in c++29"
# endif
-# if !defined(_LIBCPP_VERSION)
+# if !defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_HAZARD_POINTER)
# ifndef __cpp_lib_hazard_pointer
# error "__cpp_lib_hazard_pointer should be defined in c++29"
# endif
@@ -9303,7 +9303,7 @@
# endif
# else
# ifdef __cpp_lib_hazard_pointer
-# error "__cpp_lib_hazard_pointer should not be defined because it is unimplemented in libc++!"
+# error "__cpp_lib_hazard_pointer should not be defined when the requirement '!defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_HAZARD_POINTER)' is not met!"
# endif
# endif
diff --git a/libcxx/test/std/thread/saferecl/saferecl.hp/protection.pass.cpp b/libcxx/test/std/thread/saferecl/saferecl.hp/protection.pass.cpp
new file mode 100644
index 0000000000000..815236aa8a3c1
--- /dev/null
+++ b/libcxx/test/std/thread/saferecl/saferecl.hp/protection.pass.cpp
@@ -0,0 +1,103 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// [saferecl.hp.general]: an object retired while some hazard pointer is associated with it is not
+// reclaimed until that protection epoch ends; the number of possibly-reclaimable objects is bounded.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <cassert>
+
+#include "test_macros.h"
+
+// Namespace-scope counters: a deleter may run well after the block that retired the object has
+// exited (the engine leaves up to `threshold` retirements pending at any time), so no deleter may
+// write through a pointer to an automatic-storage-duration object.
+std::atomic<int> dummies_deleted{0};
+struct Dummy : std::hazard_pointer_obj_base<Dummy> {
+ ~Dummy() { ++dummies_deleted; }
+};
+
+// One flag per block, and each Node reports to the flag it was handed: an object retired by an earlier
+// block may still be reclaimed during a later one (when that happens is unspecified), so a single shared
+// flag would make one block's assertions depend on another block's reclamation timing.
+std::atomic<bool> node_deleted_1{false};
+std::atomic<bool> node_deleted_2{false};
+std::atomic<bool> node_deleted_3{false};
+
+struct Node : std::hazard_pointer_obj_base<Node> {
+ explicit Node(std::atomic<bool>& deleted) : deleted_(&deleted) {}
+ std::atomic<bool>* deleted_;
+ int value = 42;
+ ~Node() { deleted_->store(true); }
+};
+
+#if defined(TEST_IS_EXECUTED_IN_A_SLOW_ENVIRONMENT)
+constexpr int N = 5000;
+#else
+constexpr int N = 100000;
+#endif
+
+void retire_dummies() {
+ for (int i = 0; i < N; ++i)
+ (new Dummy)->retire();
+}
+
+int main(int, char**) {
+ {
+ // Protected via protect(): survives reclamation passes; readable throughout.
+ int before = dummies_deleted.load();
+ Node* n = new Node(node_deleted_1);
+ std::atomic<Node*> src{n};
+ std::hazard_pointer h = std::make_hazard_pointer();
+ Node* p = h.protect(src);
+ assert(p == n);
+ src.store(nullptr);
+ n->retire();
+ retire_dummies();
+ assert(dummies_deleted.load() - before > 0); // reclamation did happen ...
+ assert(!node_deleted_1.load()); // ... but not of the protected object
+ assert(p->value == 42);
+ h.reset_protection(); // epoch ends; n becomes possibly-reclaimable
+ retire_dummies();
+ // Whether n has been reclaimed by now is unspecified (see libcxx/.../reclamation.pass.cpp).
+ }
+ {
+ // Protected via reset_protection(ptr) then released by ~hazard_pointer(): same guarantee.
+ Node* n = new Node(node_deleted_2);
+ {
+ std::hazard_pointer h = std::make_hazard_pointer();
+ h.reset_protection(n);
+ n->retire();
+ retire_dummies();
+ assert(!node_deleted_2.load());
+ assert(n->value == 42);
+ } // h destroyed: epoch ends
+ retire_dummies();
+ }
+ {
+ // Two hazard pointers on the same object: it stays protected until both epochs end.
+ Node* n = new Node(node_deleted_3);
+ std::hazard_pointer h1 = std::make_hazard_pointer(), h2 = std::make_hazard_pointer();
+ h1.reset_protection(n);
+ h2.reset_protection(n);
+ n->retire();
+ h1.reset_protection();
+ retire_dummies();
+ assert(!node_deleted_3.load()); // still protected by h2
+ h2.reset_protection();
+ retire_dummies();
+ }
+ return 0;
+}
diff --git a/libcxx/test/std/thread/saferecl/saferecl.hp/readers_writers.pass.cpp b/libcxx/test/std/thread/saferecl/saferecl.hp/readers_writers.pass.cpp
new file mode 100644
index 0000000000000..4a13bc1808b49
--- /dev/null
+++ b/libcxx/test/std/thread/saferecl/saferecl.hp/readers_writers.pass.cpp
@@ -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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// Concurrent readers protecting a published object while writers replace and retire it. Every reader
+// checks an invariant of the payload; under ASan/TSan this test also acts as a use-after-free / data-race
+// detector for the implementation. No timing assumptions.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <cassert>
+#include <cstdint>
+#include <thread>
+#include <vector>
+
+#include "make_test_thread.h"
+#include "test_macros.h"
+
+struct Payload : std::hazard_pointer_obj_base<Payload> {
+ std::uint64_t a;
+ std::uint64_t b; // invariant: b == ~a while the object is alive
+ explicit Payload(std::uint64_t v) : a(v), b(~v) {}
+ ~Payload() {
+ a = 0xDEADBEEF;
+ b = 0xDEADBEEF; // break the invariant on destruction
+ }
+};
+
+#if defined(TEST_IS_EXECUTED_IN_A_SLOW_ENVIRONMENT)
+constexpr int kReads = 20000;
+constexpr int kWrites = 2000;
+#else
+constexpr int kReads = 200000;
+constexpr int kWrites = 20000;
+#endif
+constexpr int kReaders = 4;
+constexpr int kWriters = 2;
+
+int main(int, char**) {
+ std::atomic<Payload*> current{new Payload(1)};
+ std::atomic<bool> go{false};
+
+ std::vector<std::thread> threads;
+ for (int r = 0; r < kReaders; ++r) {
+ threads.push_back(support::make_test_thread([&] {
+ while (!go.load(std::memory_order_acquire)) {
+ }
+ for (int i = 0; i < kReads; ++i) {
+ std::hazard_pointer h = std::make_hazard_pointer();
+ Payload* p = h.protect(current);
+ if (p != nullptr) {
+ std::uint64_t a = p->a;
+ std::uint64_t b = p->b;
+ assert(b == ~a);
+ }
+ }
+ }));
+ }
+ for (int w = 0; w < kWriters; ++w) {
+ threads.push_back(support::make_test_thread([&, w] {
+ while (!go.load(std::memory_order_acquire)) {
+ }
+ for (int i = 0; i < kWrites; ++i) {
+ Payload* fresh = new Payload(static_cast<std::uint64_t>(w) * kWrites + i + 2);
+ Payload* old = current.exchange(fresh, std::memory_order_acq_rel);
+ if (old != nullptr)
+ old->retire();
+ }
+ }));
+ }
+ go.store(true, std::memory_order_release);
+ for (std::thread& t : threads)
+ t.join();
+ Payload* last = current.exchange(nullptr);
+ if (last != nullptr)
+ last->retire();
+ return 0;
+}
diff --git a/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.base/incomplete_type.compile.pass.cpp b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.base/incomplete_type.compile.pass.cpp
new file mode 100644
index 0000000000000..6118e577d17fa
--- /dev/null
+++ b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.base/incomplete_type.compile.pass.cpp
@@ -0,0 +1,39 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// [saferecl.hp.base]/4: T may be an incomplete type. It shall be complete before any member of the
+// resulting specialization of hazard_pointer_obj_base is referenced.
+
+#include <hazard_pointer>
+
+#include "test_macros.h"
+
+struct Incomplete;
+std::hazard_pointer_obj_base<Incomplete>* declared_only(); // the specialization is named while T is incomplete
+
+struct Node : std::hazard_pointer_obj_base<Node> { // T is incomplete while the base is instantiated
+ Node* next = nullptr;
+};
+
+struct Deleter;
+struct WithForwardDeclaredT;
+struct Deleter {
+ void operator()(WithForwardDeclaredT*) const noexcept;
+};
+struct WithForwardDeclaredT : std::hazard_pointer_obj_base<WithForwardDeclaredT, Deleter> {};
+
+void use() {
+ Node n;
+ n.retire(); // T is complete here
+}
diff --git a/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.base/nonzero_offset_base.pass.cpp b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.base/nonzero_offset_base.pass.cpp
new file mode 100644
index 0000000000000..8ea32d8408f8e
--- /dev/null
+++ b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.base/nonzero_offset_base.pass.cpp
@@ -0,0 +1,104 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// The hazard_pointer_obj_base<T, D> subobject need not sit at offset 0 of T: here a polymorphic base
+// comes first. Protection, retirement and the deleter's argument must all still be expressed in terms of
+// the T*, not of the base subobject, so a protected object survives reclamation passes and the deleter
+// is eventually called with the same pointer retire() was called on.
+//
+// When an object is reclaimed is unspecified; like retire.pass.cpp this test only relies on the number
+// of possibly-reclaimable objects being bounded, so retiring many objects makes *some* reclamation
+// happen.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <cassert>
+#include <cstdint>
+
+#include "test_macros.h"
+
+#if defined(TEST_IS_EXECUTED_IN_A_SLOW_ENVIRONMENT)
+constexpr int N = 5000;
+#else
+constexpr int N = 100000; // comfortably above any sane reclamation bound
+#endif
+
+struct Prefix {
+ virtual ~Prefix() = default; // makes Node polymorphic: the obj_base base cannot be at offset 0
+ int tag = 11;
+};
+
+struct Node;
+struct NodeDeleter {
+ void operator()(Node* p) const noexcept;
+};
+
+struct Node : Prefix, std::hazard_pointer_obj_base<Node, NodeDeleter> {
+ Node* self_ = this;
+ int value = 42;
+ bool watched_ = false; // the one object the test protects reports where it went
+};
+
+// Namespace scope: a deleter may run long after the block that retired the object has exited.
+std::atomic<int> calls{0};
+std::atomic<int> mismatches{0};
+std::atomic<std::uintptr_t> watched_received{0};
+
+void NodeDeleter::operator()(Node* p) const noexcept {
+ ++calls;
+ if (p->self_ != p || p->value != 42 || p->tag != 11)
+ ++mismatches;
+ if (p->watched_)
+ watched_received.store(reinterpret_cast<std::uintptr_t>(p));
+ delete p;
+}
+
+void retire_many() {
+ for (int i = 0; i < N; ++i)
+ (new Node)->retire();
+}
+
+int main(int, char**) {
+ {
+ // The premise of the test: the base subobject really is at a non-zero offset.
+ Node probe;
+ const void* derived = &probe;
+ const void* base = static_cast<std::hazard_pointer_obj_base<Node, NodeDeleter>*>(&probe);
+ assert(derived != base);
+ }
+
+ Node* watched = new Node;
+ watched->watched_ = true;
+ const std::uintptr_t watched_addr = reinterpret_cast<std::uintptr_t>(watched);
+ {
+ std::atomic<Node*> src{watched};
+ std::hazard_pointer h = std::make_hazard_pointer();
+ Node* p = h.protect(src);
+ assert(p == watched);
+ src.store(nullptr);
+ watched->retire();
+ retire_many();
+ assert(calls.load() > 0); // reclamation did happen ...
+ assert(watched_received.load() == 0); // ... but not of the protected object
+ assert(watched->value == 42 && watched->tag == 11);
+ h.reset_protection(); // epoch ends: watched becomes possibly-reclaimable
+ }
+ retire_many();
+ // If it has been reclaimed by now -- unspecified, but the usual case -- the deleter got the T* that
+ // retire() was called on, not the address of the base subobject.
+ std::uintptr_t got = watched_received.load();
+ assert(got == 0 || got == watched_addr);
+ assert(mismatches.load() == 0);
+ return 0;
+}
diff --git a/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.base/retire.pass.cpp b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.base/retire.pass.cpp
new file mode 100644
index 0000000000000..c80d0b7bb2372
--- /dev/null
+++ b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.base/retire.pass.cpp
@@ -0,0 +1,115 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// void retire(D d = D()) noexcept;
+// Move-assigns d to the deleter, then retires *this. When the object is reclaimed its deleter is
+// invoked with a pointer to the object. When that happens is unspecified; this test only relies on
+// the fact that the number of possibly-reclaimable objects is bounded, so retiring a large number of
+// objects makes *some* reclamation happen.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <cassert>
+#include <memory>
+
+#include "test_macros.h"
+
+#if defined(TEST_IS_EXECUTED_IN_A_SLOW_ENVIRONMENT)
+constexpr int N = 5000;
+#else
+constexpr int N = 100000; // comfortably above any sane reclamation bound
+#endif
+
+// 1. The default deleter: default_delete<T>, i.e. `delete p`.
+std::atomic<int> default_deleted{0};
+struct Plain : std::hazard_pointer_obj_base<Plain> {
+ ~Plain() { ++default_deleted; }
+};
+
+// 2. A stateful deleter, move-assigned by retire(); it checks it receives the object it was given.
+struct Tally {
+ std::atomic<int> calls{0};
+ std::atomic<int> mismatches{0};
+};
+struct Recorded;
+struct RecordingDeleter {
+ Tally* tally = nullptr;
+ void operator()(Recorded* p) const noexcept;
+};
+struct Recorded : std::hazard_pointer_obj_base<Recorded, RecordingDeleter> {
+ Recorded* self = this;
+ int payload = 7;
+};
+void RecordingDeleter::operator()(Recorded* p) const noexcept {
+ ++tally->calls;
+ if (p->self != p || p->payload != 7)
+ ++tally->mismatches;
+ delete p;
+}
+
+// 3. retire(d) move-assigns d into the object's stored deleter: the deleter that eventually runs is the
+// move-assignment target, and it must have observed the assignment.
+struct MoveTracked;
+std::atomic<int> tracked_calls{0};
+std::atomic<int> tracked_not_move_assigned{0};
+struct MoveTrackingDeleter {
+ bool assigned_by_move = false;
+ MoveTrackingDeleter() = default;
+ MoveTrackingDeleter(MoveTrackingDeleter&&) = default;
+ MoveTrackingDeleter& operator=(MoveTrackingDeleter&&) noexcept {
+ assigned_by_move = true;
+ return *this;
+ }
+ void operator()(MoveTracked* p) const noexcept;
+};
+struct MoveTracked : std::hazard_pointer_obj_base<MoveTracked, MoveTrackingDeleter> {};
+void MoveTrackingDeleter::operator()(MoveTracked* p) const noexcept {
+ ++tracked_calls;
+ if (!assigned_by_move)
+ ++tracked_not_move_assigned;
+ delete p;
+}
+
+// 4. A function pointer is a valid function object type for D.
+struct FnObj;
+using FnDeleter = void (*)(FnObj*);
+struct FnObj : std::hazard_pointer_obj_base<FnObj, FnDeleter> {};
+std::atomic<int> fn_calls{0};
+void fn_delete(FnObj* p) {
+ ++fn_calls;
+ delete p;
+}
+
+int main(int, char**) {
+ for (int i = 0; i < N; ++i)
+ (new Plain)->retire();
+ assert(default_deleted.load() > 0);
+
+ Tally tally;
+ for (int i = 0; i < N; ++i)
+ (new Recorded)->retire(RecordingDeleter{&tally});
+ assert(tally.calls.load() > 0);
+ assert(tally.mismatches.load() == 0);
+
+ for (int i = 0; i < N; ++i)
+ (new MoveTracked)->retire(MoveTrackingDeleter{});
+ assert(tracked_calls.load() > 0);
+ assert(tracked_not_move_assigned.load() == 0);
+
+ for (int i = 0; i < N; ++i)
+ (new FnObj)->retire(&fn_delete);
+ assert(fn_calls.load() > 0);
+
+ return 0;
+}
diff --git a/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.base/special_members.pass.cpp b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.base/special_members.pass.cpp
new file mode 100644
index 0000000000000..f7b15e02e1b38
--- /dev/null
+++ b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.base/special_members.pass.cpp
@@ -0,0 +1,93 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// hazard_pointer_obj_base's protected special members are defaulted. A copy or move of an object --
+// even of one that has already been retired but is still protected -- is a fresh object that can itself
+// be retired; assignment never affects retirement state.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <cassert>
+#include <utility>
+
+#include "test_macros.h"
+
+#if defined(TEST_IS_EXECUTED_IN_A_SLOW_ENVIRONMENT)
+constexpr int N = 5000;
+#else
+constexpr int N = 100000; // comfortably above any sane reclamation bound
+#endif
+
+std::atomic<int> deleted{0};
+
+struct Node : std::hazard_pointer_obj_base<Node> {
+ int value;
+ explicit Node(int v) : value(v) {}
+ Node(const Node&) = default;
+ Node(Node&&) = default;
+ Node& operator=(const Node&) = default;
+ Node& operator=(Node&&) = default;
+ ~Node() { ++deleted; }
+};
+
+void force_reclamation() {
+ struct Dummy : std::hazard_pointer_obj_base<Dummy> {};
+ for (int i = 0; i < N; ++i)
+ (new Dummy)->retire();
+}
+
+int main(int, char**) {
+ {
+ // Copy of a live object, then retire both.
+ Node* a = new Node(1);
+ Node* b = new Node(*a);
+ assert(b->value == 1);
+ a->retire();
+ b->retire();
+ }
+ {
+ // Copy and move of a retired-but-protected object.
+ Node* a = new Node(2);
+ std::hazard_pointer h = std::make_hazard_pointer();
+ h.reset_protection(a);
+ a->retire();
+ Node* copy = new Node(*a); // fresh, unretired
+ Node* moved = new Node(std::move(*a)); // fresh, unretired
+ assert(copy->value == 2 && moved->value == 2);
+ copy->retire();
+ moved->retire();
+ h.reset_protection();
+ }
+ {
+ // Assignment from a retired object into a live one leaves the target retirable.
+ Node* a = new Node(3);
+ Node* b = new Node(4);
+ std::hazard_pointer h = std::make_hazard_pointer();
+ h.reset_protection(a);
+ a->retire();
+ *b = *a;
+ assert(b->value == 3);
+ b->retire();
+ Node* c = new Node(5);
+ *c = std::move(*a);
+ assert(c->value == 3);
+ c->retire();
+ h.reset_protection();
+ }
+ int before = deleted.load();
+ force_reclamation();
+ assert(deleted.load() > before); // some of the objects above were reclaimed exactly once each; the
+ // total is checked by ASan (no double free) rather than by count.
+ return 0;
+}
diff --git a/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.base/types.compile.pass.cpp b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.base/types.compile.pass.cpp
new file mode 100644
index 0000000000000..c66ab7b07572b
--- /dev/null
+++ b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.base/types.compile.pass.cpp
@@ -0,0 +1,51 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// template<class T, class D = default_delete<T>> class hazard_pointer_obj_base;
+// void retire(D d = D()) noexcept;
+// protected: defaulted special members
+
+#include <hazard_pointer>
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+#include "test_macros.h"
+
+struct Node : std::hazard_pointer_obj_base<Node> {};
+
+struct Deleter {
+ void operator()(struct WithDeleter*) const noexcept {}
+};
+struct WithDeleter : std::hazard_pointer_obj_base<WithDeleter, Deleter> {};
+
+// The default deleter is default_delete<T>.
+static_assert(std::is_base_of_v<std::hazard_pointer_obj_base<Node, std::default_delete<Node>>, Node>);
+
+// retire is noexcept and takes D by value with a default argument.
+static_assert(noexcept(std::declval<Node&>().retire()));
+static_assert(noexcept(std::declval<Node&>().retire(std::default_delete<Node>())));
+static_assert(noexcept(std::declval<WithDeleter&>().retire(Deleter())));
+static_assert(std::is_same_v<decltype(std::declval<Node&>().retire()), void>);
+
+// The special members are protected: not usable from outside, but usable by the derived class.
+static_assert(!std::is_default_constructible_v<std::hazard_pointer_obj_base<Node>>);
+static_assert(!std::is_copy_constructible_v<std::hazard_pointer_obj_base<Node>>);
+static_assert(!std::is_destructible_v<std::hazard_pointer_obj_base<Node>>);
+static_assert(std::is_default_constructible_v<Node>);
+static_assert(std::is_copy_constructible_v<Node>);
+static_assert(std::is_move_constructible_v<Node>);
+static_assert(std::is_copy_assignable_v<Node>);
+static_assert(std::is_move_assignable_v<Node>);
+static_assert(std::is_destructible_v<Node>);
diff --git a/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/moved_between_threads.pass.cpp b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/moved_between_threads.pass.cpp
new file mode 100644
index 0000000000000..96a73730e8039
--- /dev/null
+++ b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/moved_between_threads.pass.cpp
@@ -0,0 +1,82 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// A hazard pointer is owned by an object, not by the thread that made it: a nonempty hazard_pointer may
+// be moved to another thread and used and destroyed there. The protection epoch is unaffected by the
+// move, so an object retired while the moved-to hazard pointer protects it is not reclaimed.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <cassert>
+#include <thread>
+#include <utility>
+
+#include "make_test_thread.h"
+#include "test_macros.h"
+
+#if defined(TEST_IS_EXECUTED_IN_A_SLOW_ENVIRONMENT)
+constexpr int N = 5000;
+#else
+constexpr int N = 100000;
+#endif
+
+// Namespace scope: a deleter may run long after the block that retired the object has exited.
+std::atomic<bool> node_deleted{false};
+
+struct Node : std::hazard_pointer_obj_base<Node> {
+ int value = 42;
+ ~Node() { node_deleted.store(true); }
+};
+
+struct Spare : std::hazard_pointer_obj_base<Spare> {
+ int value = 7;
+};
+
+struct Dummy : std::hazard_pointer_obj_base<Dummy> {};
+
+void retire_dummies() {
+ for (int i = 0; i < N; ++i)
+ (new Dummy)->retire();
+}
+
+int main(int, char**) {
+ Node* n = new Node;
+ std::atomic<Node*> src{n};
+
+ Spare spare;
+ std::atomic<Spare*> spare_src{&spare};
+
+ std::hazard_pointer h = std::make_hazard_pointer();
+ assert(h.protect(src) == n);
+ src.store(nullptr);
+ n->retire(); // retired while protected by a hazard pointer that is about to change threads
+
+ std::thread t = support::make_test_thread([moved = std::move(h), &spare_src, &spare, n]() mutable {
+ // The epoch survived the move: n is still protected here, on a thread that never acquired it.
+ assert(!moved.empty());
+ retire_dummies();
+ assert(!node_deleted.load());
+ assert(n->value == 42);
+
+ // The moved-to hazard pointer is fully usable on this thread.
+ assert(moved.protect(spare_src) == &spare); // ends n's epoch and starts one for spare
+ assert(spare.value == 7);
+ moved.reset_protection();
+ }); // ~hazard_pointer runs here: the record is released by a thread that did not acquire it
+ t.join();
+
+ assert(h.empty()); // moved from
+ retire_dummies(); // n is unprotected now; whether it has been reclaimed is unspecified
+ return 0;
+}
diff --git a/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.ctor/assign.move.pass.cpp b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.ctor/assign.move.pass.cpp
new file mode 100644
index 0000000000000..e09f469d211db
--- /dev/null
+++ b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.ctor/assign.move.pass.cpp
@@ -0,0 +1,79 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// hazard_pointer& operator=(hazard_pointer&& other) noexcept;
+// If this == &other: no effect. Otherwise, if *this is not empty, destroys the hazard pointer owned
+// by *this. Postconditions: *this owns other's hazard pointer (if any); other is empty.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <cassert>
+#include <utility>
+
+#include "test_macros.h"
+
+struct Node : std::hazard_pointer_obj_base<Node> {};
+
+int main(int, char**) {
+ {
+ // empty = empty
+ std::hazard_pointer a, b;
+ a = std::move(b);
+ assert(a.empty());
+ assert(b.empty());
+ }
+ {
+ // nonempty = empty
+ std::hazard_pointer a = std::make_hazard_pointer(), b;
+ a = std::move(b);
+ assert(a.empty());
+ assert(b.empty());
+ }
+ {
+ // empty = nonempty
+ std::hazard_pointer a, b = std::make_hazard_pointer();
+ a = std::move(b);
+ assert(!a.empty());
+ assert(b.empty());
+ }
+ {
+ // nonempty = nonempty
+ Node node;
+ std::atomic<Node*> src{&node};
+ std::hazard_pointer a = std::make_hazard_pointer(), b = std::make_hazard_pointer();
+ (void)a.protect(src);
+ a = std::move(b);
+ assert(!a.empty());
+ assert(b.empty());
+ assert(a.protect(src) == &node);
+ }
+ {
+ // self-move: no effect
+ std::hazard_pointer a = std::make_hazard_pointer();
+ std::hazard_pointer& ref = a;
+ a = std::move(ref);
+ assert(!a.empty());
+ std::hazard_pointer e;
+ std::hazard_pointer& eref = e;
+ e = std::move(eref);
+ assert(e.empty());
+ }
+ {
+ // returns *this
+ std::hazard_pointer a, b = std::make_hazard_pointer();
+ std::hazard_pointer& r = (a = std::move(b));
+ assert(&r == &a);
+ }
+ return 0;
+}
diff --git a/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.ctor/copy.delete.compile.pass.cpp b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.ctor/copy.delete.compile.pass.cpp
new file mode 100644
index 0000000000000..eb64f20866b6c
--- /dev/null
+++ b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.ctor/copy.delete.compile.pass.cpp
@@ -0,0 +1,24 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// hazard_pointer(const hazard_pointer&) = delete; (implicitly, since move members are declared)
+// hazard_pointer& operator=(const hazard_pointer&) = delete;
+
+#include <hazard_pointer>
+#include <type_traits>
+
+static_assert(!std::is_copy_constructible_v<std::hazard_pointer>);
+static_assert(!std::is_copy_assignable_v<std::hazard_pointer>);
+static_assert(std::is_nothrow_move_constructible_v<std::hazard_pointer>);
+static_assert(std::is_nothrow_move_assignable_v<std::hazard_pointer>);
diff --git a/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.ctor/default.pass.cpp b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.ctor/default.pass.cpp
new file mode 100644
index 0000000000000..0a1bdd61647b3
--- /dev/null
+++ b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.ctor/default.pass.cpp
@@ -0,0 +1,28 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// hazard_pointer() noexcept; Postconditions: *this is empty.
+
+#include <hazard_pointer>
+#include <cassert>
+
+#include "test_macros.h"
+
+int main(int, char**) {
+ std::hazard_pointer h;
+ assert(h.empty());
+ const std::hazard_pointer ch;
+ assert(ch.empty());
+ return 0;
+}
diff --git a/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.ctor/dtor.pass.cpp b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.ctor/dtor.pass.cpp
new file mode 100644
index 0000000000000..f53c267bce1ad
--- /dev/null
+++ b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.ctor/dtor.pass.cpp
@@ -0,0 +1,51 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// ~hazard_pointer();
+// If *this is not empty, destroys the hazard pointer owned by *this, thereby ending its current
+// protection epoch. (Reclaimability after the epoch ends is tested in ../../protection.pass.cpp.)
+
+#include <hazard_pointer>
+#include <atomic>
+#include <cassert>
+#include <utility>
+
+#include "test_macros.h"
+
+struct Node : std::hazard_pointer_obj_base<Node> {};
+
+int main(int, char**) {
+ {
+ std::hazard_pointer empty; // destroying an empty hazard_pointer is a no-op
+ }
+ {
+ Node node;
+ std::atomic<Node*> src{&node};
+ std::hazard_pointer h = std::make_hazard_pointer();
+ (void)h.protect(src); // destroyed while associated
+ }
+ {
+ std::hazard_pointer a = std::make_hazard_pointer();
+ std::hazard_pointer b(std::move(a)); // a is empty: only b destroys the hazard pointer
+ }
+ // Destroy many, then acquire many again: the pool must be reusable.
+ for (int round = 0; round < 3; ++round) {
+ std::hazard_pointer hps[64];
+ for (std::hazard_pointer& h : hps)
+ h = std::make_hazard_pointer();
+ for (std::hazard_pointer& h : hps)
+ assert(!h.empty());
+ }
+ return 0;
+}
diff --git a/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.ctor/move.pass.cpp b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.ctor/move.pass.cpp
new file mode 100644
index 0000000000000..e0cb1dcd02c8e
--- /dev/null
+++ b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.ctor/move.pass.cpp
@@ -0,0 +1,52 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// hazard_pointer(hazard_pointer&& other) noexcept;
+// Postconditions: If other is empty, *this is empty. Otherwise *this owns the hazard pointer
+// originally owned by other; other is empty.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <cassert>
+#include <utility>
+
+#include "test_macros.h"
+
+struct Node : std::hazard_pointer_obj_base<Node> {
+ int value = 0;
+};
+
+int main(int, char**) {
+ {
+ std::hazard_pointer empty;
+ std::hazard_pointer h(std::move(empty));
+ assert(h.empty());
+ assert(empty.empty());
+ }
+ {
+ Node node;
+ std::atomic<Node*> src{&node};
+ std::hazard_pointer a = std::make_hazard_pointer();
+ Node* p = a.protect(src);
+ assert(p == &node);
+ std::hazard_pointer b(std::move(a));
+ assert(!b.empty());
+ assert(a.empty());
+ // b owns the hazard pointer, still associated with node; b can keep using it.
+ b.reset_protection();
+ Node* q = b.protect(src);
+ assert(q == &node);
+ }
+ return 0;
+}
diff --git a/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.mem/empty.pass.cpp b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.mem/empty.pass.cpp
new file mode 100644
index 0000000000000..42e4241eebde8
--- /dev/null
+++ b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.mem/empty.pass.cpp
@@ -0,0 +1,32 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// bool empty() const noexcept;
+
+#include <hazard_pointer>
+#include <cassert>
+#include <utility>
+
+#include "test_macros.h"
+
+int main(int, char**) {
+ const std::hazard_pointer e;
+ assert(e.empty());
+ std::hazard_pointer h = std::make_hazard_pointer();
+ assert(!h.empty());
+ std::hazard_pointer m = std::move(h);
+ assert(h.empty());
+ assert(!m.empty());
+ return 0;
+}
diff --git a/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.mem/protect.pass.cpp b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.mem/protect.pass.cpp
new file mode 100644
index 0000000000000..1258f85f809fa
--- /dev/null
+++ b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.mem/protect.pass.cpp
@@ -0,0 +1,67 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// template<class T> T* protect(const atomic<T*>& src) noexcept;
+// Equivalent to: T* ptr = src.load(relaxed); while (!try_protect(ptr, src)) {} return ptr;
+
+#include <hazard_pointer>
+#include <atomic>
+#include <cassert>
+
+#include "test_macros.h"
+
+struct Node : std::hazard_pointer_obj_base<Node> {
+ int value;
+ explicit Node(int v) : value(v) {}
+};
+
+int main(int, char**) {
+ std::hazard_pointer h = std::make_hazard_pointer();
+ {
+ Node a(1);
+ std::atomic<Node*> src{&a};
+ Node* p = h.protect(src);
+ assert(p == &a);
+ assert(p->value == 1);
+ // Protecting again (a new epoch) works and returns the current value.
+ Node b(2);
+ src.store(&b);
+ Node* q = h.protect(src);
+ assert(q == &b);
+ assert(q->value == 2);
+ }
+ {
+ // A null source: protect returns nullptr and the hazard pointer ends up unassociated.
+ std::atomic<Node*> src{nullptr};
+ Node* p = h.protect(src);
+ assert(p == nullptr);
+ }
+ {
+ // Works with a pointer to a derived-from-Node? No: T must be exactly the hazard-protectable type.
+ // (Diagnostics are covered by libcxx/test/libcxx/thread/saferecl/saferecl.hp/hazard_protectable.verify.cpp)
+ // Works with two hazard pointers protecting the same object.
+ Node a(3);
+ std::atomic<Node*> src{&a};
+ std::hazard_pointer h2 = std::make_hazard_pointer();
+ assert(h.protect(src) == &a);
+ assert(h2.protect(src) == &a);
+ }
+ {
+ // src may be a const object, not merely a reference to const.
+ Node a(4);
+ const std::atomic<Node*> csrc{&a};
+ assert(h.protect(csrc) == &a);
+ }
+ return 0;
+}
diff --git a/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.mem/reset_protection.pass.cpp b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.mem/reset_protection.pass.cpp
new file mode 100644
index 0000000000000..0d9549c8aff88
--- /dev/null
+++ b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.mem/reset_protection.pass.cpp
@@ -0,0 +1,42 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// template<class T> void reset_protection(const T* ptr) noexcept;
+// void reset_protection(nullptr_t = nullptr) noexcept;
+
+#include <hazard_pointer>
+#include <atomic>
+#include <cassert>
+
+#include "test_macros.h"
+
+struct Node : std::hazard_pointer_obj_base<Node> {};
+
+int main(int, char**) {
+ Node a, b;
+ std::hazard_pointer h = std::make_hazard_pointer();
+ h.reset_protection(); // unassociated -> unassociated
+ h.reset_protection(nullptr); // same
+ h.reset_protection(&a); // associate with a
+ h.reset_protection(&b); // re-associate
+ const Node* cb = &b;
+ h.reset_protection(cb); // pointer-to-const is fine: T is deduced as Node
+ Node* null = nullptr;
+ h.reset_protection(null); // a null T*: equivalent to reset_protection()
+ h.reset_protection();
+ // Still usable afterwards.
+ std::atomic<Node*> src{&a};
+ assert(h.protect(src) == &a);
+ return 0;
+}
diff --git a/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.mem/swap.member.pass.cpp b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.mem/swap.member.pass.cpp
new file mode 100644
index 0000000000000..726659dc85c72
--- /dev/null
+++ b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.mem/swap.member.pass.cpp
@@ -0,0 +1,60 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// void swap(hazard_pointer& other) noexcept;
+// Swaps ownership; the hazard pointers themselves stay associated with what they protected.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <cassert>
+
+#include "test_macros.h"
+
+struct Node : std::hazard_pointer_obj_base<Node> {};
+
+template <class T>
+concept IsNoThrowMemberSwappable = requires(T& t) {
+ { t.swap(t) } noexcept;
+};
+static_assert(IsNoThrowMemberSwappable<std::hazard_pointer>);
+
+int main(int, char**) {
+ {
+ std::hazard_pointer a, b;
+ a.swap(b);
+ assert(a.empty() && b.empty());
+ }
+ {
+ std::hazard_pointer a = std::make_hazard_pointer(), b;
+ a.swap(b);
+ assert(a.empty());
+ assert(!b.empty());
+ b.swap(a);
+ assert(!a.empty());
+ assert(b.empty());
+ }
+ {
+ Node node;
+ std::atomic<Node*> src{&node};
+ std::hazard_pointer a = std::make_hazard_pointer(), b = std::make_hazard_pointer();
+ (void)a.protect(src);
+ a.swap(b);
+ assert(!a.empty() && !b.empty());
+ // No epoch ended or started: b now owns the pointer associated with node and may keep using it.
+ assert(b.protect(src) == &node);
+ a.swap(a); // self-swap: no effect
+ assert(!a.empty());
+ }
+ return 0;
+}
diff --git a/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.mem/try_protect.pass.cpp b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.mem/try_protect.pass.cpp
new file mode 100644
index 0000000000000..4c4c69d23d89f
--- /dev/null
+++ b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.mem/try_protect.pass.cpp
@@ -0,0 +1,66 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// template<class T> bool try_protect(T*& ptr, const atomic<T*>& src) noexcept;
+// old = ptr; reset_protection(old); ptr = src.load(acquire); if (old != ptr) reset_protection();
+// Returns old == ptr.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <cassert>
+
+#include "test_macros.h"
+
+struct Node : std::hazard_pointer_obj_base<Node> {};
+
+int main(int, char**) {
+ Node a, b;
+ std::hazard_pointer h = std::make_hazard_pointer();
+ {
+ // src unchanged: succeeds, ptr unchanged.
+ std::atomic<Node*> src{&a};
+ Node* ptr = src.load();
+ assert(h.try_protect(ptr, src));
+ assert(ptr == &a);
+ }
+ {
+ // src changed between the caller's load and try_protect: fails and reports the new value.
+ std::atomic<Node*> src{&a};
+ Node* ptr = src.load();
+ src.store(&b);
+ assert(!h.try_protect(ptr, src));
+ assert(ptr == &b);
+ // The reported value is now current: the retry succeeds.
+ assert(h.try_protect(ptr, src));
+ assert(ptr == &b);
+ }
+ {
+ // A stale null pointer.
+ std::atomic<Node*> src{&a};
+ Node* ptr = nullptr;
+ assert(!h.try_protect(ptr, src));
+ assert(ptr == &a);
+ assert(h.try_protect(ptr, src));
+ }
+ {
+ // A stale non-null pointer against a now-null source.
+ std::atomic<Node*> src{nullptr};
+ Node* ptr = &a;
+ assert(!h.try_protect(ptr, src));
+ assert(ptr == nullptr);
+ assert(h.try_protect(ptr, src));
+ assert(ptr == nullptr);
+ }
+ return 0;
+}
diff --git a/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.nonmem/make_hazard_pointer.pass.cpp b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.nonmem/make_hazard_pointer.pass.cpp
new file mode 100644
index 0000000000000..093e45d6f7117
--- /dev/null
+++ b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.nonmem/make_hazard_pointer.pass.cpp
@@ -0,0 +1,44 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// hazard_pointer make_hazard_pointer();
+
+#include <hazard_pointer>
+#include <cassert>
+#include <vector>
+
+#include "test_macros.h"
+
+int main(int, char**) {
+ {
+ std::hazard_pointer h = std::make_hazard_pointer();
+ assert(!h.empty());
+ }
+ {
+ // Many nonempty hazard pointers alive at once.
+ std::vector<std::hazard_pointer> hps;
+ for (int i = 0; i < 100; ++i)
+ hps.push_back(std::make_hazard_pointer());
+ for (const std::hazard_pointer& h : hps)
+ assert(!h.empty());
+ }
+ {
+ // Repeated construction/destruction keeps working (hazard pointers are pooled and reused).
+ for (int i = 0; i < 10000; ++i) {
+ std::hazard_pointer h = std::make_hazard_pointer();
+ assert(!h.empty());
+ }
+ }
+ return 0;
+}
diff --git a/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.nonmem/swap.free.pass.cpp b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.nonmem/swap.free.pass.cpp
new file mode 100644
index 0000000000000..096d2b94c0629
--- /dev/null
+++ b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/saferecl.hp.holder.nonmem/swap.free.pass.cpp
@@ -0,0 +1,37 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// void swap(hazard_pointer& a, hazard_pointer& b) noexcept; Equivalent to a.swap(b).
+
+#include <hazard_pointer>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <class T>
+concept IsNoThrowFreeSwappable = requires(T& t) {
+ { swap(t, t) } noexcept;
+};
+static_assert(IsNoThrowFreeSwappable<std::hazard_pointer>);
+
+int main(int, char**) {
+ std::hazard_pointer a = std::make_hazard_pointer(), b;
+ swap(a, b); // ADL
+ assert(a.empty());
+ assert(!b.empty());
+ std::swap(a, b);
+ assert(!a.empty());
+ assert(b.empty());
+ return 0;
+}
diff --git a/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/types.compile.pass.cpp b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/types.compile.pass.cpp
new file mode 100644
index 0000000000000..7f5a396d8f88d
--- /dev/null
+++ b/libcxx/test/std/thread/saferecl/saferecl.hp/saferecl.hp.holder/types.compile.pass.cpp
@@ -0,0 +1,52 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// UNSUPPORTED: no-threads
+// XFAIL: availability-hazard_pointer-missing
+
+// <hazard_pointer>
+
+// class hazard_pointer;
+// The class is move-only and every member listed in [saferecl.hp.holder] is present with the return
+// type and noexcept-ness the synopsis gives: the default constructor, the move constructor, move
+// assignment, empty(), protect(), try_protect(), both reset_protection() overloads, swap(), the free
+// swap() and make_hazard_pointer().
+
+#include <hazard_pointer>
+#include <atomic>
+#include <type_traits>
+#include <utility>
+
+#include "test_macros.h"
+
+struct Node : std::hazard_pointer_obj_base<Node> {};
+
+static_assert(std::is_nothrow_default_constructible_v<std::hazard_pointer>);
+static_assert(std::is_nothrow_move_constructible_v<std::hazard_pointer>);
+static_assert(std::is_nothrow_move_assignable_v<std::hazard_pointer>);
+static_assert(!std::is_copy_constructible_v<std::hazard_pointer>);
+static_assert(!std::is_copy_assignable_v<std::hazard_pointer>);
+static_assert(std::is_nothrow_swappable_v<std::hazard_pointer>);
+static_assert(noexcept(std::declval<const std::hazard_pointer&>().empty()));
+static_assert(noexcept(std::declval<std::hazard_pointer&>().protect(std::declval<const std::atomic<Node*>&>())));
+static_assert(noexcept(std::declval<std::hazard_pointer&>().try_protect(
+ std::declval<Node*&>(), std::declval<const std::atomic<Node*>&>())));
+static_assert(noexcept(std::declval<std::hazard_pointer&>().reset_protection(std::declval<const Node*>())));
+static_assert(noexcept(std::declval<std::hazard_pointer&>().reset_protection()));
+static_assert(noexcept(std::declval<std::hazard_pointer&>().reset_protection(nullptr)));
+static_assert(noexcept(std::declval<std::hazard_pointer&>().swap(std::declval<std::hazard_pointer&>())));
+static_assert(noexcept(std::swap(std::declval<std::hazard_pointer&>(), std::declval<std::hazard_pointer&>())));
+static_assert(!noexcept(std::make_hazard_pointer()));
+static_assert(std::is_same_v<decltype(std::make_hazard_pointer()), std::hazard_pointer>);
+static_assert(
+ std::is_same_v<decltype(std::declval<std::hazard_pointer&>().protect(std::declval<const std::atomic<Node*>&>())),
+ Node*>);
+static_assert(std::is_same_v<decltype(std::declval<std::hazard_pointer&>().try_protect(
+ std::declval<Node*&>(), std::declval<const std::atomic<Node*>&>())),
+ bool>);
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index 4f56fd83fa202..6e2c798629726 100644
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -745,10 +745,9 @@ def add_version_header(tc):
{
"name": "__cpp_lib_hazard_pointer",
"values": {"c++26": 202306}, # P2530R3 Hazard Pointers for C++26
- "headers": [
- "hazard_pointer" # TODO verify this entry since the paper was underspecified.
- ],
- "unimplemented": True,
+ "headers": ["hazard_pointer"],
+ "test_suite_guard": "!defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_HAZARD_POINTER)",
+ "libcxx_guard": "_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_HAZARD_POINTER",
},
{
"name": "__cpp_lib_hypot",
@@ -1566,6 +1565,7 @@ def add_version_header(tc):
"barrier": ["UNSUPPORTED: no-threads"],
"filesystem": ["UNSUPPORTED: no-filesystem"],
"fstream": ["UNSUPPORTED: no-localization"],
+ "hazard_pointer": ["UNSUPPORTED: no-threads"],
"iomanip": ["UNSUPPORTED: no-localization"],
"ios": ["UNSUPPORTED: no-localization"],
"iostream": ["UNSUPPORTED: no-localization"],
diff --git a/libcxx/utils/libcxx/header_information.py b/libcxx/utils/libcxx/header_information.py
index 7316662171b77..b5635d3242c3d 100644
--- a/libcxx/utils/libcxx/header_information.py
+++ b/libcxx/utils/libcxx/header_information.py
@@ -159,7 +159,6 @@ def __hash__(self) -> int:
headers_not_available = list(map(Header, [
"debugging",
"generator",
- "hazard_pointer",
"inplace_vector",
"linalg",
"rcu",
diff --git a/libcxx/utils/libcxx/test/features/availability.py b/libcxx/utils/libcxx/test/features/availability.py
index 65cf19f0a7617..66ab2a504ceb2 100644
--- a/libcxx/utils/libcxx/test/features/availability.py
+++ b/libcxx/utils/libcxx/test/features/availability.py
@@ -13,12 +13,19 @@
# Those are used for backdeployment features below, do not use directly in tests.
features = [
Feature(
- name="_target-has-llvm-23",
+ name="_target-has-llvm-24",
when=lambda cfg: BooleanExpression.evaluate(
"TBD",
cfg.available_features,
),
),
+ Feature(
+ name="_target-has-llvm-23",
+ when=lambda cfg: BooleanExpression.evaluate(
+ "_target-has-llvm-24",
+ cfg.available_features,
+ ),
+ ),
Feature(
name="_target-has-llvm-22",
when=lambda cfg: BooleanExpression.evaluate(
@@ -187,6 +194,14 @@
cfg.available_features,
),
),
+ # Tests that require the hazard pointer domain (<hazard_pointer>) in the built library.
+ Feature(
+ name="availability-hazard_pointer-missing",
+ when=lambda cfg: BooleanExpression.evaluate(
+ "!libcpp-has-no-availability-markup && (stdlib=apple-libc++ && !_target-has-llvm-24)",
+ cfg.available_features,
+ ),
+ ),
# Tests that require time zone database support in the built library
Feature(
name="availability-tzdb-missing",
More information about the libcxx-commits
mailing list