[libcxx-commits] [libcxx] [libc++] Add an internal asymmetric thread fence helper (PR #231475)

Nikita Grivin via libcxx-commits libcxx-commits at lists.llvm.org
Wed Oct 14 02:52:30 PDT 2026


https://github.com/MindSpectre updated https://github.com/llvm/llvm-project/pull/231475

>From c7f84382b9ffa44721284a2d99f0aded00160d02 Mon Sep 17 00:00:00 2001
From: Nikita Grivin <neuronspectrelin at gmail.com>
Date: Tue, 13 Oct 2026 15:12:33 +0300
Subject: [PATCH] [libc++] Add an internal asymmetric thread fence helper

Add std::__asymmetric_thread_fence_light/heavy(memory_order), shaped after
the Concurrency TS 2 (P1202) asymmetric_thread_fence_{light,heavy}, as a
private helper for <hazard_pointer> and <rcu>. Nothing public is added.
RFC: https://discourse.llvm.org/t/libc-asymmetric-fences/92444

A light fence and a heavy fence order memory like two seq_cst fences. Both
ignore their memory_order argument for now and always behave as seq_cst.

As suggested in the RFC, whether the platform supports the asymmetric
barrier is decided in the dylib, not in the headers:
- The light fence is inline. It reads the exported flag
  __asymmetric_thread_fence_is_asymmetric and is a compiler barrier when the
  flag is true, or a seq_cst fence otherwise. On Windows it is always a
  compiler barrier.
- The heavy fence is the exported __asymmetric_thread_fence_heavy_impl.
  - On Linux, the first call registers membarrier(2) PRIVATE_EXPEDITED once,
    in a function-local static, and only then sets the flag. Every heavy
    fence after that is a membarrier, so a light fence that sees the flag
    always pairs with a process-wide barrier.
  - Without membarrier, the flag stays false and both sides are seq_cst
    fences. There is no mprotect() fallback yet (left as a TODO).
  - On Windows the heavy fence is FlushProcessWriteBuffers; elsewhere it is
    a seq_cst fence.
- Where the dylib lacks the symbols (availability, LLVM 24), both fences
  are plain inline seq_cst fences.

The tests cover:
- a store-buffering litmus test, which fails if the heavy fence is replaced
  by a plain fence;
- the flag's state when 8 threads issue the first heavy fence at once;
- fork();
- fences issued from destructors at static destruction and thread exit.
---
 libcxx/include/CMakeLists.txt                 |   1 +
 .../__atomic/asymmetric_thread_fence.h        |  74 ++++++++++++
 libcxx/include/__configuration/availability.h |   5 +
 libcxx/include/module.modulemap.in            |  35 +++---
 libcxx/lib/abi/CHANGELOG.TXT                  |  10 ++
 ...bcxxabi.v1.stable.exceptions.nonew.abilist |   2 +
 ...bcxxabi.v1.stable.exceptions.nonew.abilist |   2 +
 ...bcxxabi.v1.stable.exceptions.nonew.abilist |   2 +
 ...bcxxabi.v1.stable.exceptions.nonew.abilist |   2 +
 ...bcxxabi.v1.stable.exceptions.nonew.abilist |   2 +
 ...bcxxabi.v1.stable.exceptions.nonew.abilist |   2 +
 ...bcxxabi.v1.stable.exceptions.nonew.abilist |   2 +
 ...bcxxabi.v1.stable.exceptions.nonew.abilist |   2 +
 ...xxabi.v1.stable.noexceptions.nonew.abilist |   2 +
 libcxx/src/CMakeLists.txt                     |   1 +
 libcxx/src/asymmetric_thread_fence.cpp        | 105 ++++++++++++++++++
 .../asymmetric_thread_fence/flag.pass.cpp     |  71 ++++++++++++
 .../asymmetric_thread_fence/fork.pass.cpp     |  40 +++++++
 .../header.compile.pass.cpp                   |  24 ++++
 .../asymmetric_thread_fence/store_buffering.h |  76 +++++++++++++
 .../store_buffering.pass.cpp                  |  55 +++++++++
 21 files changed, 498 insertions(+), 17 deletions(-)
 create mode 100644 libcxx/include/__atomic/asymmetric_thread_fence.h
 create mode 100644 libcxx/src/asymmetric_thread_fence.cpp
 create mode 100644 libcxx/test/libcxx/atomics/asymmetric_thread_fence/flag.pass.cpp
 create mode 100644 libcxx/test/libcxx/atomics/asymmetric_thread_fence/fork.pass.cpp
 create mode 100644 libcxx/test/libcxx/atomics/asymmetric_thread_fence/header.compile.pass.cpp
 create mode 100644 libcxx/test/libcxx/atomics/asymmetric_thread_fence/store_buffering.h
 create mode 100644 libcxx/test/libcxx/atomics/asymmetric_thread_fence/store_buffering.pass.cpp

diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 9af3d00f36f7e6..8b59bb2a7b3521 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -210,6 +210,7 @@ set(files
   __algorithm/upper_bound.h
   __assert
   __atomic/aliases.h
+  __atomic/asymmetric_thread_fence.h
   __atomic/atomic.h
   __atomic/atomic_flag.h
   __atomic/atomic_init.h
diff --git a/libcxx/include/__atomic/asymmetric_thread_fence.h b/libcxx/include/__atomic/asymmetric_thread_fence.h
new file mode 100644
index 00000000000000..3217a8b563ed47
--- /dev/null
+++ b/libcxx/include/__atomic/asymmetric_thread_fence.h
@@ -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___ATOMIC_ASYMMETRIC_THREAD_FENCE_H
+#define _LIBCPP___ATOMIC_ASYMMETRIC_THREAD_FENCE_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_HAS_THREADS
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+// Asymmetric thread fences, shaped after Concurrency TS 2 (P1202) asymmetric_thread_fence_{light,heavy}.
+// A light fence and a heavy fence order memory as if both were atomic_thread_fence(memory_order_seq_cst);
+// two light fences order nothing with respect to each other. The light fence is meant for hot paths and
+// the heavy fence for rare ones.
+//
+// Whether the heavy fence is a process-wide barrier (which lets the light fence be a compiler barrier) is
+// decided by the built library, not by these headers: __asymmetric_thread_fence_is_asymmetric becomes true
+// at most once, inside the first heavy fence, and only if every heavy fence from then on is such a barrier.
+// On Windows the library always provides one. Both fences currently ignore their memory_order argument and
+// behave as seq_cst.
+//
+// Where the dylib may lack these symbols (vendor availability annotations, e.g. Apple back-deployment), both
+// fences are plain inline fences. A heavy fence compiled that way can never become a process-wide barrier, so
+// on such platforms the library must keep the flag false (and the light fence a full fence) forever; otherwise
+// a light fence from newer code could pair with an old inline heavy fence and order nothing.
+
+#  if _LIBCPP_AVAILABILITY_HAS_ASYMMETRIC_THREAD_FENCE
+_LIBCPP_BEGIN_EXPLICIT_ABI_ANNOTATIONS
+extern _LIBCPP_EXPORTED_FROM_ABI atomic<bool> __asymmetric_thread_fence_is_asymmetric;
+_LIBCPP_EXPORTED_FROM_ABI void __asymmetric_thread_fence_heavy_impl(memory_order) _NOEXCEPT;
+_LIBCPP_END_EXPLICIT_ABI_ANNOTATIONS
+#  endif
+
+_LIBCPP_HIDE_FROM_ABI inline void __asymmetric_thread_fence_light([[__maybe_unused__]] memory_order __order) _NOEXCEPT {
+#  if !_LIBCPP_AVAILABILITY_HAS_ASYMMETRIC_THREAD_FENCE
+  std::atomic_thread_fence(memory_order_seq_cst);
+#  elif defined(_WIN32)
+  std::atomic_signal_fence(memory_order_seq_cst);
+#  else
+  if (__asymmetric_thread_fence_is_asymmetric.load(memory_order_relaxed))
+    std::atomic_signal_fence(memory_order_seq_cst);
+  else
+    std::atomic_thread_fence(memory_order_seq_cst);
+#  endif
+}
+
+_LIBCPP_HIDE_FROM_ABI inline void __asymmetric_thread_fence_heavy([[__maybe_unused__]] memory_order __order) _NOEXCEPT {
+#  if !_LIBCPP_AVAILABILITY_HAS_ASYMMETRIC_THREAD_FENCE
+  std::atomic_thread_fence(memory_order_seq_cst);
+#  else
+  std::__asymmetric_thread_fence_heavy_impl(__order);
+#  endif
+}
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_HAS_THREADS
+
+#endif // _LIBCPP___ATOMIC_ASYMMETRIC_THREAD_FENCE_H
diff --git a/libcxx/include/__configuration/availability.h b/libcxx/include/__configuration/availability.h
index 75d911438e4af1..04a8f84de15bdd 100644
--- a/libcxx/include/__configuration/availability.h
+++ b/libcxx/include/__configuration/availability.h
@@ -284,6 +284,11 @@
 #define _LIBCPP_AVAILABILITY_HAS_NEW_SYNC _LIBCPP_INTRODUCED_IN_LLVM_22
 #define _LIBCPP_AVAILABILITY_NEW_SYNC _LIBCPP_INTRODUCED_IN_LLVM_22_ATTRIBUTE
 
+// This controls whether the asymmetric thread fence entry points (an internal helper used by
+// <hazard_pointer> and <rcu>) are available in the dylib.
+#define _LIBCPP_AVAILABILITY_HAS_ASYMMETRIC_THREAD_FENCE _LIBCPP_INTRODUCED_IN_LLVM_24
+// No attribute, since the symbols are only referenced under the macro above
+
 // This controls whether `std::__hash_memory` is available in the dylib, which
 // is used for some `std::hash` specializations.
 #define _LIBCPP_AVAILABILITY_HAS_HASH_MEMORY _LIBCPP_INTRODUCED_IN_LLVM_21
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 2f6640f8469a5b..c7cc47801755bd 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -879,27 +879,28 @@ module std {
   }
 
   module atomic {
-    module aliases                { header "__atomic/aliases.h" }
-    module atomic_flag            { header "__atomic/atomic_flag.h" }
-    module atomic_init            { header "__atomic/atomic_init.h" }
-    module atomic_lock_free       { header "__atomic/atomic_lock_free.h" }
-    module atomic_ref             { header "__atomic/atomic_ref.h" }
-    module atomic_sync            { header "__atomic/atomic_sync.h" }
-    module atomic_sync_timed      { header "__atomic/atomic_sync_timed.h" }
-    module atomic_waitable_traits { header "__atomic/atomic_waitable_traits.h" }
+    module aliases                 { header "__atomic/aliases.h" }
+    module asymmetric_thread_fence { header "__atomic/asymmetric_thread_fence.h" }
+    module atomic_flag             { header "__atomic/atomic_flag.h" }
+    module atomic_init             { header "__atomic/atomic_init.h" }
+    module atomic_lock_free        { header "__atomic/atomic_lock_free.h" }
+    module atomic_ref              { header "__atomic/atomic_ref.h" }
+    module atomic_sync             { header "__atomic/atomic_sync.h" }
+    module atomic_sync_timed       { header "__atomic/atomic_sync_timed.h" }
+    module atomic_waitable_traits  { header "__atomic/atomic_waitable_traits.h" }
     module atomic {
       header "__atomic/atomic.h"
       export std.atomic.atomic_base // most of std::atomic methods are defined there
     }
-    module check_memory_order     { header "__atomic/check_memory_order.h" }
-    module clear_padding          { header "__atomic/clear_padding.h" }
-    module contention_t           { header "__atomic/contention_t.h" }
-    module fence                  { header "__atomic/fence.h" }
-    module floating_point_helper  { header "__atomic/floating_point_helper.h" }
-    module is_always_lock_free    { header "__atomic/is_always_lock_free.h" }
-    module kill_dependency        { header "__atomic/kill_dependency.h" }
-    module memory_order           { header "__atomic/memory_order.h" }
-    module to_gcc_order           { header "__atomic/to_gcc_order.h" }
+    module check_memory_order      { header "__atomic/check_memory_order.h" }
+    module clear_padding           { header "__atomic/clear_padding.h" }
+    module contention_t            { header "__atomic/contention_t.h" }
+    module fence                   { header "__atomic/fence.h" }
+    module floating_point_helper   { header "__atomic/floating_point_helper.h" }
+    module is_always_lock_free     { header "__atomic/is_always_lock_free.h" }
+    module kill_dependency         { header "__atomic/kill_dependency.h" }
+    module memory_order            { header "__atomic/memory_order.h" }
+    module to_gcc_order            { header "__atomic/to_gcc_order.h" }
 
     module support {
       header "__atomic/support.h"
diff --git a/libcxx/lib/abi/CHANGELOG.TXT b/libcxx/lib/abi/CHANGELOG.TXT
index 85d2e596ed51f4..2c224e372ab3f2 100644
--- a/libcxx/lib/abi/CHANGELOG.TXT
+++ b/libcxx/lib/abi/CHANGELOG.TXT
@@ -16,6 +16,16 @@ New entries should be added directly below the "Version" header.
 Version 24.0
 ------------
 
+* [libc++] Add an internal asymmetric thread fence helper
+
+  The heavy side of the asymmetric thread fence lives in the dylib, together with the flag telling the
+  inline light side whether the heavy side is a process-wide barrier.
+
+  All platforms
+  -------------
+  Symbol added: _ZNSt3__136__asymmetric_thread_fence_heavy_implENS_12memory_orderE
+  Symbol added: _ZNSt3__139__asymmetric_thread_fence_is_asymmetricE
+
 * [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 74e0af974e4538..2abf481dab05ae 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
@@ -1592,6 +1592,8 @@
 {'is_defined': True, 'name': '__ZNSt3__132__destroy_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseE', 'type': 'FUNC'}
 {'is_defined': True, 'name': '__ZNSt3__133__atomic_wait_native_with_timeoutILm8EEEvPKvS2_y', 'type': 'FUNC'}
 {'is_defined': True, 'name': '__ZNSt3__134__construct_barrier_algorithm_baseERl', 'type': 'FUNC'}
+{'is_defined': True, 'name': '__ZNSt3__136__asymmetric_thread_fence_heavy_implENS_12memory_orderE', 'type': 'FUNC'}
+{'is_defined': True, 'name': '__ZNSt3__139__asymmetric_thread_fence_is_asymmetricE', 'size': 0, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '__ZNSt3__139__atomic_wait_global_table_with_timeoutEPKvxy', 'type': 'FUNC'}
 {'is_defined': True, 'name': '__ZNSt3__13cinE', 'size': 0, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '__ZNSt3__13pmr15memory_resourceD0Ev', 'type': 'FUNC'}
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 1b0cd758bfcf36..2f7b504902396c 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
@@ -1229,6 +1229,8 @@
 {'is_defined': True, 'name': '_ZNSt6__ndk132__destroy_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseE', 'type': 'FUNC'}
 {'is_defined': True, 'name': '_ZNSt6__ndk133__atomic_wait_native_with_timeoutILj4EEEvPKvS2_y', 'type': 'FUNC'}
 {'is_defined': True, 'name': '_ZNSt6__ndk134__construct_barrier_algorithm_baseERi', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt6__ndk136__asymmetric_thread_fence_heavy_implENS_12memory_orderE', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt6__ndk139__asymmetric_thread_fence_is_asymmetricE', 'size': 1, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZNSt6__ndk139__atomic_wait_global_table_with_timeoutEPKviy', 'type': 'FUNC'}
 {'is_defined': True, 'name': '_ZNSt6__ndk13cinE', 'size': 88, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZNSt6__ndk13pmr15memory_resourceD0Ev', '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 c61db56dc2b904..96690d1dacd5f3 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
@@ -578,6 +578,8 @@
 {'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__132__atomic_notify_one_global_tableEPKv', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
 {'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__132__destroy_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
 {'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__134__construct_barrier_algorithm_baseERl', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
+{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__136__asymmetric_thread_fence_heavy_implENS_12memory_orderE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
+{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__139__asymmetric_thread_fence_is_asymmetricE', 'storage_mapping_class': 'RW', 'type': 'OBJECT'}
 {'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__139__atomic_wait_global_table_with_timeoutEPKviy', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
 {'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__13cinE', 'storage_mapping_class': 'RW', 'type': 'OBJECT'}
 {'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__13pmr15memory_resourceD0Ev', '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 7663aa877a096f..9d8be9262cab07 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
@@ -578,6 +578,8 @@
 {'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__132__atomic_notify_one_global_tableEPKv', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
 {'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__132__destroy_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
 {'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__134__construct_barrier_algorithm_baseERl', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
+{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__136__asymmetric_thread_fence_heavy_implENS_12memory_orderE', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
+{'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__139__asymmetric_thread_fence_is_asymmetricE', 'storage_mapping_class': 'RW', 'type': 'OBJECT'}
 {'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__139__atomic_wait_global_table_with_timeoutEPKvlm', 'storage_mapping_class': 'DS', 'type': 'FUNC'}
 {'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__13cinE', 'storage_mapping_class': 'RW', 'type': 'OBJECT'}
 {'import_export': 'EXP', 'is_defined': True, 'name': '_ZNSt3__13pmr15memory_resourceD0Ev', '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 00b5949f843acd..970f7f6faa9df5 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
@@ -1582,6 +1582,8 @@
 {'is_defined': True, 'name': '__ZNSt3__131__arrive_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseEh', 'type': 'FUNC'}
 {'is_defined': True, 'name': '__ZNSt3__132__destroy_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseE', 'type': 'FUNC'}
 {'is_defined': True, 'name': '__ZNSt3__134__construct_barrier_algorithm_baseERl', 'type': 'FUNC'}
+{'is_defined': True, 'name': '__ZNSt3__136__asymmetric_thread_fence_heavy_implENS_12memory_orderE', 'type': 'FUNC'}
+{'is_defined': True, 'name': '__ZNSt3__139__asymmetric_thread_fence_is_asymmetricE', 'size': 0, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '__ZNSt3__13cinE', 'size': 0, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '__ZNSt3__13pmr15memory_resourceD0Ev', 'type': 'FUNC'}
 {'is_defined': True, 'name': '__ZNSt3__13pmr15memory_resourceD1Ev', 'type': 'FUNC'}
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 536614e0ccfa64..42e10f68b91781 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
@@ -1229,6 +1229,8 @@
 {'is_defined': True, 'name': '_ZNSt6__ndk132__destroy_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseE', 'type': 'FUNC'}
 {'is_defined': True, 'name': '_ZNSt6__ndk133__atomic_wait_native_with_timeoutILm4EEEvPKvS2_m', 'type': 'FUNC'}
 {'is_defined': True, 'name': '_ZNSt6__ndk134__construct_barrier_algorithm_baseERl', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt6__ndk136__asymmetric_thread_fence_heavy_implENS_12memory_orderE', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt6__ndk139__asymmetric_thread_fence_is_asymmetricE', 'size': 1, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZNSt6__ndk139__atomic_wait_global_table_with_timeoutEPKvim', 'type': 'FUNC'}
 {'is_defined': True, 'name': '_ZNSt6__ndk13cinE', 'size': 168, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZNSt6__ndk13pmr15memory_resourceD0Ev', '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 d655541acdc8d5..7b8717434c11ad 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
@@ -1242,6 +1242,8 @@
 {'is_defined': True, 'name': '_ZNSt3__132__destroy_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseE', 'type': 'FUNC'}
 {'is_defined': True, 'name': '_ZNSt3__133__atomic_wait_native_with_timeoutILm8EEEvPKvS2_m', 'type': 'FUNC'}
 {'is_defined': True, 'name': '_ZNSt3__134__construct_barrier_algorithm_baseERl', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt3__136__asymmetric_thread_fence_heavy_implENS_12memory_orderE', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt3__139__asymmetric_thread_fence_is_asymmetricE', 'size': 1, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZNSt3__139__atomic_wait_global_table_with_timeoutEPKvlm', 'type': 'FUNC'}
 {'is_defined': True, 'name': '_ZNSt3__13cinE', 'size': 168, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZNSt3__13pmr15memory_resourceD0Ev', '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 69c32bd5be8775..8c007928a2b844 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
@@ -1240,6 +1240,8 @@
 {'is_defined': True, 'name': '_ZNSt3__132__destroy_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseE', 'type': 'FUNC'}
 {'is_defined': True, 'name': '_ZNSt3__133__atomic_wait_native_with_timeoutILm4EEEvPKvS2_m', 'type': 'FUNC'}
 {'is_defined': True, 'name': '_ZNSt3__134__construct_barrier_algorithm_baseERl', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt3__136__asymmetric_thread_fence_heavy_implENS_12memory_orderE', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt3__139__asymmetric_thread_fence_is_asymmetricE', 'size': 1, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZNSt3__139__atomic_wait_global_table_with_timeoutEPKvim', 'type': 'FUNC'}
 {'is_defined': True, 'name': '_ZNSt3__13cinE', 'size': 168, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZNSt3__13pmr15memory_resourceD0Ev', '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 b6850b2ec80938..2adc47f5fc001c 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
@@ -1211,6 +1211,8 @@
 {'is_defined': True, 'name': '_ZNSt3__132__destroy_barrier_algorithm_baseEPNS_24__barrier_algorithm_baseE', 'type': 'FUNC'}
 {'is_defined': True, 'name': '_ZNSt3__133__atomic_wait_native_with_timeoutILm4EEEvPKvS2_m', 'type': 'FUNC'}
 {'is_defined': True, 'name': '_ZNSt3__134__construct_barrier_algorithm_baseERl', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt3__136__asymmetric_thread_fence_heavy_implENS_12memory_orderE', 'type': 'FUNC'}
+{'is_defined': True, 'name': '_ZNSt3__139__asymmetric_thread_fence_is_asymmetricE', 'size': 1, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZNSt3__139__atomic_wait_global_table_with_timeoutEPKvim', 'type': 'FUNC'}
 {'is_defined': True, 'name': '_ZNSt3__13cinE', 'size': 168, 'type': 'OBJECT'}
 {'is_defined': True, 'name': '_ZNSt3__13pmr15memory_resourceD0Ev', 'type': 'FUNC'}
diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt
index 99e6bfb5692f52..b5d9f30883ae22 100644
--- a/libcxx/src/CMakeLists.txt
+++ b/libcxx/src/CMakeLists.txt
@@ -68,6 +68,7 @@ set(LIBCXX_SOURCES
 
 if (LIBCXX_ENABLE_THREADS)
   list(APPEND LIBCXX_SOURCES
+    asymmetric_thread_fence.cpp
     atomic.cpp
     barrier.cpp
     condition_variable_destructor.cpp
diff --git a/libcxx/src/asymmetric_thread_fence.cpp b/libcxx/src/asymmetric_thread_fence.cpp
new file mode 100644
index 00000000000000..a2e4dc0509619e
--- /dev/null
+++ b/libcxx/src/asymmetric_thread_fence.cpp
@@ -0,0 +1,105 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 <__atomic/asymmetric_thread_fence.h>
+#include <__atomic/fence.h>
+#include <__atomic/memory_order.h>
+#include <__config>
+#include <__verbose_abort>
+
+#if defined(__linux__)
+#  include <cerrno>
+#  include <sys/syscall.h>
+#  include <unistd.h>
+#elif defined(_WIN32)
+#  define WIN32_LEAN_AND_MEAN
+#  define VC_EXTRA_LEAN
+#  include <windows.h>
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+_LIBCPP_BEGIN_EXPLICIT_ABI_ANNOTATIONS
+
+// See <__atomic/asymmetric_thread_fence.h> for the protocol. The flag may only become true once every heavy
+// fence that can still run is a process-wide barrier, and it never becomes false again.
+#if defined(_WIN32)
+constinit atomic<bool> __asymmetric_thread_fence_is_asymmetric{true};
+#else
+constinit atomic<bool> __asymmetric_thread_fence_is_asymmetric{false};
+#endif
+
+#if defined(__linux__)
+
+namespace {
+
+#  if defined(SYS_membarrier)
+// membarrier(2) commands. The values are kernel ABI; spelling them here keeps the build independent of the
+// UAPI headers it was configured with.
+constexpr int __membarrier_cmd_query                      = 0;
+constexpr int __membarrier_cmd_private_expedited          = 1 << 3;
+constexpr int __membarrier_cmd_register_private_expedited = 1 << 4;
+
+long __membarrier(int __cmd) noexcept { return ::syscall(SYS_membarrier, __cmd, 0, 0); }
+
+bool __enable_membarrier() noexcept {
+  long __commands = __membarrier(__membarrier_cmd_query);
+  return __commands != -1 && (__commands & __membarrier_cmd_private_expedited) != 0 &&
+         __membarrier(__membarrier_cmd_register_private_expedited) == 0;
+}
+#  else
+bool __enable_membarrier() noexcept { return false; }
+#  endif
+
+} // namespace
+
+// TODO: add an mprotect()-based fallback (as folly does; possibly x86-only, since on AArch64 the TLB
+// invalidation is broadcast in hardware and does not interrupt the other CPUs) for kernels without
+// membarrier(2) PRIVATE_EXPEDITED. This is private to the library, so it can change without ABI impact.
+void __asymmetric_thread_fence_heavy_impl([[maybe_unused]] memory_order __order) noexcept {
+  // Decided once, before any heavy fence could be a plain fence while the flag reads true: concurrent first
+  // calls wait for this initialization, and the flag is only ever set here.
+  static const bool __asymmetric = [] {
+    bool __ok = __enable_membarrier();
+    if (__ok)
+      __asymmetric_thread_fence_is_asymmetric.store(true, memory_order_relaxed);
+    return __ok;
+  }();
+  if (!__asymmetric) {
+    std::atomic_thread_fence(memory_order_seq_cst);
+    return;
+  }
+#  if defined(SYS_membarrier)
+  if (__membarrier(__membarrier_cmd_private_expedited) == 0)
+    return;
+  // Light fences may already rely on this barrier, so never fall back to a plain fence. The registration
+  // survives fork() and only exec() clears it (which also reloads this library), so EPERM is not expected;
+  // should the registration ever be lost anyway, register again.
+  if (errno == EPERM && __membarrier(__membarrier_cmd_register_private_expedited) == 0 &&
+      __membarrier(__membarrier_cmd_private_expedited) == 0)
+    return;
+#  endif
+  __libcpp_verbose_abort("membarrier(2) failed after the asymmetric thread fence was enabled");
+}
+
+#else
+
+void __asymmetric_thread_fence_heavy_impl([[maybe_unused]] memory_order __order) noexcept {
+#  if defined(_WIN32)
+  ::FlushProcessWriteBuffers();
+#  else
+  // TODO: process-wide barriers on more platforms (FreeBSD 14.1+ has membarrier(2)). Not on platforms with
+  // vendor availability annotations (Apple): code back-deployed to a dylib without these symbols issues inline
+  // plain heavy fences, so the flag must stay false there (see <__atomic/asymmetric_thread_fence.h>).
+  std::atomic_thread_fence(memory_order_seq_cst);
+#  endif
+}
+
+#endif
+
+_LIBCPP_END_EXPLICIT_ABI_ANNOTATIONS
+_LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/test/libcxx/atomics/asymmetric_thread_fence/flag.pass.cpp b/libcxx/test/libcxx/atomics/asymmetric_thread_fence/flag.pass.cpp
new file mode 100644
index 00000000000000..4a44139d1fb54d
--- /dev/null
+++ b/libcxx/test/libcxx/atomics/asymmetric_thread_fence/flag.pass.cpp
@@ -0,0 +1,71 @@
+//===----------------------------------------------------------------------===//
+//
+// 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++11
+// UNSUPPORTED: no-threads
+// REQUIRES: linux || windows
+
+// __asymmetric_thread_fence_is_asymmetric is always true on Windows. On Linux it starts false, only a heavy
+// fence can set it, and after the first heavy fence it tells whether the kernel provides membarrier(2)
+// PRIVATE_EXPEDITED -- also when that first heavy fence is issued by many threads at once.
+
+#include <__atomic/asymmetric_thread_fence.h>
+#include <atomic>
+#include <cassert>
+#include <thread>
+#include <vector>
+
+#include "make_test_thread.h"
+
+#if defined(__linux__)
+#  include <sys/syscall.h>
+#  include <unistd.h>
+#endif
+
+#if !defined(_WIN32)
+static bool kernel_has_private_expedited_membarrier() {
+#  if defined(__linux__) && defined(SYS_membarrier)
+  long commands = syscall(SYS_membarrier, /* MEMBARRIER_CMD_QUERY */ 0, 0, 0);
+  return commands != -1 && (commands & /* MEMBARRIER_CMD_PRIVATE_EXPEDITED */ (1 << 3)) != 0;
+#  else
+  return false;
+#  endif
+}
+#endif
+
+int main(int, char**) {
+#if defined(_WIN32)
+  assert(std::__asymmetric_thread_fence_is_asymmetric.load());
+  std::__asymmetric_thread_fence_heavy(std::memory_order_seq_cst);
+  assert(std::__asymmetric_thread_fence_is_asymmetric.load());
+#else
+  assert(!std::__asymmetric_thread_fence_is_asymmetric.load());
+  std::__asymmetric_thread_fence_light(std::memory_order_seq_cst);
+  assert(!std::__asymmetric_thread_fence_is_asymmetric.load());
+
+  // The very first heavy fence comes from 8 threads at once.
+  std::atomic<bool> go(false);
+  std::vector<std::thread> threads;
+  for (int i = 0; i != 8; ++i)
+    threads.push_back(support::make_test_thread([&go] {
+      while (!go.load())
+        std::this_thread::yield();
+      std::__asymmetric_thread_fence_heavy(std::memory_order_seq_cst);
+      std::__asymmetric_thread_fence_light(std::memory_order_seq_cst);
+    }));
+  go.store(true);
+  for (std::thread& t : threads)
+    t.join();
+
+  const bool expected = kernel_has_private_expedited_membarrier();
+  assert(std::__asymmetric_thread_fence_is_asymmetric.load() == expected);
+  std::__asymmetric_thread_fence_heavy(std::memory_order_seq_cst);
+  assert(std::__asymmetric_thread_fence_is_asymmetric.load() == expected);
+#endif
+  return 0;
+}
diff --git a/libcxx/test/libcxx/atomics/asymmetric_thread_fence/fork.pass.cpp b/libcxx/test/libcxx/atomics/asymmetric_thread_fence/fork.pass.cpp
new file mode 100644
index 00000000000000..b237831b7e01d4
--- /dev/null
+++ b/libcxx/test/libcxx/atomics/asymmetric_thread_fence/fork.pass.cpp
@@ -0,0 +1,40 @@
+//===----------------------------------------------------------------------===//
+//
+// 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++11
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: windows
+
+// After the parent has issued a heavy fence (which enables the asymmetric path where the system supports
+// it), a forked child can still use both fences, and they still order memory.
+
+#include <__atomic/asymmetric_thread_fence.h>
+#include <atomic>
+#include <cassert>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "store_buffering.h"
+
+int main(int, char**) {
+  std::__asymmetric_thread_fence_heavy(std::memory_order_seq_cst);
+
+  pid_t pid = fork();
+  assert(pid != -1);
+  if (pid == 0) {
+    std::__asymmetric_thread_fence_light(std::memory_order_seq_cst);
+    std::__asymmetric_thread_fence_heavy(std::memory_order_seq_cst);
+    check_store_buffering(light, heavy, std::memory_order_seq_cst, 20000);
+    _exit(0);
+  }
+
+  int status = 0;
+  assert(waitpid(pid, &status, 0) == pid);
+  assert(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+  return 0;
+}
diff --git a/libcxx/test/libcxx/atomics/asymmetric_thread_fence/header.compile.pass.cpp b/libcxx/test/libcxx/atomics/asymmetric_thread_fence/header.compile.pass.cpp
new file mode 100644
index 00000000000000..d37e037feac3c9
--- /dev/null
+++ b/libcxx/test/libcxx/atomics/asymmetric_thread_fence/header.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++11
+// UNSUPPORTED: no-threads
+
+// <__atomic/asymmetric_thread_fence.h> is self-contained and warning-free in every language mode
+// (the test suite compiles with -Wunused-parameter -Werror and _LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER,
+// so an unused memory_order parameter without [[maybe_unused]] fails here).
+
+#include <__atomic/asymmetric_thread_fence.h>
+#include <__atomic/memory_order.h>
+
+void test(std::memory_order order) {
+  static_assert(noexcept(std::__asymmetric_thread_fence_light(order)), "");
+  static_assert(noexcept(std::__asymmetric_thread_fence_heavy(order)), "");
+  std::__asymmetric_thread_fence_light(order);
+  std::__asymmetric_thread_fence_heavy(order);
+}
diff --git a/libcxx/test/libcxx/atomics/asymmetric_thread_fence/store_buffering.h b/libcxx/test/libcxx/atomics/asymmetric_thread_fence/store_buffering.h
new file mode 100644
index 00000000000000..314ca470f15cb7
--- /dev/null
+++ b/libcxx/test/libcxx/atomics/asymmetric_thread_fence/store_buffering.h
@@ -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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef TEST_LIBCXX_ATOMICS_ASYMMETRIC_THREAD_FENCE_STORE_BUFFERING_H
+#define TEST_LIBCXX_ATOMICS_ASYMMETRIC_THREAD_FENCE_STORE_BUFFERING_H
+
+#include <__atomic/asymmetric_thread_fence.h>
+#include <atomic>
+#include <cassert>
+#include <thread>
+
+#include "make_test_thread.h"
+
+// A two-thread spin barrier: neither thread leaves arrive_and_wait() before both have entered it.
+class SpinBarrier {
+public:
+  SpinBarrier() : arrived_(0), phase_(0) {}
+
+  void arrive_and_wait() {
+    int phase = phase_.load(std::memory_order_acquire);
+    if (arrived_.fetch_add(1, std::memory_order_acq_rel) == 1) {
+      arrived_.store(0, std::memory_order_relaxed);
+      phase_.store(phase + 1, std::memory_order_release);
+    } else {
+      while (phase_.load(std::memory_order_acquire) == phase)
+        std::this_thread::yield();
+    }
+  }
+
+private:
+  std::atomic<int> arrived_;
+  std::atomic<int> phase_;
+};
+
+using Fence = void (*)(std::memory_order);
+
+inline void light(std::memory_order order) { std::__asymmetric_thread_fence_light(order); }
+inline void heavy(std::memory_order order) { std::__asymmetric_thread_fence_heavy(order); }
+
+// Store buffering (Dekker): `x = 1; fence_a; r1 = y` races with `y = 1; fence_b; r2 = x`.
+// If the two fences order memory like two seq_cst fences, r1 == 0 && r2 == 0 is impossible.
+inline void check_store_buffering(Fence fence_a, Fence fence_b, std::memory_order order, int rounds) {
+  std::atomic<int> x(0);
+  std::atomic<int> y(0);
+  int r2 = -1;
+  SpinBarrier barrier;
+  std::thread other = support::make_test_thread([&] {
+    for (int i = 0; i != rounds; ++i) {
+      barrier.arrive_and_wait(); // start of round
+      y.store(1, std::memory_order_relaxed);
+      fence_b(order);
+      r2 = x.load(std::memory_order_relaxed);
+      barrier.arrive_and_wait(); // r2 published
+      barrier.arrive_and_wait(); // x and y reset
+    }
+  });
+  for (int i = 0; i != rounds; ++i) {
+    barrier.arrive_and_wait();
+    x.store(1, std::memory_order_relaxed);
+    fence_a(order);
+    int r1 = y.load(std::memory_order_relaxed);
+    barrier.arrive_and_wait();
+    assert(!(r1 == 0 && r2 == 0));
+    x.store(0, std::memory_order_relaxed);
+    y.store(0, std::memory_order_relaxed);
+    barrier.arrive_and_wait();
+  }
+  other.join();
+}
+
+#endif // TEST_LIBCXX_ATOMICS_ASYMMETRIC_THREAD_FENCE_STORE_BUFFERING_H
diff --git a/libcxx/test/libcxx/atomics/asymmetric_thread_fence/store_buffering.pass.cpp b/libcxx/test/libcxx/atomics/asymmetric_thread_fence/store_buffering.pass.cpp
new file mode 100644
index 00000000000000..6f958ee3729ecb
--- /dev/null
+++ b/libcxx/test/libcxx/atomics/asymmetric_thread_fence/store_buffering.pass.cpp
@@ -0,0 +1,55 @@
+//===----------------------------------------------------------------------===//
+//
+// 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++11
+// UNSUPPORTED: no-threads
+
+// A light/heavy (or heavy/heavy) pair of asymmetric fences orders memory like a pair of seq_cst fences.
+// Two light fences give no guarantee and are not tested.
+
+#include <__atomic/asymmetric_thread_fence.h>
+#include <atomic>
+#include <thread>
+
+#include "make_test_thread.h"
+#include "store_buffering.h"
+
+// Fences are issued from destructors during static destruction and thread exit (where e.g. a hazard
+// pointer domain is torn down); they must neither crash nor deadlock there.
+struct FenceInDestructor {
+  ~FenceInDestructor() {
+    std::__asymmetric_thread_fence_light(std::memory_order_seq_cst);
+    std::__asymmetric_thread_fence_heavy(std::memory_order_seq_cst);
+  }
+};
+
+FenceInDestructor fence_at_exit;
+
+int main(int, char**) {
+  check_store_buffering(light, heavy, std::memory_order_seq_cst, 100000);
+  check_store_buffering(heavy, light, std::memory_order_seq_cst, 100000);
+  check_store_buffering(heavy, heavy, std::memory_order_seq_cst, 20000);
+
+  // libc++ ignores the order and always fences as seq_cst (P1202 would allow weaker orders to do less).
+  const std::memory_order orders[] = {
+      std::memory_order_relaxed,
+      std::memory_order_consume,
+      std::memory_order_acquire,
+      std::memory_order_release,
+      std::memory_order_acq_rel,
+      std::memory_order_seq_cst};
+  for (std::memory_order order : orders)
+    check_store_buffering(light, heavy, order, 5000);
+
+  support::make_test_thread([] {
+    thread_local FenceInDestructor fence_at_thread_exit;
+    (void)&fence_at_thread_exit;
+  }).join();
+
+  return 0;
+}



More information about the libcxx-commits mailing list