[clang] [clang] constexpr atomic builtins (__c11_atomic_OP and __atomic_OP) (PR #98756)

Hana Dusíková via cfe-commits cfe-commits at lists.llvm.org
Wed Jan 8 15:43:31 PST 2025


https://github.com/hanickadot updated https://github.com/llvm/llvm-project/pull/98756

>From 7d65b133b5f04e078b3b26cee2b4be0fe78d01e6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hana=20Dusi=CC=81kova=CC=81?= <hanicka at hanicka.net>
Date: Mon, 6 Jan 2025 21:06:10 +0100
Subject: [PATCH 1/6] [clang] implement P3309 atomic builtins in constant
 evaluator

---
 clang/include/clang/Basic/Builtins.td         |  80 +--
 clang/lib/AST/ExprConstant.cpp                | 540 ++++++++++++++++++
 .../SemaCXX/atomic-constexpr-c11-builtins.cpp | 326 +++++++++++
 .../SemaCXX/atomic-constexpr-gcc-builtins.cpp | 504 ++++++++++++++++
 4 files changed, 1410 insertions(+), 40 deletions(-)
 create mode 100644 clang/test/SemaCXX/atomic-constexpr-c11-builtins.cpp
 create mode 100644 clang/test/SemaCXX/atomic-constexpr-gcc-builtins.cpp

diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 468c16050e2bf0..797191fba888d2 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -1744,97 +1744,97 @@ def SyncSwapN : Builtin, SyncBuiltinsTemplate {
 // C11 _Atomic operations for <stdatomic.h>.
 def C11AtomicInit : AtomicBuiltin {
   let Spellings = ["__c11_atomic_init"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicLoad : AtomicBuiltin {
   let Spellings = ["__c11_atomic_load"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicStore : AtomicBuiltin {
   let Spellings = ["__c11_atomic_store"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicExchange : AtomicBuiltin {
   let Spellings = ["__c11_atomic_exchange"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicCompareExchangeStrong : AtomicBuiltin {
   let Spellings = ["__c11_atomic_compare_exchange_strong"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicCompareExchangeWeak : AtomicBuiltin {
   let Spellings = ["__c11_atomic_compare_exchange_weak"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchAdd : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_add"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchSub : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_sub"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchAnd : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_and"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchOr : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_or"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchXor : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_xor"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchNand : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_nand"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchMax : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_max"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchMin : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_min"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicThreadFence : Builtin {
   let Spellings = ["__c11_atomic_thread_fence"];
-  let Attributes = [NoThrow];
+  let Attributes = [NoThrow, Constexpr];
   let Prototype = "void(int)";
 }
 
 def C11AtomicSignalFence : Builtin {
   let Spellings = ["__c11_atomic_signal_fence"];
-  let Attributes = [NoThrow];
+  let Attributes = [NoThrow, Constexpr];
   let Prototype = "void(int)";
 }
 
@@ -1847,133 +1847,133 @@ def C11AtomicIsLockFree : Builtin {
 // GNU atomic builtins.
 def AtomicLoad : AtomicBuiltin {
   let Spellings = ["__atomic_load"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicLoadN : AtomicBuiltin {
   let Spellings = ["__atomic_load_n"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicStore : AtomicBuiltin {
   let Spellings = ["__atomic_store"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicStoreN : AtomicBuiltin {
   let Spellings = ["__atomic_store_n"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicExchange : AtomicBuiltin {
   let Spellings = ["__atomic_exchange"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicExchangeN : AtomicBuiltin {
   let Spellings = ["__atomic_exchange_n"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicCompareExchange : AtomicBuiltin {
   let Spellings = ["__atomic_compare_exchange"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicCompareExchangeN : AtomicBuiltin {
   let Spellings = ["__atomic_compare_exchange_n"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicFetchAdd : AtomicBuiltin {
   let Spellings = ["__atomic_fetch_add"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicFetchSub : AtomicBuiltin {
   let Spellings = ["__atomic_fetch_sub"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicFetchAnd : AtomicBuiltin {
   let Spellings = ["__atomic_fetch_and"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicFetchOr : AtomicBuiltin {
   let Spellings = ["__atomic_fetch_or"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicFetchXor : AtomicBuiltin {
   let Spellings = ["__atomic_fetch_xor"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicFetchNand : AtomicBuiltin {
   let Spellings = ["__atomic_fetch_nand"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicAddFetch : AtomicBuiltin {
   let Spellings = ["__atomic_add_fetch"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicSubFetch : AtomicBuiltin {
   let Spellings = ["__atomic_sub_fetch"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicAndFetch : AtomicBuiltin {
   let Spellings = ["__atomic_and_fetch"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicOrFetch : AtomicBuiltin {
   let Spellings = ["__atomic_or_fetch"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicXorFetch : AtomicBuiltin {
   let Spellings = ["__atomic_xor_fetch"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicMaxFetch : AtomicBuiltin {
   let Spellings = ["__atomic_max_fetch"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicMinFetch : AtomicBuiltin {
   let Spellings = ["__atomic_min_fetch"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicNandFetch : AtomicBuiltin {
   let Spellings = ["__atomic_nand_fetch"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
@@ -1991,7 +1991,7 @@ def AtomicClear : Builtin {
 
 def AtomicThreadFence : Builtin {
   let Spellings = ["__atomic_thread_fence"];
-  let Attributes = [NoThrow];
+  let Attributes = [NoThrow, Constexpr];
   let Prototype = "void(int)";
 }
 
@@ -2003,7 +2003,7 @@ def ScopedAtomicThreadFence : Builtin {
 
 def AtomicSignalFence : Builtin {
   let Spellings = ["__atomic_signal_fence"];
-  let Attributes = [NoThrow];
+  let Attributes = [NoThrow, Constexpr];
   let Prototype = "void(int)";
 }
 
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index e220f69b3a4f58..54c0000f05cb25 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -1921,6 +1921,22 @@ static bool EvaluateFixedPointOrInteger(const Expr *E, APFixedPoint &Result,
 static bool EvaluateFixedPoint(const Expr *E, APFixedPoint &Result,
                                EvalInfo &Info);
 
+/// Support for atomic builtins
+static bool EvaluateAtomicOrder(const AtomicExpr *E, EvalInfo &Info);
+static bool EvaluateAtomicWeak(const AtomicExpr *E, EvalInfo &Info);
+static bool EvaluateAtomicLoad(const AtomicExpr *E, APValue &Result,
+                               EvalInfo &Info);
+static bool EvaluatePointerAndStoreValueInto(Expr *ResultPtr,
+                                             APValue &ValueToStore,
+                                             EvalInfo &Info);
+static bool EvaluateAtomicLoadInto(const AtomicExpr *E, EvalInfo &Info);
+static bool EvaluateAtomicStore(const AtomicExpr *E, EvalInfo &Info);
+static bool EvaluateAtomicExchangeInto(const AtomicExpr *E, EvalInfo &Info);
+static bool EvaluateAtomicFetchOp(const AtomicExpr *E, APValue &Result,
+                                  EvalInfo &Info, bool StoreToResultAfter);
+static bool EvaluateAtomicCompareExchange(const AtomicExpr *E, APValue &Result,
+                                          EvalInfo &Info);
+
 //===----------------------------------------------------------------------===//
 // Misc utilities
 //===----------------------------------------------------------------------===//
@@ -8075,6 +8091,66 @@ class ExprEvaluatorBase
     return StmtVisitorTy::Visit(Source);
   }
 
+  bool VisitAtomicExpr(const AtomicExpr *E) {
+    if (!EvaluateAtomicOrder(E, Info))
+      return false;
+
+    APValue LocalResult;
+    switch (E->getOp()) {
+    default:
+      return Error(E);
+    case AtomicExpr::AO__c11_atomic_load:
+    case AtomicExpr::AO__atomic_load_n:
+      if (!EvaluateAtomicLoad(E, LocalResult, Info))
+        return Error(E);
+      return DerivedSuccess(LocalResult, E);
+    case AtomicExpr::AO__c11_atomic_compare_exchange_strong:
+    case AtomicExpr::AO__c11_atomic_compare_exchange_weak:
+    case AtomicExpr::AO__atomic_compare_exchange:
+    case AtomicExpr::AO__atomic_compare_exchange_n:
+      if (!EvaluateAtomicCompareExchange(E, LocalResult, Info))
+        return Error(E);
+      return DerivedSuccess(LocalResult, E);
+    case AtomicExpr::AO__c11_atomic_exchange:
+    case AtomicExpr::AO__atomic_exchange_n:
+      if (!EvaluateAtomicLoad(E, LocalResult, Info))
+        return Error(E);
+      if (!EvaluateAtomicStore(E, Info))
+        return Error(E);
+      return DerivedSuccess(LocalResult, E);
+    case AtomicExpr::AO__c11_atomic_fetch_add:
+    case AtomicExpr::AO__c11_atomic_fetch_sub:
+    case AtomicExpr::AO__c11_atomic_fetch_and:
+    case AtomicExpr::AO__c11_atomic_fetch_or:
+    case AtomicExpr::AO__c11_atomic_fetch_xor:
+    case AtomicExpr::AO__c11_atomic_fetch_nand:
+    case AtomicExpr::AO__c11_atomic_fetch_max:
+    case AtomicExpr::AO__c11_atomic_fetch_min:
+    case AtomicExpr::AO__atomic_fetch_add:
+    case AtomicExpr::AO__atomic_fetch_sub:
+    case AtomicExpr::AO__atomic_fetch_and:
+    case AtomicExpr::AO__atomic_fetch_xor:
+    case AtomicExpr::AO__atomic_fetch_or:
+    case AtomicExpr::AO__atomic_fetch_nand:
+    case AtomicExpr::AO__atomic_fetch_max:
+    case AtomicExpr::AO__atomic_fetch_min:
+      if (!EvaluateAtomicFetchOp(E, LocalResult, Info, false))
+        return Error(E);
+      return DerivedSuccess(LocalResult, E);
+    case AtomicExpr::AO__atomic_add_fetch:
+    case AtomicExpr::AO__atomic_sub_fetch:
+    case AtomicExpr::AO__atomic_and_fetch:
+    case AtomicExpr::AO__atomic_xor_fetch:
+    case AtomicExpr::AO__atomic_or_fetch:
+    case AtomicExpr::AO__atomic_nand_fetch:
+    case AtomicExpr::AO__atomic_max_fetch:
+    case AtomicExpr::AO__atomic_min_fetch:
+      if (!EvaluateAtomicFetchOp(E, LocalResult, Info, true))
+        return Error(E);
+      return DerivedSuccess(LocalResult, E);
+    }
+  }
+
   bool VisitPseudoObjectExpr(const PseudoObjectExpr *E) {
     for (const Expr *SemE : E->semantics()) {
       if (auto *OVE = dyn_cast<OpaqueValueExpr>(SemE)) {
@@ -16268,6 +16344,25 @@ class VoidExprEvaluator
     }
   }
 
+  bool VisitAtomicExpr(const AtomicExpr *E) {
+    if (!EvaluateAtomicOrder(E, Info))
+      return false;
+
+    switch (E->getOp()) {
+    default:
+      return Error(E);
+    case AtomicExpr::AO__atomic_load:
+      return EvaluateAtomicLoadInto(E, Info);
+    case AtomicExpr::AO__atomic_exchange:
+      return EvaluateAtomicExchangeInto(E, Info);
+    case AtomicExpr::AO__c11_atomic_init:
+    case AtomicExpr::AO__c11_atomic_store:
+    case AtomicExpr::AO__atomic_store:
+    case AtomicExpr::AO__atomic_store_n:
+      return EvaluateAtomicStore(E, Info);
+    }
+  }
+
   bool VisitCallExpr(const CallExpr *E) {
     if (!IsConstantEvaluatedBuiltinCall(E))
       return ExprEvaluatorBaseTy::VisitCallExpr(E);
@@ -16281,6 +16376,14 @@ class VoidExprEvaluator
     case Builtin::BI__builtin_operator_delete:
       return HandleOperatorDeleteCall(Info, E);
 
+    case Builtin::BI__c11_atomic_thread_fence:
+    case Builtin::BI__c11_atomic_signal_fence:
+    case Builtin::BI__atomic_thread_fence:
+    case Builtin::BI__atomic_signal_fence: {
+      [[maybe_unused]] APSInt IgnoredAtomicOrdering;
+      return EvaluateInteger(E->getArg(0), IgnoredAtomicOrdering, Info);
+    }
+
     default:
       return false;
     }
@@ -17893,4 +17996,441 @@ std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &IEE,
   IsWithinLifetimeHandler handler{Info};
   return findSubobject(Info, E, CO, Val.getLValueDesignator(), handler);
 }
+
 } // namespace
+
+static bool EvaluateAtomicOrder(const AtomicExpr *E, EvalInfo &Info) {
+  // we ignore results, but we need to evaluate them
+  [[maybe_unused]] APSInt OrderIgnoredResult;
+
+  if (E->getOp() != AtomicExpr::AO__c11_atomic_init) {
+    const Expr *OrderSuccess = E->getOrder();
+    if (OrderSuccess &&
+        !EvaluateInteger(OrderSuccess, OrderIgnoredResult, Info))
+      return false;
+  }
+
+  if (E->isCmpXChg()) {
+    const Expr *OrderFail = E->getOrderFail();
+    if (!EvaluateInteger(OrderFail, OrderIgnoredResult, Info))
+      return false;
+  }
+
+  return true;
+}
+
+static bool EvaluateAtomicWeak(const AtomicExpr *E, EvalInfo &Info) {
+  // we ignore results, but we need to evaluate them
+  [[maybe_unused]] APSInt WeakIgnoredResult;
+
+  if (E->getOp() == AtomicExpr::AO__atomic_compare_exchange_n ||
+      E->getOp() == AtomicExpr::AO__atomic_compare_exchange) {
+    const Expr *Weak = E->getWeak();
+    if (!EvaluateInteger(Weak, WeakIgnoredResult, Info))
+      return false;
+  }
+
+  return true;
+}
+
+static bool EvaluateAtomicLoad(const AtomicExpr *E, APValue &Result,
+                               EvalInfo &Info) {
+  LValue AtomicStorageLV;
+
+  if (!EvaluatePointer(E->getPtr(), AtomicStorageLV, Info))
+    return false;
+
+  return handleLValueToRValueConversion(Info, E->getPtr(), E->getValueType(),
+                                        AtomicStorageLV, Result);
+}
+
+static bool EvaluatePointerAndStoreValueInto(Expr *ResultPtr,
+                                             APValue &ValueToStore,
+                                             EvalInfo &Info) {
+  assert(ResultPtr->getType()->isPointerType());
+  QualType PointeeTy = ResultPtr->getType()->getPointeeType();
+  LValue PointeeLV;
+
+  if (!EvaluatePointer(ResultPtr, PointeeLV, Info))
+    return false;
+
+  return handleAssignment(Info, ResultPtr, PointeeLV, PointeeTy, ValueToStore);
+}
+
+static bool EvaluateAtomicLoadInto(const AtomicExpr *E, EvalInfo &Info) {
+  APValue LocalResult;
+
+  if (!EvaluateAtomicLoad(E, LocalResult, Info))
+    return false;
+
+  if (!EvaluatePointerAndStoreValueInto(E->getVal1(), LocalResult, Info))
+    return false;
+
+  return true;
+}
+
+static bool EvaluateAtomicStore(const AtomicExpr *E, EvalInfo &Info) {
+  LValue AtomicStorageLV;
+
+  if (!EvaluatePointer(E->getPtr(), AtomicStorageLV, Info))
+    return false;
+
+  APValue ProvidedValue;
+
+  // GCC's atomic_store takes pointer to value, not value itself
+  if (E->getOp() == AtomicExpr::AO__atomic_store) {
+    LValue ProvidedLV;
+    if (!EvaluatePointer(E->getVal1(), ProvidedLV, Info))
+      return false;
+
+    if (!handleLValueToRValueConversion(Info, E->getVal1(),
+                                        E->getVal1()->getType(), ProvidedLV,
+                                        ProvidedValue))
+      return false;
+
+  } else {
+    if (!Evaluate(ProvidedValue, Info, E->getVal1()))
+      return false;
+  }
+  if (!handleAssignment(Info, E, AtomicStorageLV, E->getValueType(),
+                        ProvidedValue))
+    return false;
+
+  return true;
+}
+
+static bool EvaluateAtomicExchangeInto(const AtomicExpr *E, EvalInfo &Info) {
+  assert(E->getOp() == AtomicExpr::AO__atomic_exchange);
+  // implementation of GCC's exchange (non _n version)
+  LValue AtomicStorageLV;
+  if (!EvaluatePointer(E->getPtr(), AtomicStorageLV, Info))
+    return false;
+
+  // read previous value
+  APValue PreviousValue;
+  if (!handleLValueToRValueConversion(Info, E->getPtr(), E->getValueType(),
+                                      AtomicStorageLV, PreviousValue))
+    return false;
+
+  // get provided value from argument (pointer)
+  LValue ProvidedLV;
+  if (!EvaluatePointer(E->getVal1(), ProvidedLV, Info))
+    return false;
+
+  APValue ProvidedValue;
+  if (!handleLValueToRValueConversion(Info, E->getVal1(),
+                                      E->getVal1()->getType(), ProvidedLV,
+                                      ProvidedValue))
+    return false;
+
+  // store provided value to atomic value
+  if (!handleAssignment(Info, E, AtomicStorageLV, E->getValueType(),
+                        ProvidedValue))
+    return false;
+
+  // store previous value in output pointer
+  if (!EvaluatePointerAndStoreValueInto(E->getVal2(), PreviousValue, Info))
+    return false;
+
+  return true;
+}
+
+static bool EvaluateAtomicFetchOp(const AtomicExpr *E, APValue &Result,
+                                  EvalInfo &Info, bool StoreToResultAfter) {
+  // read atomic
+  LValue AtomicStorageLV;
+  QualType AtomicValueTy = E->getValueType();
+  if (!EvaluatePointer(E->getPtr(), AtomicStorageLV, Info))
+    return false;
+
+  APValue CurrentValue;
+  if (!handleLValueToRValueConversion(Info, E->getPtr(), E->getType(),
+                                      AtomicStorageLV, CurrentValue))
+    return false;
+
+  // store current value for fetch-OP operations
+  if (!StoreToResultAfter)
+    Result = CurrentValue;
+
+  // read argument for fetch OP
+  APValue ArgumentVal;
+  if (!Evaluate(ArgumentVal, Info, E->getVal1()))
+    return false;
+
+  // calculate new value
+  APValue Replacement;
+  if (AtomicValueTy->isIntegralOrEnumerationType()) {
+    // both arguments are integers
+    const APSInt AtomicInt = CurrentValue.getInt();
+    const APSInt ArgumentInt = ArgumentVal.getInt();
+
+    switch (E->getOp()) {
+    case AtomicExpr::AO__c11_atomic_fetch_add:
+    case AtomicExpr::AO__atomic_fetch_add:
+    case AtomicExpr::AO__atomic_add_fetch:
+      Replacement = APValue(AtomicInt + ArgumentInt);
+      break;
+    case AtomicExpr::AO__c11_atomic_fetch_sub:
+    case AtomicExpr::AO__atomic_fetch_sub:
+    case AtomicExpr::AO__atomic_sub_fetch:
+      Replacement = APValue(AtomicInt - ArgumentInt);
+      break;
+    case AtomicExpr::AO__c11_atomic_fetch_and:
+    case AtomicExpr::AO__atomic_fetch_and:
+    case AtomicExpr::AO__atomic_and_fetch:
+      Replacement = APValue(AtomicInt & ArgumentInt);
+      break;
+    case AtomicExpr::AO__c11_atomic_fetch_or:
+    case AtomicExpr::AO__atomic_fetch_or:
+    case AtomicExpr::AO__atomic_or_fetch:
+      Replacement = APValue(AtomicInt | ArgumentInt);
+      break;
+    case AtomicExpr::AO__c11_atomic_fetch_xor:
+    case AtomicExpr::AO__atomic_fetch_xor:
+    case AtomicExpr::AO__atomic_xor_fetch:
+      Replacement = APValue(AtomicInt ^ ArgumentInt);
+      break;
+    case AtomicExpr::AO__c11_atomic_fetch_nand:
+    case AtomicExpr::AO__atomic_fetch_nand:
+    case AtomicExpr::AO__atomic_nand_fetch:
+      Replacement = APValue(~(AtomicInt & ArgumentInt));
+      break;
+    case AtomicExpr::AO__c11_atomic_fetch_max:
+    case AtomicExpr::AO__atomic_fetch_max:
+    case AtomicExpr::AO__atomic_max_fetch:
+      Replacement =
+          APValue((AtomicInt > ArgumentInt) ? AtomicInt : ArgumentInt);
+      break;
+    case AtomicExpr::AO__c11_atomic_fetch_min:
+    case AtomicExpr::AO__atomic_fetch_min:
+    case AtomicExpr::AO__atomic_min_fetch:
+      Replacement =
+          APValue((AtomicInt < ArgumentInt) ? AtomicInt : ArgumentInt);
+      break;
+    default:
+      return false;
+    }
+  } else if (AtomicValueTy->isRealFloatingType()) {
+    // both arguments are float operations
+    const llvm::RoundingMode RM = getActiveRoundingMode(Info, E);
+    APFloat AtomicFlt = CurrentValue.getFloat();
+    const APFloat ArgumentFlt = ArgumentVal.getFloat();
+    APFloat::opStatus St;
+
+    switch (E->getOp()) {
+    case AtomicExpr::AO__c11_atomic_fetch_add: // GCC atomics doesn't support
+                                               // floats
+      St = AtomicFlt.add(ArgumentFlt, RM);
+      Replacement = APValue(AtomicFlt);
+      break;
+    case AtomicExpr::AO__c11_atomic_fetch_sub:
+      St = AtomicFlt.subtract(ArgumentFlt, RM);
+      Replacement = APValue(AtomicFlt);
+      break;
+    default:
+      return false;
+    }
+
+    if (!checkFloatingPointResult(Info, E, St))
+      return false;
+
+  } else if (AtomicValueTy->isPointerType()) {
+    // pointer + int arguments
+    LValue AtomicPtr;
+    AtomicPtr.setFrom(Info.Ctx, CurrentValue);
+
+    APSInt ArgumentInt = ArgumentVal.getInt();
+
+    // calculate size of pointee object
+    CharUnits SizeOfPointee;
+    if (!HandleSizeof(Info, E->getExprLoc(), AtomicValueTy->getPointeeType(),
+                      SizeOfPointee))
+      return false;
+
+    // GCC's atomic_fetch add/sub compute new pointer by bytes and not
+    // sizeof(T)
+    switch (E->getOp()) {
+    case AtomicExpr::AO__atomic_fetch_add:
+    case AtomicExpr::AO__atomic_add_fetch:
+    case AtomicExpr::AO__atomic_fetch_sub:
+    case AtomicExpr::AO__atomic_sub_fetch: {
+      const auto sizeOfOneItem = APSInt(
+          APInt(ArgumentInt.getBitWidth(), SizeOfPointee.getQuantity(), false),
+          false);
+      // incrementing pointer by size which is not dividable by pointee size
+      // is UB and therefore disallowed
+      if ((ArgumentInt % sizeOfOneItem) != 0)
+        return false;
+
+      ArgumentInt /= sizeOfOneItem;
+    } break;
+    default:
+      break;
+    }
+
+    switch (E->getOp()) {
+    case AtomicExpr::AO__c11_atomic_fetch_add:
+    case AtomicExpr::AO__atomic_fetch_add:
+    case AtomicExpr::AO__atomic_add_fetch:
+      AtomicPtr.adjustOffsetAndIndex(Info, E, ArgumentInt, SizeOfPointee);
+      break;
+    case AtomicExpr::AO__c11_atomic_fetch_sub:
+    case AtomicExpr::AO__atomic_fetch_sub:
+    case AtomicExpr::AO__atomic_sub_fetch:
+      ArgumentInt.negate();
+      AtomicPtr.adjustOffsetAndIndex(Info, E, ArgumentInt, SizeOfPointee);
+      break;
+    default:
+      return false;
+    }
+
+    AtomicPtr.moveInto(Replacement);
+  } else {
+    // not float,int,pointer?
+    return false;
+  }
+
+  // OP-fetch operation store result, not previous value
+  if (StoreToResultAfter)
+    Result = Replacement;
+
+  // store result to atomic's storage
+  return handleAssignment(Info, E, AtomicStorageLV, AtomicValueTy, Replacement);
+}
+
+static bool EvaluateAtomicCompareExchange(const AtomicExpr *E, APValue &Result,
+                                          EvalInfo &Info) {
+  EvaluateAtomicWeak(E, Info);
+
+  // dereference _Atomic * (atomic value)
+  LValue AtomicLV;
+  QualType AtomicTy = E->getValueType();
+  if (!EvaluatePointer(E->getPtr(), AtomicLV, Info))
+    return false;
+
+  // dereference T * (expected value)
+  LValue ExpectedLV;
+  QualType ExpectedTy = E->getVal1()->getType()->getPointeeType();
+  if (!EvaluatePointer(E->getVal1(), ExpectedLV, Info))
+    return false;
+
+  // get values for atomic and expected
+  APValue AtomicVal;
+  APValue ExpectedVal;
+
+  // convert pointer to value
+  if (!handleLValueToRValueConversion(Info, E->getPtr(), AtomicTy, AtomicLV,
+                                      AtomicVal))
+    return false;
+
+  if (!handleLValueToRValueConversion(Info, E->getVal1(), ExpectedTy,
+                                      ExpectedLV, ExpectedVal))
+    return false;
+
+  bool DoExchange = false;
+
+  // compare atomic<int> and friends
+  if (AtomicTy->isIntegralOrEnumerationType() &&
+      ExpectedTy->isIntegralOrEnumerationType()) {
+    const APSInt AtomicInt = AtomicVal.getInt();
+    const APSInt ExpectedInt = ExpectedVal.getInt();
+    if (AtomicInt == ExpectedInt)
+      DoExchange = true;
+
+  } else if (AtomicTy->isRealFloatingType() &&
+             ExpectedTy->isRealFloatingType()) {
+    const APFloat AtomicFlt = AtomicVal.getFloat();
+    const APFloat ExpectedFlt = ExpectedVal.getFloat();
+    if (AtomicFlt == ExpectedFlt)
+      DoExchange = true;
+
+  } else if (AtomicTy->isPointerType() && ExpectedTy->isPointerType()) {
+    // get LValue of objects pointed to
+    LValue LHS;
+    LHS.setFrom(Info.Ctx, AtomicVal);
+
+    LValue RHS;
+    RHS.setFrom(Info.Ctx, ExpectedVal);
+
+    if (HasSameBase(LHS, RHS)) {
+      const CharUnits &LHSOffset = LHS.getLValueOffset();
+      const CharUnits &RHSOffset = RHS.getLValueOffset();
+
+      const unsigned PtrSize = Info.Ctx.getTypeSize(AtomicTy);
+      assert(PtrSize <= 64 && "Pointer width is larger than expected");
+      const uint64_t Mask = ~0ULL >> (64 - PtrSize);
+
+      const uint64_t CompareLHS = LHSOffset.getQuantity() & Mask;
+      const uint64_t CompareRHS = RHSOffset.getQuantity() & Mask;
+
+      if (CompareLHS == CompareRHS)
+        DoExchange = true;
+
+    } else {
+
+      if (ArePotentiallyOverlappingStringLiterals(Info, LHS, RHS))
+        return false;
+
+      if (IsOpaqueConstantCall(LHS) || IsOpaqueConstantCall(RHS))
+        return false;
+
+      if (IsWeakLValue(LHS) || IsWeakLValue(RHS))
+        return false;
+
+      if ((!LHS.Base && !LHS.Offset.isZero()) ||
+          (!RHS.Base && !RHS.Offset.isZero()))
+        return false;
+
+      if (LHS.Base && LHS.Offset.isZero() &&
+          isOnePastTheEndOfCompleteObject(Info.Ctx, RHS))
+        return false;
+
+      if (RHS.Base && RHS.Offset.isZero() &&
+          isOnePastTheEndOfCompleteObject(Info.Ctx, LHS))
+        return false;
+
+      if ((RHS.Base && isZeroSized(RHS)) || (LHS.Base && isZeroSized(LHS)))
+        return false;
+
+      // after all it's a different object
+      DoExchange = false;
+    }
+
+  } else {
+    return false;
+  }
+
+  APValue Replacement;
+  if (E->getOp() == AtomicExpr::AO__atomic_compare_exchange) {
+    // GCC atomic_compare_exchange provides pointer and not value
+    LValue ReplacementLV;
+    if (!EvaluatePointer(E->getVal2(), ReplacementLV, Info))
+      return false;
+
+    if (!handleLValueToRValueConversion(Info, E->getVal2(),
+                                        E->getVal2()->getType(), ReplacementLV,
+                                        Replacement))
+      return false;
+
+  } else {
+    if (!Evaluate(Replacement, Info, E->getVal2()))
+      return false;
+  }
+
+  if (DoExchange) {
+    // if values are same do the exchange with replacement value
+    // but first I must evaluate the replacement value
+
+    // and assign it to atomic
+    if (!handleAssignment(Info, E, AtomicLV, AtomicTy, Replacement))
+      return false;
+  }
+
+  // to expected pointer I need to put previous value in atomic
+  if (!handleAssignment(Info, E, ExpectedLV, ExpectedTy, AtomicVal))
+    return false;
+
+  // and return boolean if I did the exchange
+  Result = APValue(Info.Ctx.MakeIntValue(DoExchange, E->getType()));
+  return true;
+}
diff --git a/clang/test/SemaCXX/atomic-constexpr-c11-builtins.cpp b/clang/test/SemaCXX/atomic-constexpr-c11-builtins.cpp
new file mode 100644
index 00000000000000..9ff775d37bd52e
--- /dev/null
+++ b/clang/test/SemaCXX/atomic-constexpr-c11-builtins.cpp
@@ -0,0 +1,326 @@
+// RUN: %clang_cc1 -std=c++20 %s
+
+// expected-no-diagnostics
+
+constexpr int int_min = -2147483648;
+constexpr int int_max = 2147483647;
+
+const int array[2] = {1,2};
+const char small_array[2] = {1,2};
+
+template <typename T> struct identity {
+  using type = T;
+};
+
+template <typename T> using do_not_deduce = typename identity<T>::type;
+
+// -- SIGNAL and THREAD fence --
+consteval int fence_test(int v) {
+  // both are no-op in constexpr
+  __c11_atomic_thread_fence(__ATOMIC_ACQUIRE); 
+  __c11_atomic_signal_fence(__ATOMIC_ACQUIRE);
+  return v;
+}
+
+static_assert(fence_test(42) == 42);
+
+// -- LOAD --
+
+template <typename T> consteval T init(T value) {
+  auto * av = new _Atomic T;
+  __c11_atomic_init(av, value);
+  auto result = __c11_atomic_load(av, __ATOMIC_RELAXED);
+  delete av;
+  return result;
+}
+
+// integers
+static_assert(init(true) == true);
+static_assert(init(false) == false);
+
+static_assert(init(42) == 42);
+static_assert(init(-128) == -128);
+
+static_assert(init(42u) == 42u);
+static_assert(init(0xFFFFFFFFu) == 0xFFFFFFFFu);
+
+// floats
+static_assert(init(42.3) == 42.3);
+static_assert(init(42.3f) == 42.3f);
+
+// pointers
+static_assert(init(&array[0]) == &array[0]);
+static_assert(init(&small_array[1]) == &small_array[1]);
+
+// -- LOAD --
+
+template <typename T> consteval T load(T value) {
+  _Atomic(T) av = value;
+  return __c11_atomic_load(&av, __ATOMIC_RELAXED);
+}
+
+// integers
+static_assert(load(true) == true);
+static_assert(load(false) == false);
+
+static_assert(load(42) == 42);
+static_assert(load(-128) == -128);
+
+static_assert(load(42u) == 42u);
+static_assert(load(0xFFFFFFFFu) == 0xFFFFFFFFu);
+
+// floats
+static_assert(load(42.3) == 42.3);
+static_assert(load(42.3f) == 42.3f);
+
+// pointers
+static_assert(load(&array[0]) == &array[0]);
+static_assert(load(&small_array[1]) == &small_array[1]);
+
+// -- STORE --
+
+template <typename T> consteval T store(T value) {
+  _Atomic(T) av = T{};
+  __c11_atomic_store(&av, value, __ATOMIC_RELAXED);
+  return __c11_atomic_load(&av, __ATOMIC_RELAXED);
+}
+
+// integers
+static_assert(store(true) == true);
+static_assert(store(false) == false);
+
+static_assert(store(42) == 42);
+static_assert(store(-128) == -128);
+
+static_assert(store(42u) == 42u);
+static_assert(store(0xFFFFFFFFu) == 0xFFFFFFFFu);
+
+// floats
+static_assert(store(42.3) == 42.3);
+static_assert(store(42.3f) == 42.3f);
+
+// pointers
+static_assert(store(&array[0]) == &array[0]);
+static_assert(store(&small_array[1]) == &small_array[1]);
+
+// -- EXCHANGE --
+template <typename T> struct two_values {
+  T before;
+  T after;
+  constexpr friend bool operator==(two_values, two_values) = default;
+};
+
+template <typename T> consteval auto exchange(T value, do_not_deduce<T> replacement) -> two_values<T> {
+  _Atomic(T) av = T{value};
+  T previous = __c11_atomic_exchange(&av, replacement, __ATOMIC_RELAXED);
+  return two_values<T>{previous, __c11_atomic_load(&av, __ATOMIC_RELAXED)};
+}
+
+// integers
+static_assert(exchange(true,false) == two_values{true, false});
+static_assert(exchange(false,true) == two_values{false, true});
+
+static_assert(exchange(10,42) == two_values{10,42});
+static_assert(exchange(14,-128) == two_values{14,-128});
+
+
+static_assert(exchange(56u,42u) == two_values{56u,42u});
+static_assert(exchange(0xFFu, 0xFFFFFFFFu) == two_values{0xFFu,0xFFFFFFFFu});
+
+// floats
+static_assert(exchange(42.3,1.2) == two_values{42.3,1.2});
+static_assert(exchange(42.3f,-16.7f) == two_values{42.3f, -16.7f});
+
+// pointers
+static_assert(exchange(&array[0], &array[1]) == two_values{&array[0],&array[1]});
+static_assert(exchange(&small_array[1], &small_array[0]) == two_values{&small_array[1], &small_array[0]});
+
+// -- COMPARE-EXCHANGE --
+template <typename T> struct comp_result {
+  bool success;
+  T output;
+  T after;
+  
+  constexpr comp_result(bool success_, T output_, do_not_deduce<T> after_): success{success_}, output{output_}, after{after_} { }
+  
+  constexpr friend bool operator==(comp_result, comp_result) = default;
+};
+
+template <typename T> constexpr auto comp_success(T output, do_not_deduce<T> after) {
+  return comp_result<T>{true, output, after};
+}
+
+template <typename T> constexpr auto comp_failure(T after) {
+  return comp_result<T>{false, after, after};
+}
+
+template <typename T> consteval auto compare_exchange_weak(T original, do_not_deduce<T> expected, do_not_deduce<T> replacement) -> comp_result<T> {
+  _Atomic(T) av = T{original};
+  const bool success = __c11_atomic_compare_exchange_weak(&av, &expected, replacement, __ATOMIC_RELAXED, __ATOMIC_RELAXED);
+  return comp_result<T>{success, expected, __c11_atomic_load(&av, __ATOMIC_RELAXED)};
+}
+
+// integers
+static_assert(compare_exchange_weak(true, true, false) == comp_success(true, false));
+static_assert(compare_exchange_weak(false, false, true) == comp_success(false, true));
+static_assert(compare_exchange_weak(false, true, false) == comp_failure(false));
+static_assert(compare_exchange_weak(true, false, true) == comp_failure(true));
+
+static_assert(compare_exchange_weak(10,10,42) == comp_success(10,42));
+static_assert(compare_exchange_weak(14,14,-128) == comp_success(14,-128));
+static_assert(compare_exchange_weak(-10,10,42) == comp_failure(-10));
+static_assert(compare_exchange_weak(-14,14,-128) == comp_failure(-14));
+
+static_assert(compare_exchange_weak(56u, 56u,42u) == comp_success(56u,42u));
+static_assert(compare_exchange_weak(0xFFu, 0xFFu, 0xFFFFFFFFu) == comp_success(0xFFu,0xFFFFFFFFu));
+static_assert(compare_exchange_weak(3u, 56u,42u) == comp_failure(3u));
+static_assert(compare_exchange_weak(0xFu, 0xFFu, 0xFFFFFFFFu) == comp_failure(0xFu));
+
+// floats
+static_assert(compare_exchange_weak(42.3, 42.3,1.2) == comp_success(42.3,1.2));
+static_assert(compare_exchange_weak(42.3f, 42.3f,-16.7f) == comp_success(42.3f, -16.7f));
+static_assert(compare_exchange_weak(0.0, 42.3,1.2) == comp_failure(0.0));
+static_assert(compare_exchange_weak(0.0f, 42.3f,-16.7f) == comp_failure(0.0f));
+
+// pointers
+static_assert(compare_exchange_weak(&array[0], &array[0], &array[1]) == comp_success(&array[0],&array[1]));
+static_assert(compare_exchange_weak(&small_array[1], &small_array[1], &small_array[0]) == comp_success(&small_array[1], &small_array[0]));
+static_assert(compare_exchange_weak(&array[1], &array[0], &array[1]) == comp_failure(&array[1]));
+static_assert(compare_exchange_weak(&small_array[0], &small_array[1], &small_array[0]) == comp_failure(&small_array[0]));
+
+
+template <typename T> consteval auto compare_exchange_strong(T original, do_not_deduce<T> expected, do_not_deduce<T> replacement) -> comp_result<T> {
+  _Atomic(T) av = T{original};
+  const bool success = __c11_atomic_compare_exchange_strong(&av, &expected, replacement, __ATOMIC_RELAXED, __ATOMIC_RELAXED);
+  return comp_result<T>{success, expected, __c11_atomic_load(&av, __ATOMIC_RELAXED)};
+}
+
+// integers
+static_assert(compare_exchange_strong(true, true, false) == comp_success(true, false));
+static_assert(compare_exchange_strong(false, false, true) == comp_success(false, true));
+static_assert(compare_exchange_strong(false, true, false) == comp_failure(false));
+static_assert(compare_exchange_strong(true, false, true) == comp_failure(true));
+
+static_assert(compare_exchange_strong(10,10,42) == comp_success(10,42));
+static_assert(compare_exchange_strong(14,14,-128) == comp_success(14,-128));
+static_assert(compare_exchange_strong(-10,10,42) == comp_failure(-10));
+static_assert(compare_exchange_strong(-14,14,-128) == comp_failure(-14));
+
+static_assert(compare_exchange_strong(56u, 56u,42u) == comp_success(56u,42u));
+static_assert(compare_exchange_strong(0xFFu, 0xFFu, 0xFFFFFFFFu) == comp_success(0xFFu,0xFFFFFFFFu));
+static_assert(compare_exchange_strong(3u, 56u,42u) == comp_failure(3u));
+static_assert(compare_exchange_strong(0xFu, 0xFFu, 0xFFFFFFFFu) == comp_failure(0xFu));
+
+// floats
+static_assert(compare_exchange_strong(42.3, 42.3,1.2) == comp_success(42.3,1.2));
+static_assert(compare_exchange_strong(42.3f, 42.3f,-16.7f) == comp_success(42.3f, -16.7f));
+static_assert(compare_exchange_strong(0.0, 42.3,1.2) == comp_failure(0.0));
+static_assert(compare_exchange_strong(0.0f, 42.3f,-16.7f) == comp_failure(0.0f));
+
+// pointers
+static_assert(compare_exchange_strong(&array[0], &array[0], &array[1]) == comp_success(&array[0],&array[1]));
+static_assert(compare_exchange_strong(&small_array[1], &small_array[1], &small_array[0]) == comp_success(&small_array[1], &small_array[0]));
+static_assert(compare_exchange_strong(&array[1], &array[0], &array[1]) == comp_failure(&array[1]));
+static_assert(compare_exchange_strong(&small_array[0], &small_array[1], &small_array[0]) == comp_failure(&small_array[0]));
+
+
+// --FETCH-OP--
+template <typename T, typename Y> consteval auto fetch_add(T original, Y arg) -> two_values<T> {
+  _Atomic(T) av = T{original};
+  const T result = __c11_atomic_fetch_add(&av, arg, __ATOMIC_RELAXED);
+  return two_values<T>{result, __c11_atomic_load(&av, __ATOMIC_RELAXED)};
+}
+
+// integers
+static_assert(fetch_add(false, 1) == two_values{false, true});
+static_assert(fetch_add(0, 100) == two_values{0, 100});
+static_assert(fetch_add(100, -50) == two_values{100, 50});
+
+static_assert(fetch_add(int_max, 1) == two_values{int_max, int_min}); // overflow is defined for atomic
+static_assert(fetch_add(int_min, -1) == two_values{int_min, int_max});
+
+// floats
+static_assert(fetch_add(10.3, 2.1) == two_values{10.3, 12.4});
+static_assert(fetch_add(10.3f, 2.1f) == two_values{10.3f, 12.4f});
+
+// pointers
+static_assert(fetch_add(&array[0], 1) == two_values{&array[0], &array[1]});
+static_assert(fetch_add(&small_array[0], 1) == two_values{&small_array[0], &small_array[1]});
+static_assert(fetch_add(&array[1], 0) == two_values{&array[1], &array[1]});
+static_assert(fetch_add(&small_array[1], 0) == two_values{&small_array[1], &small_array[1]});
+static_assert(fetch_add(&array[1], -1) == two_values{&array[1], &array[0]});
+static_assert(fetch_add(&small_array[1], -1) == two_values{&small_array[1], &small_array[0]});
+
+template <typename T, typename Y> consteval auto fetch_sub(T original, Y arg) -> two_values<T> {
+  _Atomic(T) av = T{original};
+  const T result = __c11_atomic_fetch_sub(&av, arg, __ATOMIC_RELAXED);
+  return two_values<T>{result, __c11_atomic_load(&av, __ATOMIC_RELAXED)};
+}
+
+// integers
+static_assert(fetch_sub(true, 1) == two_values{true, false});
+static_assert(fetch_sub(0, 100) == two_values{0, -100});
+static_assert(fetch_sub(100, -50) == two_values{100, 150});
+
+static_assert(fetch_sub(int_min, 1) == two_values{int_min, int_max}); // overflow is defined for atomic
+static_assert(fetch_sub(int_max, -1) == two_values{int_max, int_min});
+
+// floats
+static_assert(fetch_sub(10.3, 2.3) == two_values{10.3, 8.0});
+static_assert(fetch_sub(10.3f, 2.3f) == two_values{10.3f, 8.0f});
+
+// pointers
+static_assert(fetch_sub(&array[1], 1) == two_values{&array[1], &array[0]});
+static_assert(fetch_sub(&small_array[1], 1) == two_values{&small_array[1], &small_array[0]});
+static_assert(fetch_sub(&array[1], 0) == two_values{&array[1], &array[1]});
+static_assert(fetch_sub(&small_array[1], 0) == two_values{&small_array[1], &small_array[1]});
+static_assert(fetch_sub(&array[0], -1) == two_values{&array[0], &array[1]});
+static_assert(fetch_sub(&small_array[0], -1) == two_values{&small_array[0], &small_array[1]});
+
+template <typename T, typename Y> consteval auto fetch_and(T original, Y arg) -> two_values<T> {
+  _Atomic(T) av = T{original};
+  const T result = __c11_atomic_fetch_and(&av, arg, __ATOMIC_RELAXED);
+  return two_values<T>{result, __c11_atomic_load(&av, __ATOMIC_RELAXED)};
+}
+
+template <typename T, typename Y> consteval auto fetch_or(T original, Y arg) -> two_values<T> {
+  _Atomic(T) av = T{original};
+  const T result = __c11_atomic_fetch_or(&av, arg, __ATOMIC_RELAXED);
+  return two_values<T>{result, __c11_atomic_load(&av, __ATOMIC_RELAXED)};
+}
+
+template <typename T, typename Y> consteval auto fetch_xor(T original, Y arg) -> two_values<T> {
+  _Atomic(T) av = T{original};
+  const T result = __c11_atomic_fetch_xor(&av, arg, __ATOMIC_RELAXED);
+  return two_values<T>{result, __c11_atomic_load(&av, __ATOMIC_RELAXED)};
+}
+
+template <typename T, typename Y> consteval auto fetch_nand(T original, Y arg) -> two_values<T> {
+  _Atomic(T) av = T{original};
+  const T result = __c11_atomic_fetch_nand(&av, arg, __ATOMIC_RELAXED);
+  return two_values<T>{result, __c11_atomic_load(&av, __ATOMIC_RELAXED)};
+}
+
+static_assert(fetch_and(0b1101u, 0b1011u) == two_values{0b1101u, 0b1001u});
+static_assert(fetch_or(0b1101u, 0b1011u) == two_values{0b1101u, 0b1111u});
+static_assert(fetch_xor(0b1101u, 0b1011u) == two_values{0b1101u, 0b0110u});
+static_assert(fetch_nand(0b1001u, 0b1011u) == two_values{0b1001u, 0xFFFFFFF6u});
+
+template <typename T> consteval auto fetch_min(T original, T arg) -> two_values<T> {
+  _Atomic(T) av = T{original};
+  const T result = __c11_atomic_fetch_min(&av, arg, __ATOMIC_RELAXED);
+  return two_values<T>{result, __c11_atomic_load(&av, __ATOMIC_RELAXED)};
+}
+
+template <typename T> consteval auto fetch_max(T original, T arg) -> two_values<T> {
+  _Atomic(T) av = T{original};
+  const T result = __c11_atomic_fetch_max(&av, arg, __ATOMIC_RELAXED);
+  return two_values<T>{result, __c11_atomic_load(&av, __ATOMIC_RELAXED)};
+}
+
+static_assert(fetch_max(10, 16) == two_values{10, 16});
+static_assert(fetch_max(16, 10) == two_values{16, 16});
+
+static_assert(fetch_min(10, 16) == two_values{10, 10});
+static_assert(fetch_min(16, 10) == two_values{16, 10});
+
diff --git a/clang/test/SemaCXX/atomic-constexpr-gcc-builtins.cpp b/clang/test/SemaCXX/atomic-constexpr-gcc-builtins.cpp
new file mode 100644
index 00000000000000..a1d23294a2080e
--- /dev/null
+++ b/clang/test/SemaCXX/atomic-constexpr-gcc-builtins.cpp
@@ -0,0 +1,504 @@
+// RUN: %clang_cc1 -std=c++20 %s
+
+// expected-no-diagnostics
+
+constexpr int int_min = -2147483648;
+constexpr int int_max = 2147483647;
+
+const int array[2] = {1,2};
+const char small_array[2] = {1,2};
+
+template <typename T> struct identity {
+  using type = T;
+};
+
+template <typename T> using do_not_deduce = typename identity<T>::type;
+
+// -- SIGNAL and THREAD fence --
+consteval int fence_test(int v) {
+  // both are no-op in constexpr
+  __atomic_thread_fence(__ATOMIC_ACQUIRE); 
+  __atomic_signal_fence(__ATOMIC_ACQUIRE);
+  return v;
+}
+
+static_assert(fence_test(42) == 42);
+
+// -- LOAD --
+
+template <typename T> consteval T load(T value) {
+  T av = value;
+  T out{};
+  __atomic_load(&av, &out, __ATOMIC_RELAXED);
+  return out;
+}
+
+// integers
+static_assert(load(true) == true);
+static_assert(load(false) == false);
+
+static_assert(load(42) == 42);
+static_assert(load(-128) == -128);
+
+static_assert(load(42u) == 42u);
+static_assert(load(0xFFFFFFFFu) == 0xFFFFFFFFu);
+
+// pointers
+static_assert(load(&array[0]) == &array[0]);
+static_assert(load(&small_array[1]) == &small_array[1]);
+
+// -- LOAD-N --
+
+template <typename T> consteval T load_n(T value) {
+  T av = value;
+  return __atomic_load_n(&av, __ATOMIC_RELAXED);
+}
+
+// integers
+static_assert(load_n(true) == true);
+static_assert(load_n(false) == false);
+
+static_assert(load_n(42) == 42);
+static_assert(load_n(-128) == -128);
+
+static_assert(load_n(42u) == 42u);
+static_assert(load_n(0xFFFFFFFFu) == 0xFFFFFFFFu);
+
+// pointers
+static_assert(load_n(&array[0]) == &array[0]);
+static_assert(load_n(&small_array[1]) == &small_array[1]);
+
+
+// -- STORE --
+
+template <typename T> consteval T store(T value) {
+  T av = T{};
+  __atomic_store(&av, &value, __ATOMIC_RELAXED);
+  return __atomic_load_n(&av, __ATOMIC_RELAXED);
+}
+
+// integers
+static_assert(store(true) == true);
+static_assert(store(false) == false);
+
+static_assert(store(42) == 42);
+static_assert(store(-128) == -128);
+
+static_assert(store(42u) == 42u);
+static_assert(store(0xFFFFFFFFu) == 0xFFFFFFFFu);
+
+// pointers
+static_assert(store(&array[0]) == &array[0]);
+static_assert(store(&small_array[1]) == &small_array[1]);
+
+// -- STORE-N --
+
+template <typename T> consteval T store_n(T value) {
+  T av = T{};
+  __atomic_store_n(&av, value, __ATOMIC_RELAXED);
+  return __atomic_load_n(&av, __ATOMIC_RELAXED);
+}
+
+// integers
+static_assert(store_n(true) == true);
+static_assert(store_n(false) == false);
+
+static_assert(store_n(42) == 42);
+static_assert(store_n(-128) == -128);
+
+static_assert(store_n(42u) == 42u);
+static_assert(store_n(0xFFFFFFFFu) == 0xFFFFFFFFu);
+
+// pointers
+static_assert(store_n(&array[0]) == &array[0]);
+static_assert(store_n(&small_array[1]) == &small_array[1]);
+
+// -- EXCHANGE --
+template <typename T> struct two_values {
+  T before;
+  T after;
+  constexpr friend bool operator==(two_values, two_values) = default;
+};
+
+template <typename T> consteval auto exchange(T value, do_not_deduce<T> replacement) -> two_values<T> {
+  T av = T{value};
+  T out{};
+  __atomic_exchange(&av, &replacement, &out, __ATOMIC_RELAXED);
+  return two_values<T>{out, __atomic_load_n(&av, __ATOMIC_RELAXED)};
+}
+
+// integers
+static_assert(exchange(true,false) == two_values{true, false});
+static_assert(exchange(false,true) == two_values{false, true});
+
+static_assert(exchange(10,42) == two_values{10,42});
+static_assert(exchange(14,-128) == two_values{14,-128});
+
+
+static_assert(exchange(56u,42u) == two_values{56u,42u});
+static_assert(exchange(0xFFu, 0xFFFFFFFFu) == two_values{0xFFu,0xFFFFFFFFu});
+
+// -- EXCHANGE-N --
+template <typename T> consteval auto exchange_n(T value, do_not_deduce<T> replacement) -> two_values<T> {
+  T av = T{value};
+  T previous = __atomic_exchange_n(&av, replacement, __ATOMIC_RELAXED);
+  return two_values<T>{previous, __atomic_load_n(&av, __ATOMIC_RELAXED)};
+}
+
+// integers
+static_assert(exchange_n(true,false) == two_values{true, false});
+static_assert(exchange_n(false,true) == two_values{false, true});
+
+static_assert(exchange_n(10,42) == two_values{10,42});
+static_assert(exchange_n(14,-128) == two_values{14,-128});
+
+
+static_assert(exchange_n(56u,42u) == two_values{56u,42u});
+static_assert(exchange_n(0xFFu, 0xFFFFFFFFu) == two_values{0xFFu,0xFFFFFFFFu});
+
+// pointers
+static_assert(exchange_n(&array[0], &array[1]) == two_values{&array[0],&array[1]});
+static_assert(exchange_n(&small_array[1], &small_array[0]) == two_values{&small_array[1], &small_array[0]});
+
+// -- COMPARE-EXCHANGE --
+template <typename T> struct comp_result {
+  bool success;
+  T output;
+  T after;
+  
+  constexpr comp_result(bool success_, T output_, do_not_deduce<T> after_): success{success_}, output{output_}, after{after_} { }
+  
+  constexpr friend bool operator==(comp_result, comp_result) = default;
+};
+
+template <typename T> constexpr auto comp_success(T output, do_not_deduce<T> after) {
+  return comp_result<T>{true, output, after};
+}
+
+template <typename T> constexpr auto comp_failure(T after) {
+  return comp_result<T>{false, after, after};
+}
+
+template <typename T> consteval auto compare_exchange_weak(T original, do_not_deduce<T> expected, do_not_deduce<T> replacement) -> comp_result<T> {
+  T av = T{original};
+  const bool success = __atomic_compare_exchange(&av, &expected, &replacement, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED);
+  return comp_result<T>{success, expected, __atomic_load_n(&av, __ATOMIC_RELAXED)};
+}
+
+// integers
+static_assert(compare_exchange_weak(true, true, false) == comp_success(true, false));
+static_assert(compare_exchange_weak(false, false, true) == comp_success(false, true));
+static_assert(compare_exchange_weak(false, true, false) == comp_failure(false));
+static_assert(compare_exchange_weak(true, false, true) == comp_failure(true));
+
+static_assert(compare_exchange_weak(10,10,42) == comp_success(10,42));
+static_assert(compare_exchange_weak(14,14,-128) == comp_success(14,-128));
+static_assert(compare_exchange_weak(-10,10,42) == comp_failure(-10));
+static_assert(compare_exchange_weak(-14,14,-128) == comp_failure(-14));
+
+static_assert(compare_exchange_weak(56u, 56u,42u) == comp_success(56u,42u));
+static_assert(compare_exchange_weak(0xFFu, 0xFFu, 0xFFFFFFFFu) == comp_success(0xFFu,0xFFFFFFFFu));
+static_assert(compare_exchange_weak(3u, 56u,42u) == comp_failure(3u));
+static_assert(compare_exchange_weak(0xFu, 0xFFu, 0xFFFFFFFFu) == comp_failure(0xFu));
+
+// pointers
+static_assert(compare_exchange_weak(&array[0], &array[0], &array[1]) == comp_success(&array[0],&array[1]));
+static_assert(compare_exchange_weak(&small_array[1], &small_array[1], &small_array[0]) == comp_success(&small_array[1], &small_array[0]));
+static_assert(compare_exchange_weak(&array[1], &array[0], &array[1]) == comp_failure(&array[1]));
+static_assert(compare_exchange_weak(&small_array[0], &small_array[1], &small_array[0]) == comp_failure(&small_array[0]));
+
+
+template <typename T> consteval auto compare_exchange_strong(T original, do_not_deduce<T> expected, do_not_deduce<T> replacement) -> comp_result<T> {
+  T av = T{original};
+  const bool success = __atomic_compare_exchange(&av, &expected, &replacement, true, __ATOMIC_RELAXED, __ATOMIC_RELAXED);
+  return comp_result<T>{success, expected, __atomic_load_n(&av, __ATOMIC_RELAXED)};
+}
+
+// integers
+static_assert(compare_exchange_strong(true, true, false) == comp_success(true, false));
+static_assert(compare_exchange_strong(false, false, true) == comp_success(false, true));
+static_assert(compare_exchange_strong(false, true, false) == comp_failure(false));
+static_assert(compare_exchange_strong(true, false, true) == comp_failure(true));
+
+static_assert(compare_exchange_strong(10,10,42) == comp_success(10,42));
+static_assert(compare_exchange_strong(14,14,-128) == comp_success(14,-128));
+static_assert(compare_exchange_strong(-10,10,42) == comp_failure(-10));
+static_assert(compare_exchange_strong(-14,14,-128) == comp_failure(-14));
+
+static_assert(compare_exchange_strong(56u, 56u,42u) == comp_success(56u,42u));
+static_assert(compare_exchange_strong(0xFFu, 0xFFu, 0xFFFFFFFFu) == comp_success(0xFFu,0xFFFFFFFFu));
+static_assert(compare_exchange_strong(3u, 56u,42u) == comp_failure(3u));
+static_assert(compare_exchange_strong(0xFu, 0xFFu, 0xFFFFFFFFu) == comp_failure(0xFu));
+
+// pointers
+static_assert(compare_exchange_strong(&array[0], &array[0], &array[1]) == comp_success(&array[0],&array[1]));
+static_assert(compare_exchange_strong(&small_array[1], &small_array[1], &small_array[0]) == comp_success(&small_array[1], &small_array[0]));
+static_assert(compare_exchange_strong(&array[1], &array[0], &array[1]) == comp_failure(&array[1]));
+static_assert(compare_exchange_strong(&small_array[0], &small_array[1], &small_array[0]) == comp_failure(&small_array[0]));
+
+// --COMPARE-EXCHANGE-N--
+
+template <typename T> consteval auto compare_exchange_weak_n(T original, do_not_deduce<T> expected, do_not_deduce<T> replacement) -> comp_result<T> {
+  T av = T{original};
+  const bool success = __atomic_compare_exchange_n(&av, &expected, replacement, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED);
+  return comp_result<T>{success, expected, __atomic_load_n(&av, __ATOMIC_RELAXED)};
+}
+
+// integers
+static_assert(compare_exchange_weak_n(true, true, false) == comp_success(true, false));
+static_assert(compare_exchange_weak_n(false, false, true) == comp_success(false, true));
+static_assert(compare_exchange_weak_n(false, true, false) == comp_failure(false));
+static_assert(compare_exchange_weak_n(true, false, true) == comp_failure(true));
+
+static_assert(compare_exchange_weak_n(10,10,42) == comp_success(10,42));
+static_assert(compare_exchange_weak_n(14,14,-128) == comp_success(14,-128));
+static_assert(compare_exchange_weak_n(-10,10,42) == comp_failure(-10));
+static_assert(compare_exchange_weak_n(-14,14,-128) == comp_failure(-14));
+
+static_assert(compare_exchange_weak_n(56u, 56u,42u) == comp_success(56u,42u));
+static_assert(compare_exchange_weak_n(0xFFu, 0xFFu, 0xFFFFFFFFu) == comp_success(0xFFu,0xFFFFFFFFu));
+static_assert(compare_exchange_weak_n(3u, 56u,42u) == comp_failure(3u));
+static_assert(compare_exchange_weak_n(0xFu, 0xFFu, 0xFFFFFFFFu) == comp_failure(0xFu));
+
+// pointers
+static_assert(compare_exchange_weak_n(&array[0], &array[0], &array[1]) == comp_success(&array[0],&array[1]));
+static_assert(compare_exchange_weak_n(&small_array[1], &small_array[1], &small_array[0]) == comp_success(&small_array[1], &small_array[0]));
+static_assert(compare_exchange_weak_n(&array[1], &array[0], &array[1]) == comp_failure(&array[1]));
+static_assert(compare_exchange_weak_n(&small_array[0], &small_array[1], &small_array[0]) == comp_failure(&small_array[0]));
+
+
+template <typename T> consteval auto compare_exchange_strong_n(T original, do_not_deduce<T> expected, do_not_deduce<T> replacement) -> comp_result<T> {
+  T av = T{original};
+  const bool success = __atomic_compare_exchange_n(&av, &expected, replacement, true, __ATOMIC_RELAXED, __ATOMIC_RELAXED);
+  return comp_result<T>{success, expected, __atomic_load_n(&av, __ATOMIC_RELAXED)};
+}
+
+// integers
+static_assert(compare_exchange_strong_n(true, true, false) == comp_success(true, false));
+static_assert(compare_exchange_strong_n(false, false, true) == comp_success(false, true));
+static_assert(compare_exchange_strong_n(false, true, false) == comp_failure(false));
+static_assert(compare_exchange_strong_n(true, false, true) == comp_failure(true));
+
+static_assert(compare_exchange_strong_n(10,10,42) == comp_success(10,42));
+static_assert(compare_exchange_strong_n(14,14,-128) == comp_success(14,-128));
+static_assert(compare_exchange_strong_n(-10,10,42) == comp_failure(-10));
+static_assert(compare_exchange_strong_n(-14,14,-128) == comp_failure(-14));
+
+static_assert(compare_exchange_strong_n(56u, 56u,42u) == comp_success(56u,42u));
+static_assert(compare_exchange_strong_n(0xFFu, 0xFFu, 0xFFFFFFFFu) == comp_success(0xFFu,0xFFFFFFFFu));
+static_assert(compare_exchange_strong_n(3u, 56u,42u) == comp_failure(3u));
+static_assert(compare_exchange_strong_n(0xFu, 0xFFu, 0xFFFFFFFFu) == comp_failure(0xFu));
+
+// pointers
+static_assert(compare_exchange_strong_n(&array[0], &array[0], &array[1]) == comp_success(&array[0],&array[1]));
+static_assert(compare_exchange_strong_n(&small_array[1], &small_array[1], &small_array[0]) == comp_success(&small_array[1], &small_array[0]));
+static_assert(compare_exchange_strong_n(&array[1], &array[0], &array[1]) == comp_failure(&array[1]));
+static_assert(compare_exchange_strong_n(&small_array[0], &small_array[1], &small_array[0]) == comp_failure(&small_array[0]));
+
+// --FETCH-OP--
+template <typename T, typename Y> consteval auto fetch_add(T original, Y arg) -> two_values<T> {
+  T av = T{original};
+  const T result = __atomic_fetch_add(&av, arg, __ATOMIC_RELAXED);
+  return two_values<T>{result, __atomic_load_n(&av, __ATOMIC_RELAXED)};
+}
+
+template <typename T, typename Y> consteval auto fetch_add_ptr(T original, Y arg) -> two_values<T> {
+  T av = T{original};
+  constexpr auto pointee_size = sizeof(*static_cast<T>(nullptr));
+  arg *= pointee_size;
+  const T result = __atomic_fetch_add(&av, arg, __ATOMIC_RELAXED);
+  return two_values<T>{result, __atomic_load_n(&av, __ATOMIC_RELAXED)};
+}
+
+// integers
+static_assert(fetch_add(false, 1) == two_values{false, true});
+static_assert(fetch_add(0, 100) == two_values{0, 100});
+static_assert(fetch_add(100, -50) == two_values{100, 50});
+
+static_assert(fetch_add(int_max, 1) == two_values{int_max, int_min}); // overflow is defined for atomic
+static_assert(fetch_add(int_min, -1) == two_values{int_min, int_max});
+
+// pointers
+static_assert(fetch_add_ptr(&array[0], 1) == two_values{&array[0], &array[1]});
+static_assert(fetch_add_ptr(&small_array[0], 1) == two_values{&small_array[0], &small_array[1]});
+static_assert(fetch_add_ptr(&array[1], 0) == two_values{&array[1], &array[1]});
+static_assert(fetch_add_ptr(&small_array[1], 0) == two_values{&small_array[1], &small_array[1]});
+static_assert(fetch_add_ptr(&array[1], -1) == two_values{&array[1], &array[0]});
+static_assert(fetch_add_ptr(&small_array[1], -1) == two_values{&small_array[1], &small_array[0]});
+
+template <typename T, typename Y> consteval auto fetch_sub(T original, Y arg) -> two_values<T> {
+  T av = T{original};
+  const T result = __atomic_fetch_sub(&av, arg, __ATOMIC_RELAXED);
+  return two_values<T>{result, __atomic_load_n(&av, __ATOMIC_RELAXED)};
+}
+
+template <typename T, typename Y> consteval auto fetch_sub_ptr(T original, Y arg) -> two_values<T> {
+  T av = T{original};
+  constexpr auto pointee_size = sizeof(*static_cast<T>(nullptr));
+  arg *= pointee_size;
+  const T result = __atomic_fetch_sub(&av, arg, __ATOMIC_RELAXED);
+  return two_values<T>{result, __atomic_load_n(&av, __ATOMIC_RELAXED)};
+}
+
+// integers
+static_assert(fetch_sub(true, 1) == two_values{true, false});
+static_assert(fetch_sub(0, 100) == two_values{0, -100});
+static_assert(fetch_sub(100, -50) == two_values{100, 150});
+
+static_assert(fetch_sub(int_min, 1) == two_values{int_min, int_max}); // overflow is defined for atomic
+static_assert(fetch_sub(int_max, -1) == two_values{int_max, int_min});
+
+// pointers
+static_assert(fetch_sub_ptr(&array[1], 1) == two_values{&array[1], &array[0]});
+static_assert(fetch_sub_ptr(&small_array[1], 1) == two_values{&small_array[1], &small_array[0]});
+static_assert(fetch_sub_ptr(&array[1], 0) == two_values{&array[1], &array[1]});
+static_assert(fetch_sub_ptr(&small_array[1], 0) == two_values{&small_array[1], &small_array[1]});
+static_assert(fetch_sub_ptr(&array[0], -1) == two_values{&array[0], &array[1]});
+static_assert(fetch_sub_ptr(&small_array[0], -1) == two_values{&small_array[0], &small_array[1]});
+
+template <typename T, typename Y> consteval auto fetch_and(T original, Y arg) -> two_values<T> {
+  T av = T{original};
+  const T result = __atomic_fetch_and(&av, arg, __ATOMIC_RELAXED);
+  return two_values<T>{result, __atomic_load_n(&av, __ATOMIC_RELAXED)};
+}
+
+template <typename T, typename Y> consteval auto fetch_or(T original, Y arg) -> two_values<T> {
+  T av = T{original};
+  const T result = __atomic_fetch_or(&av, arg, __ATOMIC_RELAXED);
+  return two_values<T>{result, __atomic_load_n(&av, __ATOMIC_RELAXED)};
+}
+
+template <typename T, typename Y> consteval auto fetch_xor(T original, Y arg) -> two_values<T> {
+  T av = T{original};
+  const T result = __atomic_fetch_xor(&av, arg, __ATOMIC_RELAXED);
+  return two_values<T>{result, __atomic_load_n(&av, __ATOMIC_RELAXED)};
+}
+
+template <typename T, typename Y> consteval auto fetch_nand(T original, Y arg) -> two_values<T> {
+  T av = T{original};
+  const T result = __atomic_fetch_nand(&av, arg, __ATOMIC_RELAXED);
+  return two_values<T>{result, __atomic_load_n(&av, __ATOMIC_RELAXED)};
+}
+
+static_assert(fetch_and(0b1101u, 0b1011u) == two_values{0b1101u, 0b1001u});
+static_assert(fetch_or(0b1101u, 0b1011u) == two_values{0b1101u, 0b1111u});
+static_assert(fetch_xor(0b1101u, 0b1011u) == two_values{0b1101u, 0b0110u});
+static_assert(fetch_nand(0b1001u, 0b1011u) == two_values{0b1001u, 0xFFFFFFF6u});
+
+template <typename T> consteval auto fetch_min(T original, T arg) -> two_values<T> {
+  T av = T{original};
+  const T result = __atomic_fetch_min(&av, arg, __ATOMIC_RELAXED);
+  return two_values<T>{result, __atomic_load_n(&av, __ATOMIC_RELAXED)};
+}
+
+template <typename T> consteval auto fetch_max(T original, T arg) -> two_values<T> {
+  T av = T{original};
+  const T result = __atomic_fetch_max(&av, arg, __ATOMIC_RELAXED);
+  return two_values<T>{result, __atomic_load_n(&av, __ATOMIC_RELAXED)};
+}
+
+static_assert(fetch_max(10, 16) == two_values{10, 16});
+static_assert(fetch_max(16, 10) == two_values{16, 16});
+
+static_assert(fetch_min(10, 16) == two_values{10, 10});
+static_assert(fetch_min(16, 10) == two_values{16, 10});
+
+// --OP-FETCHP--
+template <typename T, typename Y> consteval auto add_fetch(T original, Y arg) -> T {
+  T av = T{original};
+  return __atomic_add_fetch(&av, arg, __ATOMIC_RELAXED);
+}
+
+template <typename T, typename Y> consteval auto add_fetch_ptr(T original, Y arg) -> T {
+  T av = T{original};
+  constexpr auto pointee_size = sizeof(*static_cast<T>(nullptr));
+  arg *= pointee_size;
+  return __atomic_add_fetch(&av, arg, __ATOMIC_RELAXED);
+}
+
+// integers
+static_assert(add_fetch(false, 1) == true);
+static_assert(add_fetch(0, 100) == 100);
+static_assert(add_fetch(100, -50) == 50);
+
+static_assert(add_fetch(int_max, 1) == int_min); // overflow is defined for atomic
+static_assert(add_fetch(int_min, -1) == int_max);
+
+// pointers
+static_assert(add_fetch_ptr(&array[0], 1) == &array[1]);
+static_assert(add_fetch_ptr(&small_array[0], 1) == &small_array[1]);
+static_assert(add_fetch_ptr(&array[1], 0) == &array[1]);
+static_assert(add_fetch_ptr(&small_array[1], 0) == &small_array[1]);
+static_assert(add_fetch_ptr(&array[1], -1) == &array[0]);
+static_assert(add_fetch_ptr(&small_array[1], -1) ==&small_array[0]);
+
+template <typename T, typename Y> consteval auto sub_fetch(T original, Y arg) -> T {
+  T av = T{original};
+  return __atomic_sub_fetch(&av, arg, __ATOMIC_RELAXED);
+}
+
+template <typename T, typename Y> consteval auto sub_fetch_ptr(T original, Y arg) -> T {
+  T av = T{original};
+  constexpr auto pointee_size = sizeof(*static_cast<T>(nullptr));
+  arg *= pointee_size;
+  return __atomic_sub_fetch(&av, arg, __ATOMIC_RELAXED);
+}
+
+// integers
+static_assert(sub_fetch(true, 1) == false);
+static_assert(sub_fetch(0, 100) == -100);
+static_assert(sub_fetch(100, -50) == 150);
+
+static_assert(sub_fetch(int_min, 1) == int_max); // overflow is defined for atomic
+static_assert(sub_fetch(int_max, -1) == int_min);
+
+// pointers
+static_assert(sub_fetch_ptr(&array[1], 1) == &array[0]);
+static_assert(sub_fetch_ptr(&small_array[1], 1) == &small_array[0]);
+static_assert(sub_fetch_ptr(&array[1], 0) == &array[1]);
+static_assert(sub_fetch_ptr(&small_array[1], 0) == &small_array[1]);
+static_assert(sub_fetch_ptr(&array[0], -1) == &array[1]);
+static_assert(sub_fetch_ptr(&small_array[0], -1) == &small_array[1]);
+
+template <typename T, typename Y> consteval auto and_fetch(T original, Y arg) -> T {
+  T av = T{original};
+  return __atomic_and_fetch(&av, arg, __ATOMIC_RELAXED);
+}
+
+template <typename T, typename Y> consteval auto or_fetch(T original, Y arg) -> T {
+  T av = T{original};
+  return __atomic_or_fetch(&av, arg, __ATOMIC_RELAXED);
+}
+
+template <typename T, typename Y> consteval auto xor_fetch(T original, Y arg) -> T {
+  T av = T{original};
+  return __atomic_xor_fetch(&av, arg, __ATOMIC_RELAXED);
+}
+
+template <typename T, typename Y> consteval auto nand_fetch(T original, Y arg) -> T {
+  T av = T{original};
+  return __atomic_nand_fetch(&av, arg, __ATOMIC_RELAXED);
+}
+
+static_assert(and_fetch(0b1101u, 0b1011u) == 0b1001u);
+static_assert(or_fetch(0b1101u, 0b1011u) == 0b1111u);
+static_assert(xor_fetch(0b1101u, 0b1011u) == 0b0110u);
+static_assert(nand_fetch(0b1001u, 0b1011u) == 0xFFFFFFF6u);
+
+template <typename T> consteval auto min_fetch(T original, T arg) -> T {
+  T av = T{original};
+  return __atomic_min_fetch(&av, arg, __ATOMIC_RELAXED);
+}
+
+template <typename T> consteval auto max_fetch(T original, T arg) -> T {
+  T av = T{original};
+  return __atomic_max_fetch(&av, arg, __ATOMIC_RELAXED);
+}
+
+static_assert(max_fetch(10, 16) == 16);
+static_assert(max_fetch(16, 10) == 16);
+
+static_assert(min_fetch(10, 16) == 10);
+static_assert(min_fetch(16, 10) == 10);
+
+

>From 1994a552f3de9c9d94d8a89b0ab5847a8525592d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hana=20Dusi=CC=81kova=CC=81?= <hanicka at hanicka.net>
Date: Wed, 8 Jan 2025 12:02:22 +0100
Subject: [PATCH 2/6] [clang] refactor EvaluateAtomic* builtin support
 functions. Comments are now full sentences. Add Diagnostics for GCC's atomic
 misaligned op. Reuse pointer comparison code instead of duplicating.

---
 .../include/clang/Basic/DiagnosticASTKinds.td |   2 +
 clang/lib/AST/ExprConstant.cpp                | 563 +++++++++---------
 2 files changed, 280 insertions(+), 285 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index f630698757c5fb..6d3611fa1fc574 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -389,6 +389,8 @@ def note_constexpr_assumption_failed : Note<
   "assumption evaluated to false">;
 def err_experimental_clang_interp_failed : Error<
   "the experimental clang interpreter failed to evaluate an expression">;
+def note_unaligned_atomic_pointer_op : Note<
+  "atomic pointer operation with argument %0 not aligned to size of pointee type (sizeof %1 is %2)">;
 
 def warn_integer_constant_overflow : Warning<
   "overflow in expression; result is %0 with type %1">,
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 54c0000f05cb25..a428a5c75808ab 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -1921,16 +1921,14 @@ static bool EvaluateFixedPointOrInteger(const Expr *E, APFixedPoint &Result,
 static bool EvaluateFixedPoint(const Expr *E, APFixedPoint &Result,
                                EvalInfo &Info);
 
-/// Support for atomic builtins
+/// Support for atomic builtins.
 static bool EvaluateAtomicOrder(const AtomicExpr *E, EvalInfo &Info);
-static bool EvaluateAtomicWeak(const AtomicExpr *E, EvalInfo &Info);
 static bool EvaluateAtomicLoad(const AtomicExpr *E, APValue &Result,
                                EvalInfo &Info);
-static bool EvaluatePointerAndStoreValueInto(Expr *ResultPtr,
-                                             APValue &ValueToStore,
-                                             EvalInfo &Info);
 static bool EvaluateAtomicLoadInto(const AtomicExpr *E, EvalInfo &Info);
 static bool EvaluateAtomicStore(const AtomicExpr *E, EvalInfo &Info);
+static bool EvaluateAtomicExchange(const AtomicExpr *E, APValue &Result,
+                                   EvalInfo &Info);
 static bool EvaluateAtomicExchangeInto(const AtomicExpr *E, EvalInfo &Info);
 static bool EvaluateAtomicFetchOp(const AtomicExpr *E, APValue &Result,
                                   EvalInfo &Info, bool StoreToResultAfter);
@@ -8113,9 +8111,7 @@ class ExprEvaluatorBase
       return DerivedSuccess(LocalResult, E);
     case AtomicExpr::AO__c11_atomic_exchange:
     case AtomicExpr::AO__atomic_exchange_n:
-      if (!EvaluateAtomicLoad(E, LocalResult, Info))
-        return Error(E);
-      if (!EvaluateAtomicStore(E, Info))
+      if (!EvaluateAtomicExchange(E, LocalResult, Info))
         return Error(E);
       return DerivedSuccess(LocalResult, E);
     case AtomicExpr::AO__c11_atomic_fetch_add:
@@ -14203,6 +14199,158 @@ enum class CmpResult {
 };
 }
 
+template <class SuccessCB>
+static bool EvaluateLValueComparison(EvalInfo &Info, const Expr *E,
+                                     BinaryOperator::Opcode Opcode,
+                                     QualType LHSTy, LValue &LHSValue,
+                                     QualType RHSTy, LValue &RHSValue,
+                                     SuccessCB &&Success) {
+  auto Error = [&](const Expr *E) {
+    Info.FFDiag(E, diag::note_invalid_subexpr_in_const_expr);
+    return false;
+  };
+
+  // Reject differing bases from the normal codepath; we special-case
+  // comparisons to null.
+  if (!HasSameBase(LHSValue, RHSValue)) {
+    auto DiagComparison = [&](unsigned DiagID, bool Reversed = false) {
+      std::string LHS = LHSValue.toString(Info.Ctx, LHSTy);
+      std::string RHS = RHSValue.toString(Info.Ctx, RHSTy);
+      Info.FFDiag(E, DiagID)
+          << (Reversed ? RHS : LHS) << (Reversed ? LHS : RHS);
+      return false;
+    };
+    // Inequalities and subtractions between unrelated pointers have
+    // unspecified or undefined behavior.
+    if (!BinaryOperator::isEqualityOp(Opcode))
+      return DiagComparison(
+          diag::note_constexpr_pointer_comparison_unspecified);
+    // A constant address may compare equal to the address of a symbol.
+    // The one exception is that address of an object cannot compare equal
+    // to a null pointer constant.
+    // TODO: Should we restrict this to actual null pointers, and exclude the
+    // case of zero cast to pointer type?
+    if ((!LHSValue.Base && !LHSValue.Offset.isZero()) ||
+        (!RHSValue.Base && !RHSValue.Offset.isZero()))
+      return DiagComparison(diag::note_constexpr_pointer_constant_comparison,
+                            !RHSValue.Base);
+    // C++2c [intro.object]/10:
+    //   Two objects [...] may have the same address if [...] they are both
+    //   potentially non-unique objects.
+    // C++2c [intro.object]/9:
+    //   An object is potentially non-unique if it is a string literal object,
+    //   the backing array of an initializer list, or a subobject thereof.
+    //
+    // This makes the comparison result unspecified, so it's not a constant
+    // expression.
+    //
+    // TODO: Do we need to handle the initializer list case here?
+    if (ArePotentiallyOverlappingStringLiterals(Info, LHSValue, RHSValue))
+      return DiagComparison(diag::note_constexpr_literal_comparison);
+    if (IsOpaqueConstantCall(LHSValue) || IsOpaqueConstantCall(RHSValue))
+      return DiagComparison(diag::note_constexpr_opaque_call_comparison,
+                            !IsOpaqueConstantCall(LHSValue));
+    // We can't tell whether weak symbols will end up pointing to the same
+    // object.
+    if (IsWeakLValue(LHSValue) || IsWeakLValue(RHSValue))
+      return DiagComparison(diag::note_constexpr_pointer_weak_comparison,
+                            !IsWeakLValue(LHSValue));
+    // We can't compare the address of the start of one object with the
+    // past-the-end address of another object, per C++ DR1652.
+    if (LHSValue.Base && LHSValue.Offset.isZero() &&
+        isOnePastTheEndOfCompleteObject(Info.Ctx, RHSValue))
+      return DiagComparison(diag::note_constexpr_pointer_comparison_past_end,
+                            true);
+    if (RHSValue.Base && RHSValue.Offset.isZero() &&
+        isOnePastTheEndOfCompleteObject(Info.Ctx, LHSValue))
+      return DiagComparison(diag::note_constexpr_pointer_comparison_past_end,
+                            false);
+    // We can't tell whether an object is at the same address as another
+    // zero sized object.
+    if ((RHSValue.Base && isZeroSized(LHSValue)) ||
+        (LHSValue.Base && isZeroSized(RHSValue)))
+      return DiagComparison(diag::note_constexpr_pointer_comparison_zero_sized);
+    return Success(CmpResult::Unequal);
+  }
+
+  const CharUnits &LHSOffset = LHSValue.getLValueOffset();
+  const CharUnits &RHSOffset = RHSValue.getLValueOffset();
+
+  SubobjectDesignator &LHSDesignator = LHSValue.getLValueDesignator();
+  SubobjectDesignator &RHSDesignator = RHSValue.getLValueDesignator();
+
+  // C++11 [expr.rel]p2:
+  // - If two pointers point to non-static data members of the same object,
+  //   or to subobjects or array elements fo such members, recursively, the
+  //   pointer to the later declared member compares greater provided the
+  //   two members have the same access control and provided their class is
+  //   not a union.
+  //   [...]
+  // - Otherwise pointer comparisons are unspecified.
+  if (!LHSDesignator.Invalid && !RHSDesignator.Invalid &&
+      BinaryOperator::isRelationalOp(Opcode)) {
+    bool WasArrayIndex;
+    unsigned Mismatch = FindDesignatorMismatch(
+        getType(LHSValue.Base), LHSDesignator, RHSDesignator, WasArrayIndex);
+    // At the point where the designators diverge, the comparison has a
+    // specified value if:
+    //  - we are comparing array indices
+    //  - we are comparing fields of a union, or fields with the same access
+    // Otherwise, the result is unspecified and thus the comparison is not a
+    // constant expression.
+    if (!WasArrayIndex && Mismatch < LHSDesignator.Entries.size() &&
+        Mismatch < RHSDesignator.Entries.size()) {
+      const FieldDecl *LF = getAsField(LHSDesignator.Entries[Mismatch]);
+      const FieldDecl *RF = getAsField(RHSDesignator.Entries[Mismatch]);
+      if (!LF && !RF)
+        Info.CCEDiag(E, diag::note_constexpr_pointer_comparison_base_classes);
+      else if (!LF)
+        Info.CCEDiag(E, diag::note_constexpr_pointer_comparison_base_field)
+            << getAsBaseClass(LHSDesignator.Entries[Mismatch])
+            << RF->getParent() << RF;
+      else if (!RF)
+        Info.CCEDiag(E, diag::note_constexpr_pointer_comparison_base_field)
+            << getAsBaseClass(RHSDesignator.Entries[Mismatch])
+            << LF->getParent() << LF;
+      else if (!LF->getParent()->isUnion() &&
+               LF->getAccess() != RF->getAccess())
+        Info.CCEDiag(E,
+                     diag::note_constexpr_pointer_comparison_differing_access)
+            << LF << LF->getAccess() << RF << RF->getAccess()
+            << LF->getParent();
+    }
+  }
+
+  // The comparison here must be unsigned, and performed with the same
+  // width as the pointer.
+  unsigned PtrSize = Info.Ctx.getTypeSize(LHSTy);
+  uint64_t CompareLHS = LHSOffset.getQuantity();
+  uint64_t CompareRHS = RHSOffset.getQuantity();
+  assert(PtrSize <= 64 && "Unexpected pointer width");
+  uint64_t Mask = ~0ULL >> (64 - PtrSize);
+  CompareLHS &= Mask;
+  CompareRHS &= Mask;
+
+  // If there is a base and this is a relational operator, we can only
+  // compare pointers within the object in question; otherwise, the result
+  // depends on where the object is located in memory.
+  if (!LHSValue.Base.isNull() && BinaryOperator::isRelationalOp(Opcode)) {
+    QualType BaseTy = getType(LHSValue.Base);
+    if (BaseTy->isIncompleteType())
+      return Error(E);
+    CharUnits Size = Info.Ctx.getTypeSizeInChars(BaseTy);
+    uint64_t OffsetLimit = Size.getQuantity();
+    if (CompareLHS > OffsetLimit || CompareRHS > OffsetLimit)
+      return Error(E);
+  }
+
+  if (CompareLHS < CompareRHS)
+    return Success(CmpResult::Less);
+  if (CompareLHS > CompareRHS)
+    return Success(CmpResult::Greater);
+  return Success(CmpResult::Equal);
+}
+
 template <class SuccessCB, class AfterCB>
 static bool
 EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
@@ -14212,12 +14360,6 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
   assert((E->getOpcode() == BO_Cmp ||
           E->getType()->isIntegralOrEnumerationType()) &&
          "unsupported binary expression evaluation");
-  auto Error = [&](const Expr *E) {
-    Info.FFDiag(E, diag::note_invalid_subexpr_in_const_expr);
-    return false;
-  };
-
-  bool IsRelational = E->isRelationalOp() || E->getOpcode() == BO_Cmp;
   bool IsEquality = E->isEqualityOp();
 
   QualType LHSTy = E->getLHS()->getType();
@@ -14232,10 +14374,10 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
     if (!EvaluateInteger(E->getRHS(), RHS, Info) || !LHSOK)
       return false;
     if (LHS < RHS)
-      return Success(CmpResult::Less, E);
+      return Success(CmpResult::Less);
     if (LHS > RHS)
-      return Success(CmpResult::Greater, E);
-    return Success(CmpResult::Equal, E);
+      return Success(CmpResult::Greater);
+    return Success(CmpResult::Equal);
   }
 
   if (LHSTy->isFixedPointType() || RHSTy->isFixedPointType()) {
@@ -14248,10 +14390,10 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
     if (!EvaluateFixedPointOrInteger(E->getRHS(), RHSFX, Info) || !LHSOK)
       return false;
     if (LHSFX < RHSFX)
-      return Success(CmpResult::Less, E);
+      return Success(CmpResult::Less);
     if (LHSFX > RHSFX)
-      return Success(CmpResult::Greater, E);
-    return Success(CmpResult::Equal, E);
+      return Success(CmpResult::Greater);
+    return Success(CmpResult::Equal);
   }
 
   if (LHSTy->isAnyComplexType() || RHSTy->isAnyComplexType()) {
@@ -14287,12 +14429,12 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
       APFloat::cmpResult CR_i =
         LHS.getComplexFloatImag().compare(RHS.getComplexFloatImag());
       bool IsEqual = CR_r == APFloat::cmpEqual && CR_i == APFloat::cmpEqual;
-      return Success(IsEqual ? CmpResult::Equal : CmpResult::Unequal, E);
+      return Success(IsEqual ? CmpResult::Equal : CmpResult::Unequal);
     } else {
       assert(IsEquality && "invalid complex comparison");
       bool IsEqual = LHS.getComplexIntReal() == RHS.getComplexIntReal() &&
                      LHS.getComplexIntImag() == RHS.getComplexIntImag();
-      return Success(IsEqual ? CmpResult::Equal : CmpResult::Unequal, E);
+      return Success(IsEqual ? CmpResult::Equal : CmpResult::Unequal);
     }
   }
 
@@ -14329,7 +14471,7 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
       }
       llvm_unreachable("Unrecognised APFloat::cmpResult enum");
     };
-    return Success(GetCmpRes(), E);
+    return Success(GetCmpRes());
   }
 
   if (LHSTy->isPointerType() && RHSTy->isPointerType()) {
@@ -14342,145 +14484,12 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
     if (!EvaluatePointer(E->getRHS(), RHSValue, Info) || !LHSOK)
       return false;
 
-    // Reject differing bases from the normal codepath; we special-case
-    // comparisons to null.
-    if (!HasSameBase(LHSValue, RHSValue)) {
-      auto DiagComparison = [&] (unsigned DiagID, bool Reversed = false) {
-        std::string LHS = LHSValue.toString(Info.Ctx, E->getLHS()->getType());
-        std::string RHS = RHSValue.toString(Info.Ctx, E->getRHS()->getType());
-        Info.FFDiag(E, DiagID)
-            << (Reversed ? RHS : LHS) << (Reversed ? LHS : RHS);
-        return false;
-      };
-      // Inequalities and subtractions between unrelated pointers have
-      // unspecified or undefined behavior.
-      if (!IsEquality)
-        return DiagComparison(
-            diag::note_constexpr_pointer_comparison_unspecified);
-      // A constant address may compare equal to the address of a symbol.
-      // The one exception is that address of an object cannot compare equal
-      // to a null pointer constant.
-      // TODO: Should we restrict this to actual null pointers, and exclude the
-      // case of zero cast to pointer type?
-      if ((!LHSValue.Base && !LHSValue.Offset.isZero()) ||
-          (!RHSValue.Base && !RHSValue.Offset.isZero()))
-        return DiagComparison(diag::note_constexpr_pointer_constant_comparison,
-                              !RHSValue.Base);
-      // C++2c [intro.object]/10:
-      //   Two objects [...] may have the same address if [...] they are both
-      //   potentially non-unique objects.
-      // C++2c [intro.object]/9:
-      //   An object is potentially non-unique if it is a string literal object,
-      //   the backing array of an initializer list, or a subobject thereof.
-      //
-      // This makes the comparison result unspecified, so it's not a constant
-      // expression.
-      //
-      // TODO: Do we need to handle the initializer list case here?
-      if (ArePotentiallyOverlappingStringLiterals(Info, LHSValue, RHSValue))
-        return DiagComparison(diag::note_constexpr_literal_comparison);
-      if (IsOpaqueConstantCall(LHSValue) || IsOpaqueConstantCall(RHSValue))
-        return DiagComparison(diag::note_constexpr_opaque_call_comparison,
-                              !IsOpaqueConstantCall(LHSValue));
-      // We can't tell whether weak symbols will end up pointing to the same
-      // object.
-      if (IsWeakLValue(LHSValue) || IsWeakLValue(RHSValue))
-        return DiagComparison(diag::note_constexpr_pointer_weak_comparison,
-                              !IsWeakLValue(LHSValue));
-      // We can't compare the address of the start of one object with the
-      // past-the-end address of another object, per C++ DR1652.
-      if (LHSValue.Base && LHSValue.Offset.isZero() &&
-          isOnePastTheEndOfCompleteObject(Info.Ctx, RHSValue))
-        return DiagComparison(diag::note_constexpr_pointer_comparison_past_end,
-                              true);
-      if (RHSValue.Base && RHSValue.Offset.isZero() &&
-           isOnePastTheEndOfCompleteObject(Info.Ctx, LHSValue))
-        return DiagComparison(diag::note_constexpr_pointer_comparison_past_end,
-                              false);
-      // We can't tell whether an object is at the same address as another
-      // zero sized object.
-      if ((RHSValue.Base && isZeroSized(LHSValue)) ||
-          (LHSValue.Base && isZeroSized(RHSValue)))
-        return DiagComparison(
-            diag::note_constexpr_pointer_comparison_zero_sized);
-      return Success(CmpResult::Unequal, E);
-    }
-
-    const CharUnits &LHSOffset = LHSValue.getLValueOffset();
-    const CharUnits &RHSOffset = RHSValue.getLValueOffset();
-
-    SubobjectDesignator &LHSDesignator = LHSValue.getLValueDesignator();
-    SubobjectDesignator &RHSDesignator = RHSValue.getLValueDesignator();
-
-    // C++11 [expr.rel]p2:
-    // - If two pointers point to non-static data members of the same object,
-    //   or to subobjects or array elements fo such members, recursively, the
-    //   pointer to the later declared member compares greater provided the
-    //   two members have the same access control and provided their class is
-    //   not a union.
-    //   [...]
-    // - Otherwise pointer comparisons are unspecified.
-    if (!LHSDesignator.Invalid && !RHSDesignator.Invalid && IsRelational) {
-      bool WasArrayIndex;
-      unsigned Mismatch = FindDesignatorMismatch(
-          getType(LHSValue.Base), LHSDesignator, RHSDesignator, WasArrayIndex);
-      // At the point where the designators diverge, the comparison has a
-      // specified value if:
-      //  - we are comparing array indices
-      //  - we are comparing fields of a union, or fields with the same access
-      // Otherwise, the result is unspecified and thus the comparison is not a
-      // constant expression.
-      if (!WasArrayIndex && Mismatch < LHSDesignator.Entries.size() &&
-          Mismatch < RHSDesignator.Entries.size()) {
-        const FieldDecl *LF = getAsField(LHSDesignator.Entries[Mismatch]);
-        const FieldDecl *RF = getAsField(RHSDesignator.Entries[Mismatch]);
-        if (!LF && !RF)
-          Info.CCEDiag(E, diag::note_constexpr_pointer_comparison_base_classes);
-        else if (!LF)
-          Info.CCEDiag(E, diag::note_constexpr_pointer_comparison_base_field)
-              << getAsBaseClass(LHSDesignator.Entries[Mismatch])
-              << RF->getParent() << RF;
-        else if (!RF)
-          Info.CCEDiag(E, diag::note_constexpr_pointer_comparison_base_field)
-              << getAsBaseClass(RHSDesignator.Entries[Mismatch])
-              << LF->getParent() << LF;
-        else if (!LF->getParent()->isUnion() &&
-                 LF->getAccess() != RF->getAccess())
-          Info.CCEDiag(E,
-                       diag::note_constexpr_pointer_comparison_differing_access)
-              << LF << LF->getAccess() << RF << RF->getAccess()
-              << LF->getParent();
-      }
-    }
-
-    // The comparison here must be unsigned, and performed with the same
-    // width as the pointer.
-    unsigned PtrSize = Info.Ctx.getTypeSize(LHSTy);
-    uint64_t CompareLHS = LHSOffset.getQuantity();
-    uint64_t CompareRHS = RHSOffset.getQuantity();
-    assert(PtrSize <= 64 && "Unexpected pointer width");
-    uint64_t Mask = ~0ULL >> (64 - PtrSize);
-    CompareLHS &= Mask;
-    CompareRHS &= Mask;
-
-    // If there is a base and this is a relational operator, we can only
-    // compare pointers within the object in question; otherwise, the result
-    // depends on where the object is located in memory.
-    if (!LHSValue.Base.isNull() && IsRelational) {
-      QualType BaseTy = getType(LHSValue.Base);
-      if (BaseTy->isIncompleteType())
-        return Error(E);
-      CharUnits Size = Info.Ctx.getTypeSizeInChars(BaseTy);
-      uint64_t OffsetLimit = Size.getQuantity();
-      if (CompareLHS > OffsetLimit || CompareRHS > OffsetLimit)
-        return Error(E);
+    if (!EvaluateLValueComparison(Info, E, E->getOpcode(), LHSTy, LHSValue,
+                                  RHSTy, RHSValue, Success)) {
+      return false;
     }
 
-    if (CompareLHS < CompareRHS)
-      return Success(CmpResult::Less, E);
-    if (CompareLHS > CompareRHS)
-      return Success(CmpResult::Greater, E);
-    return Success(CmpResult::Equal, E);
+    return true;
   }
 
   if (LHSTy->isMemberPointerType()) {
@@ -14514,7 +14523,7 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
     //   null, they compare unequal.
     if (!LHSValue.getDecl() || !RHSValue.getDecl()) {
       bool Equal = !LHSValue.getDecl() && !RHSValue.getDecl();
-      return Success(Equal ? CmpResult::Equal : CmpResult::Unequal, E);
+      return Success(Equal ? CmpResult::Equal : CmpResult::Unequal);
     }
 
     //   Otherwise if either is a pointer to a virtual member function, the
@@ -14531,7 +14540,7 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
     //   they were dereferenced with a hypothetical object of the associated
     //   class type.
     bool Equal = LHSValue == RHSValue;
-    return Success(Equal ? CmpResult::Equal : CmpResult::Unequal, E);
+    return Success(Equal ? CmpResult::Equal : CmpResult::Unequal);
   }
 
   if (LHSTy->isNullPtrType()) {
@@ -14544,7 +14553,7 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E,
     if (!EvaluatePointer(E->getLHS(), Res, Info) ||
         !EvaluatePointer(E->getRHS(), Res, Info))
       return false;
-    return Success(CmpResult::Equal, E);
+    return Success(CmpResult::Equal);
   }
 
   return DoAfter();
@@ -14554,7 +14563,7 @@ bool RecordExprEvaluator::VisitBinCmp(const BinaryOperator *E) {
   if (!CheckLiteralType(Info, E))
     return false;
 
-  auto OnSuccess = [&](CmpResult CR, const BinaryOperator *E) {
+  auto OnSuccess = [&](CmpResult CR) {
     ComparisonCategoryResult CCR;
     switch (CR) {
     case CmpResult::Unequal:
@@ -14614,7 +14623,7 @@ bool IntExprEvaluator::VisitBinaryOperator(const BinaryOperator *E) {
   if (E->isComparisonOp()) {
     // Evaluate builtin binary comparisons by evaluating them as three-way
     // comparisons and then translating the result.
-    auto OnSuccess = [&](CmpResult CR, const BinaryOperator *E) {
+    auto OnSuccess = [&](CmpResult CR) {
       assert((CR != CmpResult::Unequal || E->isEqualityOp()) &&
              "should only produce Unequal for equality comparisons");
       bool IsEqual   = CR == CmpResult::Equal,
@@ -16380,7 +16389,7 @@ class VoidExprEvaluator
     case Builtin::BI__c11_atomic_signal_fence:
     case Builtin::BI__atomic_thread_fence:
     case Builtin::BI__atomic_signal_fence: {
-      [[maybe_unused]] APSInt IgnoredAtomicOrdering;
+      APSInt IgnoredAtomicOrdering;
       return EvaluateInteger(E->getArg(0), IgnoredAtomicOrdering, Info);
     }
 
@@ -18000,8 +18009,9 @@ std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &IEE,
 } // namespace
 
 static bool EvaluateAtomicOrder(const AtomicExpr *E, EvalInfo &Info) {
-  // we ignore results, but we need to evaluate them
-  [[maybe_unused]] APSInt OrderIgnoredResult;
+  // We need to evaluate Order argument(s), but we ignore it as constant
+  // evaluation is single threaded.
+  APSInt OrderIgnoredResult;
 
   if (E->getOp() != AtomicExpr::AO__c11_atomic_init) {
     const Expr *OrderSuccess = E->getOrder();
@@ -18019,20 +18029,6 @@ static bool EvaluateAtomicOrder(const AtomicExpr *E, EvalInfo &Info) {
   return true;
 }
 
-static bool EvaluateAtomicWeak(const AtomicExpr *E, EvalInfo &Info) {
-  // we ignore results, but we need to evaluate them
-  [[maybe_unused]] APSInt WeakIgnoredResult;
-
-  if (E->getOp() == AtomicExpr::AO__atomic_compare_exchange_n ||
-      E->getOp() == AtomicExpr::AO__atomic_compare_exchange) {
-    const Expr *Weak = E->getWeak();
-    if (!EvaluateInteger(Weak, WeakIgnoredResult, Info))
-      return false;
-  }
-
-  return true;
-}
-
 static bool EvaluateAtomicLoad(const AtomicExpr *E, APValue &Result,
                                EvalInfo &Info) {
   LValue AtomicStorageLV;
@@ -18044,26 +18040,20 @@ static bool EvaluateAtomicLoad(const AtomicExpr *E, APValue &Result,
                                         AtomicStorageLV, Result);
 }
 
-static bool EvaluatePointerAndStoreValueInto(Expr *ResultPtr,
-                                             APValue &ValueToStore,
-                                             EvalInfo &Info) {
-  assert(ResultPtr->getType()->isPointerType());
-  QualType PointeeTy = ResultPtr->getType()->getPointeeType();
-  LValue PointeeLV;
-
-  if (!EvaluatePointer(ResultPtr, PointeeLV, Info))
-    return false;
-
-  return handleAssignment(Info, ResultPtr, PointeeLV, PointeeTy, ValueToStore);
-}
-
 static bool EvaluateAtomicLoadInto(const AtomicExpr *E, EvalInfo &Info) {
   APValue LocalResult;
 
   if (!EvaluateAtomicLoad(E, LocalResult, Info))
     return false;
 
-  if (!EvaluatePointerAndStoreValueInto(E->getVal1(), LocalResult, Info))
+  assert(E->getVal1()->getType()->isPointerType());
+  QualType PointeeTy = E->getVal1()->getType()->getPointeeType();
+  LValue PointeeLV;
+
+  if (!EvaluatePointer(E->getVal1(), PointeeLV, Info))
+    return false;
+
+  if (!handleAssignment(Info, E->getVal1(), PointeeLV, PointeeTy, LocalResult))
     return false;
 
   return true;
@@ -18077,7 +18067,7 @@ static bool EvaluateAtomicStore(const AtomicExpr *E, EvalInfo &Info) {
 
   APValue ProvidedValue;
 
-  // GCC's atomic_store takes pointer to value, not value itself
+  // GCC's atomic_store takes pointer to value, not value itself.
   if (E->getOp() == AtomicExpr::AO__atomic_store) {
     LValue ProvidedLV;
     if (!EvaluatePointer(E->getVal1(), ProvidedLV, Info))
@@ -18099,20 +18089,34 @@ static bool EvaluateAtomicStore(const AtomicExpr *E, EvalInfo &Info) {
   return true;
 }
 
+static bool EvaluateAtomicExchange(const AtomicExpr *E, APValue &Result,
+                                   EvalInfo &Info) {
+  assert(E->getOp() == AtomicExpr::AO__c11_atomic_exchange ||
+         E->getOp() == AtomicExpr::AO__atomic_exchange_n);
+
+  if (!EvaluateAtomicLoad(E, Result, Info))
+    return false;
+
+  if (!EvaluateAtomicStore(E, Info))
+    return false;
+
+  return true;
+}
+
 static bool EvaluateAtomicExchangeInto(const AtomicExpr *E, EvalInfo &Info) {
   assert(E->getOp() == AtomicExpr::AO__atomic_exchange);
-  // implementation of GCC's exchange (non _n version)
+  // Implementation of GCC's exchange (non _n version).
   LValue AtomicStorageLV;
   if (!EvaluatePointer(E->getPtr(), AtomicStorageLV, Info))
     return false;
 
-  // read previous value
+  // Read previous value.
   APValue PreviousValue;
   if (!handleLValueToRValueConversion(Info, E->getPtr(), E->getValueType(),
                                       AtomicStorageLV, PreviousValue))
     return false;
 
-  // get provided value from argument (pointer)
+  // Get value pointer by argument of the exchange operation.
   LValue ProvidedLV;
   if (!EvaluatePointer(E->getVal1(), ProvidedLV, Info))
     return false;
@@ -18123,13 +18127,21 @@ static bool EvaluateAtomicExchangeInto(const AtomicExpr *E, EvalInfo &Info) {
                                       ProvidedValue))
     return false;
 
-  // store provided value to atomic value
+  // Store provided value to atomic value.
   if (!handleAssignment(Info, E, AtomicStorageLV, E->getValueType(),
                         ProvidedValue))
     return false;
 
-  // store previous value in output pointer
-  if (!EvaluatePointerAndStoreValueInto(E->getVal2(), PreviousValue, Info))
+  // Store previous value in output pointer.
+  assert(E->getVal2()->getType()->isPointerType());
+  QualType PointeeTy = E->getVal2()->getType()->getPointeeType();
+  LValue PointeeLV;
+
+  if (!EvaluatePointer(E->getVal2(), PointeeLV, Info))
+    return false;
+
+  if (!handleAssignment(Info, E->getVal2(), PointeeLV, PointeeTy,
+                        PreviousValue))
     return false;
 
   return true;
@@ -18137,7 +18149,7 @@ static bool EvaluateAtomicExchangeInto(const AtomicExpr *E, EvalInfo &Info) {
 
 static bool EvaluateAtomicFetchOp(const AtomicExpr *E, APValue &Result,
                                   EvalInfo &Info, bool StoreToResultAfter) {
-  // read atomic
+  // Read the atomic.
   LValue AtomicStorageLV;
   QualType AtomicValueTy = E->getValueType();
   if (!EvaluatePointer(E->getPtr(), AtomicStorageLV, Info))
@@ -18148,19 +18160,21 @@ static bool EvaluateAtomicFetchOp(const AtomicExpr *E, APValue &Result,
                                       AtomicStorageLV, CurrentValue))
     return false;
 
-  // store current value for fetch-OP operations
+  // Store current value for fetch-OP operations.
   if (!StoreToResultAfter)
     Result = CurrentValue;
 
-  // read argument for fetch OP
+  // Read argument for fetch OP.
   APValue ArgumentVal;
   if (!Evaluate(ArgumentVal, Info, E->getVal1()))
     return false;
 
-  // calculate new value
+  // Calculate new value.
   APValue Replacement;
   if (AtomicValueTy->isIntegralOrEnumerationType()) {
-    // both arguments are integers
+    assert(CurrentValue.isInt());
+    assert(ArgumentVal.isInt());
+
     const APSInt AtomicInt = CurrentValue.getInt();
     const APSInt ArgumentInt = ArgumentVal.getInt();
 
@@ -18168,6 +18182,7 @@ static bool EvaluateAtomicFetchOp(const AtomicExpr *E, APValue &Result,
     case AtomicExpr::AO__c11_atomic_fetch_add:
     case AtomicExpr::AO__atomic_fetch_add:
     case AtomicExpr::AO__atomic_add_fetch:
+      // Atomic operations are defined for overflow
       Replacement = APValue(AtomicInt + ArgumentInt);
       break;
     case AtomicExpr::AO__c11_atomic_fetch_sub:
@@ -18198,28 +18213,27 @@ static bool EvaluateAtomicFetchOp(const AtomicExpr *E, APValue &Result,
     case AtomicExpr::AO__c11_atomic_fetch_max:
     case AtomicExpr::AO__atomic_fetch_max:
     case AtomicExpr::AO__atomic_max_fetch:
-      Replacement =
-          APValue((AtomicInt > ArgumentInt) ? AtomicInt : ArgumentInt);
+      Replacement = APValue(std::max(AtomicInt, ArgumentInt));
       break;
     case AtomicExpr::AO__c11_atomic_fetch_min:
     case AtomicExpr::AO__atomic_fetch_min:
     case AtomicExpr::AO__atomic_min_fetch:
-      Replacement =
-          APValue((AtomicInt < ArgumentInt) ? AtomicInt : ArgumentInt);
+      Replacement = APValue(std::min(AtomicInt, ArgumentInt));
       break;
     default:
       return false;
     }
   } else if (AtomicValueTy->isRealFloatingType()) {
-    // both arguments are float operations
+    assert(CurrentValue.isFloat());
+    assert(ArgumentVal.isFloat());
+
     const llvm::RoundingMode RM = getActiveRoundingMode(Info, E);
     APFloat AtomicFlt = CurrentValue.getFloat();
     const APFloat ArgumentFlt = ArgumentVal.getFloat();
     APFloat::opStatus St;
 
     switch (E->getOp()) {
-    case AtomicExpr::AO__c11_atomic_fetch_add: // GCC atomics doesn't support
-                                               // floats
+    case AtomicExpr::AO__c11_atomic_fetch_add:
       St = AtomicFlt.add(ArgumentFlt, RM);
       Replacement = APValue(AtomicFlt);
       break;
@@ -18228,6 +18242,7 @@ static bool EvaluateAtomicFetchOp(const AtomicExpr *E, APValue &Result,
       Replacement = APValue(AtomicFlt);
       break;
     default:
+      // GCC's atomic fetch-op doesn't support float operands.
       return false;
     }
 
@@ -18235,34 +18250,41 @@ static bool EvaluateAtomicFetchOp(const AtomicExpr *E, APValue &Result,
       return false;
 
   } else if (AtomicValueTy->isPointerType()) {
-    // pointer + int arguments
+    assert(CurrentValue.isLValue());
+    assert(ArgumentVal.isInt());
+
     LValue AtomicPtr;
     AtomicPtr.setFrom(Info.Ctx, CurrentValue);
 
     APSInt ArgumentInt = ArgumentVal.getInt();
 
-    // calculate size of pointee object
+    // Calculate size of pointee object.
     CharUnits SizeOfPointee;
     if (!HandleSizeof(Info, E->getExprLoc(), AtomicValueTy->getPointeeType(),
                       SizeOfPointee))
       return false;
 
-    // GCC's atomic_fetch add/sub compute new pointer by bytes and not
-    // sizeof(T)
+    // GCC's atomic_fetch add/sub operations takes arguments in bytes and
+    // not in multiplies of sizeof(T).
     switch (E->getOp()) {
     case AtomicExpr::AO__atomic_fetch_add:
     case AtomicExpr::AO__atomic_add_fetch:
     case AtomicExpr::AO__atomic_fetch_sub:
     case AtomicExpr::AO__atomic_sub_fetch: {
-      const auto sizeOfOneItem = APSInt(
+      const auto SizeOfOneItem = APSInt(
           APInt(ArgumentInt.getBitWidth(), SizeOfPointee.getQuantity(), false),
           false);
-      // incrementing pointer by size which is not dividable by pointee size
-      // is UB and therefore disallowed
-      if ((ArgumentInt % sizeOfOneItem) != 0)
+      // Incrementing/decrementing pointer by size which is not dividable by
+      // pointee size is not allowed.
+      if ((ArgumentInt % SizeOfOneItem) != 0) {
+        Info.FFDiag(E->getBuiltinLoc(), diag::note_unaligned_atomic_pointer_op)
+            << E->getBuiltinLoc()
+            << E->getVal1()->getSourceRange() // Problematic argument.
+            << ArgumentInt << AtomicValueTy->getPointeeType() << SizeOfOneItem;
         return false;
+      }
 
-      ArgumentInt /= sizeOfOneItem;
+      ArgumentInt /= SizeOfOneItem;
     } break;
     default:
       break;
@@ -18286,39 +18308,47 @@ static bool EvaluateAtomicFetchOp(const AtomicExpr *E, APValue &Result,
 
     AtomicPtr.moveInto(Replacement);
   } else {
-    // not float,int,pointer?
+    assert(false && "only float, int, or pointer supported");
     return false;
   }
 
-  // OP-fetch operation store result, not previous value
+  // OP-fetch operation store result, not previous value.
   if (StoreToResultAfter)
     Result = Replacement;
 
-  // store result to atomic's storage
+  // Store result to atomic's storage.
   return handleAssignment(Info, E, AtomicStorageLV, AtomicValueTy, Replacement);
 }
 
 static bool EvaluateAtomicCompareExchange(const AtomicExpr *E, APValue &Result,
                                           EvalInfo &Info) {
-  EvaluateAtomicWeak(E, Info);
+  if (E->getOp() == AtomicExpr::AO__atomic_compare_exchange_n ||
+      E->getOp() == AtomicExpr::AO__atomic_compare_exchange) {
+
+    APSInt WeakIgnoredResult;
+
+    const Expr *Weak = E->getWeak();
+    if (!EvaluateInteger(Weak, WeakIgnoredResult, Info))
+      return false;
+  }
 
-  // dereference _Atomic * (atomic value)
+  // Dereference _Atomic T * (atomic value).
   LValue AtomicLV;
   QualType AtomicTy = E->getValueType();
   if (!EvaluatePointer(E->getPtr(), AtomicLV, Info))
     return false;
 
-  // dereference T * (expected value)
+  // Dereference T * (expected value).
   LValue ExpectedLV;
   QualType ExpectedTy = E->getVal1()->getType()->getPointeeType();
   if (!EvaluatePointer(E->getVal1(), ExpectedLV, Info))
     return false;
 
-  // get values for atomic and expected
+  // Get values for atomic and expected.
   APValue AtomicVal;
   APValue ExpectedVal;
 
-  // convert pointer to value
+  // Convert pointer to value.
   if (!handleLValueToRValueConversion(Info, E->getPtr(), AtomicTy, AtomicLV,
                                       AtomicVal))
     return false;
@@ -18329,7 +18359,7 @@ static bool EvaluateAtomicCompareExchange(const AtomicExpr *E, APValue &Result,
 
   bool DoExchange = false;
 
-  // compare atomic<int> and friends
+  // Do the comparison for equality.
   if (AtomicTy->isIntegralOrEnumerationType() &&
       ExpectedTy->isIntegralOrEnumerationType()) {
     const APSInt AtomicInt = AtomicVal.getInt();
@@ -18345,55 +18375,19 @@ static bool EvaluateAtomicCompareExchange(const AtomicExpr *E, APValue &Result,
       DoExchange = true;
 
   } else if (AtomicTy->isPointerType() && ExpectedTy->isPointerType()) {
-    // get LValue of objects pointed to
-    LValue LHS;
+    // Do the comparison of pointers.
+    LValue LHS, RHS;
     LHS.setFrom(Info.Ctx, AtomicVal);
-
-    LValue RHS;
     RHS.setFrom(Info.Ctx, ExpectedVal);
 
-    if (HasSameBase(LHS, RHS)) {
-      const CharUnits &LHSOffset = LHS.getLValueOffset();
-      const CharUnits &RHSOffset = RHS.getLValueOffset();
-
-      const unsigned PtrSize = Info.Ctx.getTypeSize(AtomicTy);
-      assert(PtrSize <= 64 && "Pointer width is larger than expected");
-      const uint64_t Mask = ~0ULL >> (64 - PtrSize);
-
-      const uint64_t CompareLHS = LHSOffset.getQuantity() & Mask;
-      const uint64_t CompareRHS = RHSOffset.getQuantity() & Mask;
-
-      if (CompareLHS == CompareRHS)
-        DoExchange = true;
-
-    } else {
-
-      if (ArePotentiallyOverlappingStringLiterals(Info, LHS, RHS))
-        return false;
-
-      if (IsOpaqueConstantCall(LHS) || IsOpaqueConstantCall(RHS))
-        return false;
-
-      if (IsWeakLValue(LHS) || IsWeakLValue(RHS))
-        return false;
-
-      if ((!LHS.Base && !LHS.Offset.isZero()) ||
-          (!RHS.Base && !RHS.Offset.isZero()))
-        return false;
-
-      if (LHS.Base && LHS.Offset.isZero() &&
-          isOnePastTheEndOfCompleteObject(Info.Ctx, RHS))
-        return false;
-
-      if (RHS.Base && RHS.Offset.isZero() &&
-          isOnePastTheEndOfCompleteObject(Info.Ctx, LHS))
-        return false;
-
-      if ((RHS.Base && isZeroSized(RHS)) || (LHS.Base && isZeroSized(LHS)))
-        return false;
+    auto OnSuccess = [&](CmpResult CR) {
+      DoExchange = (CR == CmpResult::Equal);
+      return true;
+    };
 
-      // after all it's a different object
-      DoExchange = false;
+    if (!EvaluateLValueComparison(Info, E, BO_EQ, AtomicTy, LHS, ExpectedTy,
+                                  RHS, OnSuccess)) {
+      return false;
     }
 
   } else {
@@ -18402,7 +18396,8 @@ static bool EvaluateAtomicCompareExchange(const AtomicExpr *E, APValue &Result,
 
   APValue Replacement;
   if (E->getOp() == AtomicExpr::AO__atomic_compare_exchange) {
-    // GCC atomic_compare_exchange provides pointer and not value
+    // GCC atomic_compare_exchange takes pointer to a value and not value
+    // itself.
     LValue ReplacementLV;
     if (!EvaluatePointer(E->getVal2(), ReplacementLV, Info))
       return false;
@@ -18418,19 +18413,17 @@ static bool EvaluateAtomicCompareExchange(const AtomicExpr *E, APValue &Result,
   }
 
   if (DoExchange) {
-    // if values are same do the exchange with replacement value
-    // but first I must evaluate the replacement value
-
-    // and assign it to atomic
+    // If values are same do the exchange with replacement value.
+    // But first I must evaluate the replacement value and assign it to atomic.
     if (!handleAssignment(Info, E, AtomicLV, AtomicTy, Replacement))
       return false;
   }
 
-  // to expected pointer I need to put previous value in atomic
+  // Return previous value to the Expected pointer.
   if (!handleAssignment(Info, E, ExpectedLV, ExpectedTy, AtomicVal))
     return false;
 
-  // and return boolean if I did the exchange
+  // Return boolean result if operation was successful.
   Result = APValue(Info.Ctx.MakeIntValue(DoExchange, E->getType()));
   return true;
 }

>From 8aee298ac7d0755db9fd90d3c913671ddb1342af Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hana=20Dusi=CC=81kova=CC=81?= <hanicka at hanicka.net>
Date: Wed, 8 Jan 2025 12:37:45 +0100
Subject: [PATCH 3/6] [clang] add __atomic_fetch_(add|sub) and
 __atomic_(add|sub)_fetch tests for diagnostics

---
 ...mic-constexpr-gcc-builtins-diagnostics.cpp | 102 ++++++++++++++++++
 1 file changed, 102 insertions(+)
 create mode 100644 clang/test/SemaCXX/atomic-constexpr-gcc-builtins-diagnostics.cpp

diff --git a/clang/test/SemaCXX/atomic-constexpr-gcc-builtins-diagnostics.cpp b/clang/test/SemaCXX/atomic-constexpr-gcc-builtins-diagnostics.cpp
new file mode 100644
index 00000000000000..1afe342f823f70
--- /dev/null
+++ b/clang/test/SemaCXX/atomic-constexpr-gcc-builtins-diagnostics.cpp
@@ -0,0 +1,102 @@
+// RUN: %clang_cc1 -std=c++23 -verify %s
+
+// _fetch_add
+
+constexpr bool test_int_fetch_add(int arg) {
+  int array[8] = {1,2,3,4,5,6,7,8};
+  int * ptr = &array[0];
+  int * old = __atomic_fetch_add(&ptr, arg, __ATOMIC_RELAXED); // expected-note-re {{atomic pointer operation with argument 5 not aligned to size of pointee type (sizeof 'int' is {{[0-9]+}})}}
+  return true;
+}
+
+static_assert(test_int_fetch_add(5)); // expected-error {{static assertion expression is not an integral constant expression}} expected-note {{in call to 'test_int_fetch_add(5)'}}
+
+static_assert(test_int_fetch_add(sizeof(int)));
+
+constexpr bool test_long_long_fetch_add(int arg) {
+  long long array[8] = {1,2,3,4,5,6,7,8};
+  long long * ptr = &array[0];
+  long long * old = __atomic_fetch_add(&ptr, arg, __ATOMIC_RELAXED); // expected-note-re {{atomic pointer operation with argument 6 not aligned to size of pointee type (sizeof 'long long' is {{[0-9]+}})}}
+  return true;
+}
+
+static_assert(test_long_long_fetch_add(6)); // expected-error {{static assertion expression is not an integral constant expression}} expected-note {{in call to 'test_long_long_fetch_add(6)'}}
+
+
+static_assert(test_long_long_fetch_add(sizeof(long long) * 4));
+
+// _fetch_sub
+
+constexpr bool test_int_fetch_sub(int arg) {
+  int array[8] = {1,2,3,4,5,6,7,8};
+  int * ptr = &array[7];
+  int * old = __atomic_fetch_sub(&ptr, arg, __ATOMIC_RELAXED); // expected-note-re {{atomic pointer operation with argument 7 not aligned to size of pointee type (sizeof 'int' is {{[0-9]+}})}}
+  return true;
+}
+
+static_assert(test_int_fetch_sub(7)); // expected-error {{static assertion expression is not an integral constant expression}} expected-note {{in call to 'test_int_fetch_sub(7)'}}
+
+static_assert(test_int_fetch_sub(sizeof(int)));
+
+constexpr bool test_long_long_fetch_sub(int arg) {
+  long long array[8] = {1,2,3,4,5,6,7,8};
+  long long * ptr = &array[7];
+  long long * old = __atomic_fetch_sub(&ptr, arg, __ATOMIC_RELAXED); // expected-note-re {{atomic pointer operation with argument 11 not aligned to size of pointee type (sizeof 'long long' is {{[0-9]+}})}}
+  return true;
+}
+
+static_assert(test_long_long_fetch_sub(11)); // expected-error {{static assertion expression is not an integral constant expression}} expected-note {{in call to 'test_long_long_fetch_sub(11)'}}
+
+
+static_assert(test_long_long_fetch_sub(sizeof(long long) * 4));
+
+
+// _add_fetch
+
+constexpr bool test_int_add_fetch(int arg) {
+  int array[8] = {1,2,3,4,5,6,7,8};
+  int * ptr = &array[0];
+  int * old = __atomic_add_fetch(&ptr, arg, __ATOMIC_RELAXED); // expected-note-re {{atomic pointer operation with argument 5 not aligned to size of pointee type (sizeof 'int' is {{[0-9]+}})}}
+  return true;
+}
+
+static_assert(test_int_add_fetch(5)); // expected-error {{static assertion expression is not an integral constant expression}} expected-note {{in call to 'test_int_add_fetch(5)'}}
+
+static_assert(test_int_add_fetch(sizeof(int)));
+
+constexpr bool test_long_long_add_fetch(int arg) {
+  long long array[8] = {1,2,3,4,5,6,7,8};
+  long long * ptr = &array[0];
+  long long * old = __atomic_add_fetch(&ptr, arg, __ATOMIC_RELAXED); // expected-note-re {{atomic pointer operation with argument 6 not aligned to size of pointee type (sizeof 'long long' is {{[0-9]+}})}}
+  return true;
+}
+
+static_assert(test_long_long_add_fetch(6)); // expected-error {{static assertion expression is not an integral constant expression}} expected-note {{in call to 'test_long_long_add_fetch(6)'}}
+
+
+static_assert(test_long_long_add_fetch(sizeof(long long) * 4));
+
+// _sub_fetch
+
+constexpr bool test_int_sub_fetch(int arg) {
+  int array[8] = {1,2,3,4,5,6,7,8};
+  int * ptr = &array[7];
+  int * old = __atomic_sub_fetch(&ptr, arg, __ATOMIC_RELAXED); // expected-note-re {{atomic pointer operation with argument 7 not aligned to size of pointee type (sizeof 'int' is {{[0-9]+}})}}
+  return true;
+}
+
+static_assert(test_int_sub_fetch(7)); // expected-error {{static assertion expression is not an integral constant expression}} expected-note {{in call to 'test_int_sub_fetch(7)'}}
+
+static_assert(test_int_sub_fetch(sizeof(int)));
+
+constexpr bool test_long_long_sub_fetch(int arg) {
+  long long array[8] = {1,2,3,4,5,6,7,8};
+  long long * ptr = &array[7];
+  long long * old = __atomic_sub_fetch(&ptr, arg, __ATOMIC_RELAXED); // expected-note-re {{atomic pointer operation with argument 11 not aligned to size of pointee type (sizeof 'long long' is {{[0-9]+}})}}
+  return true;
+}
+
+static_assert(test_long_long_sub_fetch(11)); // expected-error {{static assertion expression is not an integral constant expression}} expected-note {{in call to 'test_long_long_sub_fetch(11)'}}
+
+
+static_assert(test_long_long_sub_fetch(sizeof(long long) * 4));

>From 9de180f064aaf2002fe947501a4844e33da3f8b0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hana=20Dusi=CC=81kova=CC=81?= <hanicka at hanicka.net>
Date: Wed, 8 Jan 2025 23:03:15 +0100
Subject: [PATCH 4/6] [clang] revert making atomic builtins constexpr, it's not
 needed, as they are expressions and not functions

---
 clang/include/clang/Basic/Builtins.td | 80 +++++++++++++--------------
 1 file changed, 40 insertions(+), 40 deletions(-)

diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 797191fba888d2..f695e34135685d 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -1750,230 +1750,230 @@ def C11AtomicInit : AtomicBuiltin {
 
 def C11AtomicLoad : AtomicBuiltin {
   let Spellings = ["__c11_atomic_load"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def C11AtomicStore : AtomicBuiltin {
   let Spellings = ["__c11_atomic_store"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def C11AtomicExchange : AtomicBuiltin {
   let Spellings = ["__c11_atomic_exchange"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def C11AtomicCompareExchangeStrong : AtomicBuiltin {
   let Spellings = ["__c11_atomic_compare_exchange_strong"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def C11AtomicCompareExchangeWeak : AtomicBuiltin {
   let Spellings = ["__c11_atomic_compare_exchange_weak"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchAdd : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_add"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchSub : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_sub"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchAnd : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_and"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchOr : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_or"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchXor : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_xor"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchNand : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_nand"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchMax : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_max"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchMin : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_min"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking,];
   let Prototype = "void(...)";
 }
 
 def C11AtomicThreadFence : Builtin {
   let Spellings = ["__c11_atomic_thread_fence"];
-  let Attributes = [NoThrow, Constexpr];
+  let Attributes = [NoThrow];
   let Prototype = "void(int)";
 }
 
 def C11AtomicSignalFence : Builtin {
   let Spellings = ["__c11_atomic_signal_fence"];
-  let Attributes = [NoThrow, Constexpr];
+  let Attributes = [NoThrow];
   let Prototype = "void(int)";
 }
 
 def C11AtomicIsLockFree : Builtin {
   let Spellings = ["__c11_atomic_is_lock_free"];
-  let Attributes = [NoThrow, Constexpr];
+  let Attributes = [NoThrow];
   let Prototype = "bool(size_t)";
 }
 
 // GNU atomic builtins.
 def AtomicLoad : AtomicBuiltin {
   let Spellings = ["__atomic_load"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def AtomicLoadN : AtomicBuiltin {
   let Spellings = ["__atomic_load_n"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def AtomicStore : AtomicBuiltin {
   let Spellings = ["__atomic_store"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def AtomicStoreN : AtomicBuiltin {
   let Spellings = ["__atomic_store_n"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def AtomicExchange : AtomicBuiltin {
   let Spellings = ["__atomic_exchange"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def AtomicExchangeN : AtomicBuiltin {
   let Spellings = ["__atomic_exchange_n"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def AtomicCompareExchange : AtomicBuiltin {
   let Spellings = ["__atomic_compare_exchange"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def AtomicCompareExchangeN : AtomicBuiltin {
   let Spellings = ["__atomic_compare_exchange_n"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def AtomicFetchAdd : AtomicBuiltin {
   let Spellings = ["__atomic_fetch_add"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def AtomicFetchSub : AtomicBuiltin {
   let Spellings = ["__atomic_fetch_sub"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def AtomicFetchAnd : AtomicBuiltin {
   let Spellings = ["__atomic_fetch_and"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def AtomicFetchOr : AtomicBuiltin {
   let Spellings = ["__atomic_fetch_or"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def AtomicFetchXor : AtomicBuiltin {
   let Spellings = ["__atomic_fetch_xor"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def AtomicFetchNand : AtomicBuiltin {
   let Spellings = ["__atomic_fetch_nand"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def AtomicAddFetch : AtomicBuiltin {
   let Spellings = ["__atomic_add_fetch"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def AtomicSubFetch : AtomicBuiltin {
   let Spellings = ["__atomic_sub_fetch"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def AtomicAndFetch : AtomicBuiltin {
   let Spellings = ["__atomic_and_fetch"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def AtomicOrFetch : AtomicBuiltin {
   let Spellings = ["__atomic_or_fetch"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def AtomicXorFetch : AtomicBuiltin {
   let Spellings = ["__atomic_xor_fetch"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def AtomicMaxFetch : AtomicBuiltin {
   let Spellings = ["__atomic_max_fetch"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def AtomicMinFetch : AtomicBuiltin {
   let Spellings = ["__atomic_min_fetch"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
 def AtomicNandFetch : AtomicBuiltin {
   let Spellings = ["__atomic_nand_fetch"];
-  let Attributes = [CustomTypeChecking, Constexpr];
+  let Attributes = [CustomTypeChecking];
   let Prototype = "void(...)";
 }
 
@@ -1991,7 +1991,7 @@ def AtomicClear : Builtin {
 
 def AtomicThreadFence : Builtin {
   let Spellings = ["__atomic_thread_fence"];
-  let Attributes = [NoThrow, Constexpr];
+  let Attributes = [NoThrow];
   let Prototype = "void(int)";
 }
 
@@ -2003,7 +2003,7 @@ def ScopedAtomicThreadFence : Builtin {
 
 def AtomicSignalFence : Builtin {
   let Spellings = ["__atomic_signal_fence"];
-  let Attributes = [NoThrow, Constexpr];
+  let Attributes = [NoThrow];
   let Prototype = "void(int)";
 }
 

>From 537178b0176740adc06268b6a9b3cee3de4a8424 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hana=20Dusi=CC=81kova=CC=81?= <hanicka at hanicka.net>
Date: Thu, 9 Jan 2025 00:42:28 +0100
Subject: [PATCH 5/6] [clang] allow constexpr atomics only in C++ mode, check
 for valid Memory Ordering arguments of atomic's operations, support `void *`
 pointer for GCC's atomic_fetch_add/sub and atomic_add/sub_fetch

---
 clang/include/clang/AST/Expr.h                |  3 +
 .../include/clang/Basic/DiagnosticASTKinds.td |  2 +
 clang/lib/AST/ExprConstant.cpp                | 59 ++++++++++++++++---
 clang/lib/Sema/SemaChecking.cpp               |  6 +-
 .../SemaCXX/atomic-constexpr-c11-builtins.cpp |  2 +-
 ...mic-constexpr-gcc-builtins-diagnostics.cpp |  2 +-
 .../SemaCXX/atomic-constexpr-gcc-builtins.cpp |  2 +-
 7 files changed, 63 insertions(+), 13 deletions(-)

diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 708c8656decbe0..3fc8062783b8b3 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -6830,6 +6830,9 @@ class AtomicExpr : public Expr {
   std::unique_ptr<AtomicScopeModel> getScopeModel() const {
     return getScopeModel(getOp());
   }
+
+  /// Helper function to check valid ordering for specified Op.
+  static bool isValidOrderingForOp(int64_t Ordering, AtomicExpr::AtomicOp Op);
 };
 
 /// TypoExpr - Internal placeholder for expressions where typo correction
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index 6d3611fa1fc574..2d6e37a31674c4 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -391,6 +391,8 @@ def err_experimental_clang_interp_failed : Error<
   "the experimental clang interpreter failed to evaluate an expression">;
 def note_unaligned_atomic_pointer_op : Note<
   "atomic pointer operation with argument %0 not aligned to size of pointee type (sizeof %1 is %2)">;
+def note_constexpr_atomic_ops_only_in_cpp : Note<
+  "constant evaluated atomic operations are supported only in C++ mode">;
 
 def warn_integer_constant_overflow : Warning<
   "overflow in expression; result is %0 with type %1">,
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index a428a5c75808ab..3fe31e31c10ad7 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -8090,6 +8090,11 @@ class ExprEvaluatorBase
   }
 
   bool VisitAtomicExpr(const AtomicExpr *E) {
+    if (!Info.getLangOpts().CPlusPlus) {
+      Info.FFDiag(E, diag::note_constexpr_atomic_ops_only_in_cpp);
+      return false;
+    }
+
     if (!EvaluateAtomicOrder(E, Info))
       return false;
 
@@ -16354,6 +16359,11 @@ class VoidExprEvaluator
   }
 
   bool VisitAtomicExpr(const AtomicExpr *E) {
+    if (!Info.getLangOpts().CPlusPlus) {
+      Info.FFDiag(E, diag::note_constexpr_atomic_ops_only_in_cpp);
+      return false;
+    }
+
     if (!EvaluateAtomicOrder(E, Info))
       return false;
 
@@ -18011,19 +18021,35 @@ std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &IEE,
 static bool EvaluateAtomicOrder(const AtomicExpr *E, EvalInfo &Info) {
   // We need to evaluate Order argument(s), but we ignore it as constant
   // evaluation is single threaded.
-  APSInt OrderIgnoredResult;
+  APSInt AtomicOrderValue;
 
   if (E->getOp() != AtomicExpr::AO__c11_atomic_init) {
     const Expr *OrderSuccess = E->getOrder();
-    if (OrderSuccess &&
-        !EvaluateInteger(OrderSuccess, OrderIgnoredResult, Info))
+    if (OrderSuccess && !EvaluateInteger(OrderSuccess, AtomicOrderValue, Info))
+      return false;
+
+    // FIXME: this diagnostics is emited twice, once in SemaChecking.cpp and
+    // once here
+    if (!AtomicExpr::isValidOrderingForOp(AtomicOrderValue.getSExtValue(),
+                                          E->getOp())) {
+      Info.FFDiag(E->getOrder(), diag::warn_atomic_op_has_invalid_memory_order)
+          << /*success=*/(E->isCmpXChg()) << E->getOrder()->getExprLoc();
       return false;
+    }
   }
 
   if (E->isCmpXChg()) {
     const Expr *OrderFail = E->getOrderFail();
-    if (!EvaluateInteger(OrderFail, OrderIgnoredResult, Info))
+    if (!EvaluateInteger(OrderFail, AtomicOrderValue, Info))
       return false;
+
+    if (!AtomicExpr::isValidOrderingForOp(AtomicOrderValue.getSExtValue(),
+                                          E->getOp())) {
+      Info.FFDiag(E->getOrderFail(),
+                  diag::warn_atomic_op_has_invalid_memory_order)
+          << 2 << E->getOrderFail()->getExprLoc();
+      return false;
+    }
   }
 
   return true;
@@ -18258,14 +18284,30 @@ static bool EvaluateAtomicFetchOp(const AtomicExpr *E, APValue &Result,
 
     APSInt ArgumentInt = ArgumentVal.getInt();
 
+    // if (AtomicValueTy->getPointeeType()->isVoidType()) {
+    //   Info.FFDiag(E, diag::err_typecheck_pointer_arith_void_type) << 0;
+    //   return false;
+    // }
+
+    auto PointeeTy = AtomicValueTy->getPointeeType();
+
+    if (PointeeTy->isVoidType()) {
+      // Resolve type from LValue Designator, so we can do safely arithmetic
+      // over 'void *' with GCC atomic builtins.
+      PointeeTy = AtomicPtr.Designator.getType(Info.Ctx);
+    } else if (PointeeTy->isIncompleteType()) {
+      Info.FFDiag(E, diag::err_typecheck_arithmetic_incomplete_or_sizeless_type)
+          << 0 << PointeeTy;
+      return false;
+    }
+
     // Calculate size of pointee object.
     CharUnits SizeOfPointee;
-    if (!HandleSizeof(Info, E->getExprLoc(), AtomicValueTy->getPointeeType(),
-                      SizeOfPointee))
+    if (!HandleSizeof(Info, E->getExprLoc(), PointeeTy, SizeOfPointee))
       return false;
 
     // GCC's atomic_fetch add/sub operations takes arguments in bytes and
-    // not in multiplies of sizeof(T).
+    // not in multiples of sizeof(T).
     switch (E->getOp()) {
     case AtomicExpr::AO__atomic_fetch_add:
     case AtomicExpr::AO__atomic_add_fetch:
@@ -18276,11 +18318,12 @@ static bool EvaluateAtomicFetchOp(const AtomicExpr *E, APValue &Result,
           false);
       // Incrementing/decrementing pointer by size which is not dividable by
       // pointee size is not allowed.
+      // FIXME: check if we need to also look for alignof(T)
       if ((ArgumentInt % SizeOfOneItem) != 0) {
         Info.FFDiag(E->getBuiltinLoc(), diag::note_unaligned_atomic_pointer_op)
             << E->getBuiltinLoc()
             << E->getVal1()->getSourceRange() // Problematic argument.
-            << ArgumentInt << AtomicValueTy->getPointeeType() << SizeOfOneItem;
+            << ArgumentInt << PointeeTy << SizeOfOneItem;
         return false;
       }
 
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 28dcfaac2e84f5..e485c6496858a4 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -3607,7 +3607,8 @@ bool Sema::CheckOtherCall(CallExpr *TheCall, const FunctionProtoType *Proto) {
   return false;
 }
 
-static bool isValidOrderingForOp(int64_t Ordering, AtomicExpr::AtomicOp Op) {
+bool AtomicExpr::isValidOrderingForOp(int64_t Ordering,
+                                      AtomicExpr::AtomicOp Op) {
   if (!llvm::isValidAtomicOrderingCABI(Ordering))
     return false;
 
@@ -4164,7 +4165,8 @@ ExprResult Sema::BuildAtomicExpr(SourceRange CallRange, SourceRange ExprRange,
   if (SubExprs.size() >= 2 && Form != Init) {
     std::optional<llvm::APSInt> Success =
         SubExprs[1]->getIntegerConstantExpr(Context);
-    if (Success && !isValidOrderingForOp(Success->getSExtValue(), Op)) {
+    if (Success &&
+        !AtomicExpr::isValidOrderingForOp(Success->getSExtValue(), Op)) {
       Diag(SubExprs[1]->getBeginLoc(),
            diag::warn_atomic_op_has_invalid_memory_order)
           << /*success=*/(Form == C11CmpXchg || Form == GNUCmpXchg)
diff --git a/clang/test/SemaCXX/atomic-constexpr-c11-builtins.cpp b/clang/test/SemaCXX/atomic-constexpr-c11-builtins.cpp
index 9ff775d37bd52e..6858abc0f35200 100644
--- a/clang/test/SemaCXX/atomic-constexpr-c11-builtins.cpp
+++ b/clang/test/SemaCXX/atomic-constexpr-c11-builtins.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -std=c++20 %s
+// RUN: %clang_cc1 -std=c++2c %s
 
 // expected-no-diagnostics
 
diff --git a/clang/test/SemaCXX/atomic-constexpr-gcc-builtins-diagnostics.cpp b/clang/test/SemaCXX/atomic-constexpr-gcc-builtins-diagnostics.cpp
index 1afe342f823f70..dbea72d6a7c43d 100644
--- a/clang/test/SemaCXX/atomic-constexpr-gcc-builtins-diagnostics.cpp
+++ b/clang/test/SemaCXX/atomic-constexpr-gcc-builtins-diagnostics.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -std=c++23 -verify %s
+// RUN: %clang_cc1 -std=c++2c -verify %s
 
 // _fetch_add
 
diff --git a/clang/test/SemaCXX/atomic-constexpr-gcc-builtins.cpp b/clang/test/SemaCXX/atomic-constexpr-gcc-builtins.cpp
index a1d23294a2080e..7fc1121f6fbcbd 100644
--- a/clang/test/SemaCXX/atomic-constexpr-gcc-builtins.cpp
+++ b/clang/test/SemaCXX/atomic-constexpr-gcc-builtins.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -std=c++20 %s
+// RUN: %clang_cc1 -std=c++2c %s
 
 // expected-no-diagnostics
 

>From b87075c77978f62d98613e3187b8dfe0c5221964 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hana=20Dusi=CC=81kova=CC=81?= <hanicka at hanicka.net>
Date: Thu, 9 Jan 2025 00:42:56 +0100
Subject: [PATCH 6/6] Revert "[clang] revert making atomic builtins constexpr,
 it's not needed, as they are expressions and not functions"

This reverts commit 9de180f064aaf2002fe947501a4844e33da3f8b0.
---
 clang/include/clang/Basic/Builtins.td | 80 +++++++++++++--------------
 1 file changed, 40 insertions(+), 40 deletions(-)

diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index f695e34135685d..797191fba888d2 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -1750,230 +1750,230 @@ def C11AtomicInit : AtomicBuiltin {
 
 def C11AtomicLoad : AtomicBuiltin {
   let Spellings = ["__c11_atomic_load"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicStore : AtomicBuiltin {
   let Spellings = ["__c11_atomic_store"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicExchange : AtomicBuiltin {
   let Spellings = ["__c11_atomic_exchange"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicCompareExchangeStrong : AtomicBuiltin {
   let Spellings = ["__c11_atomic_compare_exchange_strong"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicCompareExchangeWeak : AtomicBuiltin {
   let Spellings = ["__c11_atomic_compare_exchange_weak"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchAdd : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_add"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchSub : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_sub"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchAnd : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_and"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchOr : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_or"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchXor : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_xor"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchNand : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_nand"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchMax : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_max"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicFetchMin : AtomicBuiltin {
   let Spellings = ["__c11_atomic_fetch_min"];
-  let Attributes = [CustomTypeChecking,];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def C11AtomicThreadFence : Builtin {
   let Spellings = ["__c11_atomic_thread_fence"];
-  let Attributes = [NoThrow];
+  let Attributes = [NoThrow, Constexpr];
   let Prototype = "void(int)";
 }
 
 def C11AtomicSignalFence : Builtin {
   let Spellings = ["__c11_atomic_signal_fence"];
-  let Attributes = [NoThrow];
+  let Attributes = [NoThrow, Constexpr];
   let Prototype = "void(int)";
 }
 
 def C11AtomicIsLockFree : Builtin {
   let Spellings = ["__c11_atomic_is_lock_free"];
-  let Attributes = [NoThrow];
+  let Attributes = [NoThrow, Constexpr];
   let Prototype = "bool(size_t)";
 }
 
 // GNU atomic builtins.
 def AtomicLoad : AtomicBuiltin {
   let Spellings = ["__atomic_load"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicLoadN : AtomicBuiltin {
   let Spellings = ["__atomic_load_n"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicStore : AtomicBuiltin {
   let Spellings = ["__atomic_store"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicStoreN : AtomicBuiltin {
   let Spellings = ["__atomic_store_n"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicExchange : AtomicBuiltin {
   let Spellings = ["__atomic_exchange"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicExchangeN : AtomicBuiltin {
   let Spellings = ["__atomic_exchange_n"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicCompareExchange : AtomicBuiltin {
   let Spellings = ["__atomic_compare_exchange"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicCompareExchangeN : AtomicBuiltin {
   let Spellings = ["__atomic_compare_exchange_n"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicFetchAdd : AtomicBuiltin {
   let Spellings = ["__atomic_fetch_add"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicFetchSub : AtomicBuiltin {
   let Spellings = ["__atomic_fetch_sub"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicFetchAnd : AtomicBuiltin {
   let Spellings = ["__atomic_fetch_and"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicFetchOr : AtomicBuiltin {
   let Spellings = ["__atomic_fetch_or"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicFetchXor : AtomicBuiltin {
   let Spellings = ["__atomic_fetch_xor"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicFetchNand : AtomicBuiltin {
   let Spellings = ["__atomic_fetch_nand"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicAddFetch : AtomicBuiltin {
   let Spellings = ["__atomic_add_fetch"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicSubFetch : AtomicBuiltin {
   let Spellings = ["__atomic_sub_fetch"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicAndFetch : AtomicBuiltin {
   let Spellings = ["__atomic_and_fetch"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicOrFetch : AtomicBuiltin {
   let Spellings = ["__atomic_or_fetch"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicXorFetch : AtomicBuiltin {
   let Spellings = ["__atomic_xor_fetch"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicMaxFetch : AtomicBuiltin {
   let Spellings = ["__atomic_max_fetch"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicMinFetch : AtomicBuiltin {
   let Spellings = ["__atomic_min_fetch"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
 def AtomicNandFetch : AtomicBuiltin {
   let Spellings = ["__atomic_nand_fetch"];
-  let Attributes = [CustomTypeChecking];
+  let Attributes = [CustomTypeChecking, Constexpr];
   let Prototype = "void(...)";
 }
 
@@ -1991,7 +1991,7 @@ def AtomicClear : Builtin {
 
 def AtomicThreadFence : Builtin {
   let Spellings = ["__atomic_thread_fence"];
-  let Attributes = [NoThrow];
+  let Attributes = [NoThrow, Constexpr];
   let Prototype = "void(int)";
 }
 
@@ -2003,7 +2003,7 @@ def ScopedAtomicThreadFence : Builtin {
 
 def AtomicSignalFence : Builtin {
   let Spellings = ["__atomic_signal_fence"];
-  let Attributes = [NoThrow];
+  let Attributes = [NoThrow, Constexpr];
   let Prototype = "void(int)";
 }
 



More information about the cfe-commits mailing list