[libcxx-commits] [libcxx] [libc++] P2530R3: Implementation of std::hazard_pointer (PR #218218)
Nikita Grivin via libcxx-commits
libcxx-commits at lists.llvm.org
Thu Sep 17 02:45:54 PDT 2026
https://github.com/MindSpectre updated https://github.com/llvm/llvm-project/pull/218218
>From b70b6475c8fd7978fd2e9f27ae42e194c7366bfa 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.
Two parts of the engine are selected per platform:
- Asymmetric fences. Where _LIBCPP_HAZARD_POINTER_ASYMMETRIC_FENCE is 1
(Linux and Windows) the reader-side fence of try_protect(), which is inline
in the headers, and the retire-side fence are compiler barriers
(atomic_signal_fence), and every reclamation pass issues a process-wide
barrier before it scans the hazard pointers: membarrier(2) with
MEMBARRIER_CMD_PRIVATE_EXPEDITED (Linux 4.14, registered on first use and
again after fork()), falling back to an mprotect() page flip where
membarrier is unavailable, and FlushProcessWriteBuffers() on Windows. On
every other platform all three sides issue atomic_thread_fence(seq_cst).
The split is part of the contract between the headers and the built
library, documented in <__hazard_pointer/domain.h>: a library built for
one of those platforms must keep issuing the process-wide barrier, because
headers compiled against it emit no hardware fence on the reader side.
No exported symbol is involved; the heavy fence is private to the library.
- Per-thread record cache. On ELF targets with thread_local support the
cache is reached through a constant-initialised, trivially destructible
thread_local pointer with the initial-exec TLS model, so a lookup is one
TLS load with no __tls_get_addr call inside the shared library. Elsewhere
it is reached through the __libcpp_tls_* key alone. The key is always what
registers the thread-exit hook that returns cached records to the domain.
These are the first uses of membarrier(2)/FlushProcessWriteBuffers() and of
thread_local in the built library. Measured against Folly on the scenarios
of Folly's own HazptrBench/HazptrTest::bench (x86-64, 54 scenarios, geometric
mean of ns/op): 1.66x with seq_cst fences and the key-only cache, 1.25x with
the thread_local cache, 1.03x with asymmetric fences as well, and 0.97x with
the slow paths kept out of line so that the three entry points are leaf
functions on their fast paths.
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 | 107 ++
.../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 | 916 ++++++++++++++++++
.../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 | 4 +-
libcxx/utils/libcxx/header_information.py | 1 -
.../libcxx/test/features/availability.py | 17 +-
75 files changed, 3741 insertions(+), 33 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 8329c6114d212..fe6a32785ee96 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 80c437e868bcb..7c22101936419 100644
--- a/libcxx/docs/ReleaseNotes/24.rst
+++ b/libcxx/docs/ReleaseNotes/24.rst
@@ -53,6 +53,7 @@ Implemented Papers
- P3778R0: Fix for ``type_order`` template definition (`Github <https://llvm.org/PR216616>`__)
- P4140R0: Proposed resolution for US70-126: allow incomplete types in type_order (`Github <https://llvm.org/PR216616>`__)
- P3793R2: Better shifting (`Github <https://llvm.org/PR219389>`__) (no ``std::simd`` overloads)
+- 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 fc38b0a47070e..5578c17d7245a 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 bc6499c6e2bbd..e958473da4da6 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -493,6 +493,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
@@ -1078,6 +1081,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..d97f48cbabf25
--- /dev/null
+++ b/libcxx/include/__hazard_pointer/domain.h
@@ -0,0 +1,107 @@
+// -*- 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 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(), acquire-reload
+// of src, compare. The library loads slots with acquire ordering (or relaxed followed by an acquire
+// fence) and, before scanning them, issues a fence that orders every reader's store-then-load pair
+// against the scan: where _LIBCPP_HAZARD_POINTER_ASYMMETRIC_FENCE is 1 (Linux, Windows) that is a
+// process-wide barrier (membarrier(2) / FlushProcessWriteBuffers) and the reader's fence is a compiler
+// barrier; elsewhere both sides issue a seq_cst fence. The platform split is part of this contract: a
+// library built for one of those platforms must keep issuing the process-wide barrier, since headers
+// compiled against it emit no hardware fence on the reader side.
+
+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;
+};
+
+// Asymmetric fences (the reader side is a compiler barrier, the reclaimer issues a process-wide barrier)
+// on the platforms where the built library has such a barrier; see the protocol note above.
+# if defined(__linux__) || defined(_WIN32)
+# define _LIBCPP_HAZARD_POINTER_ASYMMETRIC_FENCE 1
+# else
+# define _LIBCPP_HAZARD_POINTER_ASYMMETRIC_FENCE 0
+# endif
+
+// The reader-side fence of try_protect().
+_LIBCPP_HIDE_FROM_ABI inline void __hazard_pointer_reader_fence() noexcept {
+# if _LIBCPP_HAZARD_POINTER_ASYMMETRIC_FENCE
+ std::atomic_signal_fence(memory_order_seq_cst);
+# else
+ std::atomic_thread_fence(memory_order_seq_cst);
+# endif
+}
+
+_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 9ddac1013c954..d54265312b667 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -1492,6 +1492,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 de14038550c1c..29b5cf43b1614 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -611,7 +611,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..90b1111361bcf
--- /dev/null
+++ b/libcxx/src/hazard_pointer.cpp
@@ -0,0 +1,916 @@
+//===----------------------------------------------------------------------===//
+//
+// 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>
+
+#if _LIBCPP_HAZARD_POINTER_ASYMMETRIC_FENCE
+# if defined(__linux__)
+# include <cerrno>
+# include <sys/mman.h>
+# include <sys/syscall.h>
+# include <unistd.h>
+# elif defined(_WIN32)
+# define WIN32_LEAN_AND_MEAN
+# define VC_EXTRA_LEAN
+# include <windows.h>
+# endif
+#endif
+
+// The engine behind <hazard_pointer> implements 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
+
+#if defined(__ELF__) && __has_feature(cxx_thread_local)
+# define _LIBCPP_HAZARD_POINTER_THREAD_LOCAL 1
+#else
+# define _LIBCPP_HAZARD_POINTER_THREAD_LOCAL 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
+
+// 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. Reached through __tls_cache (one TLS load) where
+// _LIBCPP_HAZARD_POINTER_THREAD_LOCAL, else through the pthread key (call_once fast path + __libcpp_tls_get);
+// see __hazard_pointer_domain::__thread_cache. The key is always what registers the thread-exit hook.
+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 by __at_thread_exit once a thread's cache has been torn down (in __tls_cache, or in the
+// TLS key on the pthread-only path). While it is there, __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.
+//
+// With __tls_cache the sentinel stays for the rest of the thread's life. On the pthread-only path it 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 there: re-arming would keep
+// this thread in destructor rounds after sanitizer runtimes have already finalized it. Consequently, on
+// that path 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;
+
+#if _LIBCPP_HAZARD_POINTER_THREAD_LOCAL
+[[__gnu__::__tls_model__("initial-exec")]] constinit thread_local __hazard_pointer_thread_cache* __tls_cache = nullptr;
+#endif
+
+// 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;
+ }
+};
+
+// Fences of the reader/reclaimer protocol (see <__hazard_pointer/domain.h>)
+
+// The fence every retire() issues before publishing its node, and the one every reclamation pass issues
+// before scanning the slots. With asymmetric fences the retire side (like the reader side, inline in
+// the headers) is a compiler barrier and the reclaimer side is a barrier on every CPU currently running
+// a thread of this process; otherwise both are seq_cst fences.
+#if _LIBCPP_HAZARD_POINTER_ASYMMETRIC_FENCE
+
+inline void __retire_fence() noexcept { std::atomic_signal_fence(memory_order_seq_cst); }
+
+# if defined(__linux__)
+
+# if defined(SYS_membarrier)
+// membarrier(2) private expedited commands (Linux 4.14). The command values are kernel ABI; spelling
+// them here keeps the build independent of the UAPI headers it was configured with. The process must
+// register once before using the barrier; fork() clears the registration, which shows up as EPERM.
+constexpr long __membarrier_cmd_private_expedited = 1 << 3;
+constexpr long __membarrier_cmd_register_private_expedited = 1 << 4;
+
+bool __membarrier_private_expedited() noexcept {
+ if (::syscall(SYS_membarrier, __membarrier_cmd_private_expedited, 0, 0) == 0)
+ return true;
+ if (errno != EPERM) // ENOSYS: no membarrier(2) or seccomp; EINVAL: kernel before 4.14
+ return false;
+ return ::syscall(SYS_membarrier, __membarrier_cmd_register_private_expedited, 0, 0) == 0 &&
+ ::syscall(SYS_membarrier, __membarrier_cmd_private_expedited, 0, 0) == 0;
+}
+# else
+bool __membarrier_private_expedited() noexcept { return false; }
+# endif
+
+// Fallback without membarrier(2): taking write permission away from a resident private page makes the
+// kernel interrupt every CPU that runs a thread of this process to flush its TLB, and that interrupt
+// is a full barrier on the interrupted CPU. Serialised because two flips at once could coalesce.
+void __mprotect_barrier() noexcept {
+ static constinit __libcpp_mutex_t __lock = _LIBCPP_MUTEX_INITIALIZER;
+ static void* __page = nullptr;
+ static size_t __page_size = 0;
+ std::__libcpp_mutex_lock(&__lock);
+ if (__page == nullptr) {
+ __page_size = static_cast<size_t>(::sysconf(_SC_PAGESIZE));
+ void* __p = ::mmap(nullptr, __page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (__p == MAP_FAILED)
+ _LIBCPP_VERBOSE_ABORT("hazard_pointer: cannot map the page used as a memory barrier");
+ __page = __p;
+ }
+ if (::mprotect(__page, __page_size, PROT_READ | PROT_WRITE) != 0)
+ _LIBCPP_VERBOSE_ABORT("hazard_pointer: mprotect failed");
+ *static_cast<volatile char*>(__page) = 1; // the page must be resident for the flush to go out
+ if (::mprotect(__page, __page_size, PROT_READ) != 0)
+ _LIBCPP_VERBOSE_ABORT("hazard_pointer: mprotect failed");
+ std::__libcpp_mutex_unlock(&__lock);
+}
+
+void __heavy_fence() noexcept {
+ static atomic<bool> __no_membarrier{false};
+ if (!__no_membarrier.load(memory_order_relaxed)) {
+ if (__membarrier_private_expedited())
+ return;
+ __no_membarrier.store(true, memory_order_relaxed);
+ }
+ __mprotect_barrier();
+}
+
+# elif defined(_WIN32)
+
+void __heavy_fence() noexcept { ::FlushProcessWriteBuffers(); }
+
+# endif
+
+#else
+
+inline void __retire_fence() noexcept { std::atomic_thread_fence(memory_order_seq_cst); }
+inline void __heavy_fence() noexcept { std::atomic_thread_fence(memory_order_seq_cst); }
+
+#endif // _LIBCPP_HAZARD_POINTER_ASYMMETRIC_FENCE
+
+// 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>.
+ __retire_fence();
+ __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.
+ //
+ // Out of line, like the other slow paths below: inlined into __hazard_pointer_retire() it would make every
+ // retire() pay the prologue (six saved registers and a frame) of a pass that runs once per ~1000 calls.
+ _LIBCPP_NOINLINE 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>.
+ __heavy_fence();
+ __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. Out of line for
+ // the same reason as __acquire_records: __hazard_pointer_release()'s fast path is a thread-cache push.
+ _LIBCPP_NOINLINE 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).
+ //
+ // 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). Out of line so that
+ // __hazard_pointer_acquire()'s fast path (a thread-cache pop) stays a leaf function.
+ _LIBCPP_NOINLINE __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 {
+#if _LIBCPP_HAZARD_POINTER_THREAD_LOCAL
+ if (__hazard_pointer_thread_cache* __p = __tls_cache; __p != nullptr) [[likely]]
+ return __p == &__dead_thread_cache ? nullptr : __p;
+ return __create ? __create_thread_cache() : nullptr;
+#else
+ __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)
+ return __create ? __create_thread_cache() : nullptr;
+ return static_cast<__hazard_pointer_thread_cache*>(__p);
+#endif
+ }
+ _LIBCPP_NOINLINE __hazard_pointer_thread_cache* __create_thread_cache() noexcept {
+ __init_tls();
+ if (!__tls_ok_)
+ return nullptr;
+ auto* __cache = ::new (nothrow) __hazard_pointer_thread_cache();
+ if (__cache != nullptr && std::__libcpp_tls_set(__tls_key_, __cache) != 0) {
+ delete __cache;
+ return nullptr;
+ }
+#if _LIBCPP_HAZARD_POINTER_THREAD_LOCAL
+ __tls_cache = __cache;
+#endif
+ return __cache;
+ }
+
+ // 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: return its records to the domain, free it, and arm the sentinel.
+// With __tls_cache the sentinel lives in TLS and the key is left null, so this runs once. On the pthread-only
+// path it goes into the key: POSIX clears the key's value before each destructor round, so round 1 arrives
+// with the cache and round 2 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;
+#if _LIBCPP_HAZARD_POINTER_THREAD_LOCAL
+ __tls_cache = &__dead_thread_cache;
+#else
+ std::__libcpp_tls_set(__domain.__tls_key_, &__dead_thread_cache);
+#endif
+}
+
+} // 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 3c5e9439f100c..3a08c0e243b41 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx20.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv
@@ -426,6 +426,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 8c03f1c3c2251..376ba2009483b 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx23.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv
@@ -445,6 +445,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 800d65aed4704..91e3641230874 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 800d65aed4704..91e3641230874 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 9c89124f6b65d..3102d9f67e572 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
@@ -7451,7 +7451,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
@@ -7460,7 +7460,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
@@ -9537,7 +9537,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
@@ -9546,7 +9546,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 f97488ddca59d..8cca5f08a3fc8 100755
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -756,7 +756,8 @@ def add_version_header(tc):
# "c++29": 202606, # P3428R4 Hazard Pointer Batches
},
"headers": ["hazard_pointer"],
- "unimplemented": True,
+ "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",
@@ -1638,6 +1639,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