[clang] [compiler-rt] [llvm] [clang] builtins for atomicrmw fminmax/_num (PR #187139)

via cfe-commits cfe-commits at lists.llvm.org
Fri May 1 11:43:49 PDT 2026


https://github.com/gonzalobg updated https://github.com/llvm/llvm-project/pull/187139

>From e18ac24f3d3750ba4f62ac5f2f74e2ba36b5b207 Mon Sep 17 00:00:00 2001
From: Gonzalo Brito Gadeschi <gonzalob at nvidia.com>
Date: Tue, 17 Mar 2026 15:24:47 -0700
Subject: [PATCH 1/6] [clang] builtins for atomicrmw fminmax/_num

---
 clang/include/clang/Basic/Builtins.td         |  96 +++++
 .../clang/Basic/DiagnosticSemaKinds.td        |   3 +
 clang/lib/AST/Expr.cpp                        |  16 +
 clang/lib/CodeGen/CGAtomic.cpp                | 109 ++++-
 clang/lib/Sema/SemaChecking.cpp               |  32 +-
 .../AArch64/atomic-ops-float-check-minmax.c   |  59 +++
 clang/test/Sema/atomic-ops.c                  |  88 +++-
 clang/test/Sema/scoped-atomic-ops.c           |  32 ++
 .../builtins/Unit/atomic_fp_minmax_test.c     | 397 ++++++++++++++++++
 9 files changed, 818 insertions(+), 14 deletions(-)
 create mode 100644 compiler-rt/test/builtins/Unit/atomic_fp_minmax_test.c

diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index a1c99ccba7676..ea4b0cee32213 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -2139,6 +2139,30 @@ def AtomicMinFetch : AtomicBuiltin {
   let Prototype = "void(...)";
 }
 
+def AtomicFMinimumFetch : AtomicBuiltin {
+  let Spellings = ["__atomic_fminimum_fetch"];
+  let Attributes = [CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
+def AtomicFMaximumFetch : AtomicBuiltin {
+  let Spellings = ["__atomic_fmaximum_fetch"];
+  let Attributes = [CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
+def AtomicFMinimumNumFetch : AtomicBuiltin {
+  let Spellings = ["__atomic_fminimum_num_fetch"];
+  let Attributes = [CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
+def AtomicFMaximumNumFetch : AtomicBuiltin {
+  let Spellings = ["__atomic_fmaximum_num_fetch"];
+  let Attributes = [CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
 def AtomicNandFetch : AtomicBuiltin {
   let Spellings = ["__atomic_nand_fetch"];
   let Attributes = [CustomTypeChecking];
@@ -2284,6 +2308,30 @@ def ScopedAtomicFetchMax : AtomicBuiltin {
   let Prototype = "void(...)";
 }
 
+def ScopedAtomicFetchFMinimum : AtomicBuiltin {
+  let Spellings = ["__scoped_atomic_fetch_fminimum"];
+  let Attributes = [CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
+def ScopedAtomicFetchFMaximum : AtomicBuiltin {
+  let Spellings = ["__scoped_atomic_fetch_fmaximum"];
+  let Attributes = [CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
+def ScopedAtomicFetchFMinimumNum : AtomicBuiltin {
+  let Spellings = ["__scoped_atomic_fetch_fminimum_num"];
+  let Attributes = [CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
+def ScopedAtomicFetchFMaximumNum : AtomicBuiltin {
+  let Spellings = ["__scoped_atomic_fetch_fmaximum_num"];
+  let Attributes = [CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
 def ScopedAtomicAddFetch : AtomicBuiltin {
   let Spellings = ["__scoped_atomic_add_fetch"];
   let Attributes = [CustomTypeChecking];
@@ -2332,6 +2380,30 @@ def ScopedAtomicMaxFetch : AtomicBuiltin {
   let Prototype = "void(...)";
 }
 
+def ScopedAtomicFMinimumFetch : AtomicBuiltin {
+  let Spellings = ["__scoped_atomic_fminimum_fetch"];
+  let Attributes = [CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
+def ScopedAtomicFMaximumFetch : AtomicBuiltin {
+  let Spellings = ["__scoped_atomic_fmaximum_fetch"];
+  let Attributes = [CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
+def ScopedAtomicFMinimumNumFetch : AtomicBuiltin {
+  let Spellings = ["__scoped_atomic_fminimum_num_fetch"];
+  let Attributes = [CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
+def ScopedAtomicFMaximumNumFetch : AtomicBuiltin {
+  let Spellings = ["__scoped_atomic_fmaximum_num_fetch"];
+  let Attributes = [CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
 def ScopedAtomicUInc : AtomicBuiltin {
   let Spellings = ["__scoped_atomic_fetch_uinc"];
   let Attributes = [CustomTypeChecking];
@@ -2436,6 +2508,30 @@ def AtomicFetchMin : AtomicBuiltin {
   let Prototype = "void(...)";
 }
 
+def AtomicFetchFMinimum : AtomicBuiltin {
+  let Spellings = ["__atomic_fetch_fminimum"];
+  let Attributes = [CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
+def AtomicFetchFMaximum : AtomicBuiltin {
+  let Spellings = ["__atomic_fetch_fmaximum"];
+  let Attributes = [CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
+def AtomicFetchFMinimumNum : AtomicBuiltin {
+  let Spellings = ["__atomic_fetch_fminimum_num"];
+  let Attributes = [CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
+def AtomicFetchFMaximumNum : AtomicBuiltin {
+  let Spellings = ["__atomic_fetch_fmaximum_num"];
+  let Attributes = [CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
 def AtomicFetchUInc : AtomicBuiltin {
   let Spellings = ["__atomic_fetch_uinc"];
   let Attributes = [CustomTypeChecking];
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index d4d09a8ecef36..30508e063ac39 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -9630,6 +9630,9 @@ def err_atomic_op_needs_atomic_int_or_fp : Error<
 def err_atomic_op_needs_atomic_int : Error<
   "address argument to atomic operation must be a pointer to "
   "%select{|atomic }0integer (%1 invalid)">;
+def err_atomic_op_needs_atomic_fp
+    : Error<"address argument to atomic operation must be a pointer to "
+            "%select{|atomic }0floating point type (%1 invalid)">;
 def warn_atomic_op_has_invalid_memory_order : Warning<
   "%select{|success |failure }0memory order argument to atomic operation is invalid">,
   InGroup<DiagGroup<"atomic-memory-ordering">>;
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 185e887fb05c3..f05d4c6a495ac 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -5288,8 +5288,16 @@ unsigned AtomicExpr::getNumSubExprs(AtomicOp Op) {
   case AO__atomic_nand_fetch:
   case AO__atomic_min_fetch:
   case AO__atomic_max_fetch:
+  case AO__atomic_fminimum_fetch:
+  case AO__atomic_fmaximum_fetch:
+  case AO__atomic_fminimum_num_fetch:
+  case AO__atomic_fmaximum_num_fetch:
   case AO__atomic_fetch_min:
   case AO__atomic_fetch_max:
+  case AO__atomic_fetch_fminimum:
+  case AO__atomic_fetch_fmaximum:
+  case AO__atomic_fetch_fminimum_num:
+  case AO__atomic_fetch_fmaximum_num:
   case AO__atomic_fetch_uinc:
   case AO__atomic_fetch_udec:
     return 3;
@@ -5311,8 +5319,16 @@ unsigned AtomicExpr::getNumSubExprs(AtomicOp Op) {
   case AO__scoped_atomic_nand_fetch:
   case AO__scoped_atomic_min_fetch:
   case AO__scoped_atomic_max_fetch:
+  case AO__scoped_atomic_fminimum_fetch:
+  case AO__scoped_atomic_fmaximum_fetch:
+  case AO__scoped_atomic_fminimum_num_fetch:
+  case AO__scoped_atomic_fmaximum_num_fetch:
   case AO__scoped_atomic_fetch_min:
   case AO__scoped_atomic_fetch_max:
+  case AO__scoped_atomic_fetch_fminimum:
+  case AO__scoped_atomic_fetch_fmaximum:
+  case AO__scoped_atomic_fetch_fminimum_num:
+  case AO__scoped_atomic_fetch_fmaximum_num:
   case AO__scoped_atomic_exchange_n:
   case AO__scoped_atomic_fetch_uinc:
   case AO__scoped_atomic_fetch_udec:
diff --git a/clang/lib/CodeGen/CGAtomic.cpp b/clang/lib/CodeGen/CGAtomic.cpp
index 859ab20bb6740..4f02b27eb4f31 100644
--- a/clang/lib/CodeGen/CGAtomic.cpp
+++ b/clang/lib/CodeGen/CGAtomic.cpp
@@ -530,10 +530,27 @@ static llvm::Value *EmitPostAtomicMinMax(CGBuilderTy &Builder,
   const bool IsFP = OldVal->getType()->isFloatingPointTy();
 
   if (IsFP) {
-    llvm::Intrinsic::ID IID = (Op == AtomicExpr::AO__atomic_max_fetch ||
-                               Op == AtomicExpr::AO__scoped_atomic_max_fetch)
-                                  ? llvm::Intrinsic::maxnum
-                                  : llvm::Intrinsic::minnum;
+    llvm::Intrinsic::ID IID;
+    if (Op == AtomicExpr::AO__atomic_max_fetch ||
+        Op == AtomicExpr::AO__scoped_atomic_max_fetch)
+      IID = llvm::Intrinsic::maxnum;
+    else if (Op == AtomicExpr::AO__atomic_min_fetch ||
+             Op == AtomicExpr::AO__scoped_atomic_min_fetch)
+      IID = llvm::Intrinsic::minnum;
+    else if (Op == AtomicExpr::AO__atomic_fmaximum_fetch ||
+             Op == AtomicExpr::AO__scoped_atomic_fmaximum_fetch)
+      IID = llvm::Intrinsic::maximum;
+    else if (Op == AtomicExpr::AO__atomic_fminimum_fetch ||
+             Op == AtomicExpr::AO__scoped_atomic_fminimum_fetch)
+      IID = llvm::Intrinsic::minimum;
+    else if (Op == AtomicExpr::AO__atomic_fmaximum_num_fetch ||
+             Op == AtomicExpr::AO__scoped_atomic_fmaximum_num_fetch)
+      IID = llvm::Intrinsic::maximumnum;
+    else if (Op == AtomicExpr::AO__atomic_fminimum_num_fetch ||
+             Op == AtomicExpr::AO__scoped_atomic_fminimum_num_fetch)
+      IID = llvm::Intrinsic::minimumnum;
+    else
+      llvm_unreachable("Unexpected atomic FP min/max operation");
 
     return Builder.CreateBinaryIntrinsic(IID, OldVal, RHS, llvm::FMFSource(),
                                          "newval");
@@ -545,10 +562,18 @@ static llvm::Value *EmitPostAtomicMinMax(CGBuilderTy &Builder,
     llvm_unreachable("Unexpected min/max operation");
   case AtomicExpr::AO__atomic_max_fetch:
   case AtomicExpr::AO__scoped_atomic_max_fetch:
+  case AtomicExpr::AO__atomic_fmaximum_fetch:
+  case AtomicExpr::AO__scoped_atomic_fmaximum_fetch:
+  case AtomicExpr::AO__atomic_fmaximum_num_fetch:
+  case AtomicExpr::AO__scoped_atomic_fmaximum_num_fetch:
     Pred = IsSigned ? llvm::CmpInst::ICMP_SGT : llvm::CmpInst::ICMP_UGT;
     break;
   case AtomicExpr::AO__atomic_min_fetch:
   case AtomicExpr::AO__scoped_atomic_min_fetch:
+  case AtomicExpr::AO__atomic_fminimum_fetch:
+  case AtomicExpr::AO__scoped_atomic_fminimum_fetch:
+  case AtomicExpr::AO__atomic_fminimum_num_fetch:
+  case AtomicExpr::AO__scoped_atomic_fminimum_num_fetch:
     Pred = IsSigned ? llvm::CmpInst::ICMP_SLT : llvm::CmpInst::ICMP_ULT;
     break;
   }
@@ -705,6 +730,28 @@ static void EmitAtomicOp(CodeGenFunction &CGF, AtomicExpr *E, Address Dest,
                     : llvm::AtomicRMWInst::UMin);
     break;
 
+  case AtomicExpr::AO__atomic_fminimum_fetch:
+  case AtomicExpr::AO__scoped_atomic_fminimum_fetch:
+    PostOpMinMax = true;
+    [[fallthrough]];
+  case AtomicExpr::AO__atomic_fetch_fminimum:
+  case AtomicExpr::AO__scoped_atomic_fetch_fminimum:
+    assert(E->getValueType()->isFloatingType() &&
+           "fminimum operations only support floating-point types");
+    Op = llvm::AtomicRMWInst::FMinimum;
+    break;
+
+  case AtomicExpr::AO__atomic_fminimum_num_fetch:
+  case AtomicExpr::AO__scoped_atomic_fminimum_num_fetch:
+    PostOpMinMax = true;
+    [[fallthrough]];
+  case AtomicExpr::AO__atomic_fetch_fminimum_num:
+  case AtomicExpr::AO__scoped_atomic_fetch_fminimum_num:
+    assert(E->getValueType()->isFloatingType() &&
+           "fminimum_num operations only support floating-point types");
+    Op = llvm::AtomicRMWInst::FMinimumNum;
+    break;
+
   case AtomicExpr::AO__atomic_max_fetch:
   case AtomicExpr::AO__scoped_atomic_max_fetch:
     PostOpMinMax = true;
@@ -721,6 +768,28 @@ static void EmitAtomicOp(CodeGenFunction &CGF, AtomicExpr *E, Address Dest,
                     : llvm::AtomicRMWInst::UMax);
     break;
 
+  case AtomicExpr::AO__atomic_fmaximum_fetch:
+  case AtomicExpr::AO__scoped_atomic_fmaximum_fetch:
+    PostOpMinMax = true;
+    [[fallthrough]];
+  case AtomicExpr::AO__atomic_fetch_fmaximum:
+  case AtomicExpr::AO__scoped_atomic_fetch_fmaximum:
+    assert(E->getValueType()->isFloatingType() &&
+           "fmaximum operations only support floating-point types");
+    Op = llvm::AtomicRMWInst::FMaximum;
+    break;
+
+  case AtomicExpr::AO__atomic_fmaximum_num_fetch:
+  case AtomicExpr::AO__scoped_atomic_fmaximum_num_fetch:
+    PostOpMinMax = true;
+    [[fallthrough]];
+  case AtomicExpr::AO__atomic_fetch_fmaximum_num:
+  case AtomicExpr::AO__scoped_atomic_fetch_fmaximum_num:
+    assert(E->getValueType()->isFloatingType() &&
+           "fmaximum_num operations only support floating-point types");
+    Op = llvm::AtomicRMWInst::FMaximumNum;
+    break;
+
   case AtomicExpr::AO__atomic_and_fetch:
   case AtomicExpr::AO__scoped_atomic_and_fetch:
     PostOp = llvm::Instruction::And;
@@ -1047,10 +1116,18 @@ RValue CodeGenFunction::EmitAtomicExpr(AtomicExpr *E) {
   case AtomicExpr::AO__scoped_atomic_fetch_max:
   case AtomicExpr::AO__scoped_atomic_fetch_min:
   case AtomicExpr::AO__scoped_atomic_fetch_sub:
+  case AtomicExpr::AO__scoped_atomic_fetch_fminimum:
+  case AtomicExpr::AO__scoped_atomic_fetch_fmaximum:
+  case AtomicExpr::AO__scoped_atomic_fetch_fminimum_num:
+  case AtomicExpr::AO__scoped_atomic_fetch_fmaximum_num:
   case AtomicExpr::AO__scoped_atomic_add_fetch:
   case AtomicExpr::AO__scoped_atomic_max_fetch:
   case AtomicExpr::AO__scoped_atomic_min_fetch:
   case AtomicExpr::AO__scoped_atomic_sub_fetch:
+  case AtomicExpr::AO__scoped_atomic_fminimum_fetch:
+  case AtomicExpr::AO__scoped_atomic_fmaximum_fetch:
+  case AtomicExpr::AO__scoped_atomic_fminimum_num_fetch:
+  case AtomicExpr::AO__scoped_atomic_fmaximum_num_fetch:
     [[fallthrough]];
 
   case AtomicExpr::AO__atomic_fetch_and:
@@ -1093,6 +1170,14 @@ RValue CodeGenFunction::EmitAtomicExpr(AtomicExpr *E) {
   case AtomicExpr::AO__scoped_atomic_exchange_n:
   case AtomicExpr::AO__scoped_atomic_fetch_uinc:
   case AtomicExpr::AO__scoped_atomic_fetch_udec:
+  case AtomicExpr::AO__atomic_fetch_fminimum:
+  case AtomicExpr::AO__atomic_fetch_fmaximum:
+  case AtomicExpr::AO__atomic_fetch_fminimum_num:
+  case AtomicExpr::AO__atomic_fetch_fmaximum_num:
+  case AtomicExpr::AO__atomic_fminimum_fetch:
+  case AtomicExpr::AO__atomic_fmaximum_fetch:
+  case AtomicExpr::AO__atomic_fminimum_num_fetch:
+  case AtomicExpr::AO__atomic_fmaximum_num_fetch:
     Val1 = EmitValToTemp(*this, E->getVal1());
     break;
   }
@@ -1292,6 +1377,22 @@ RValue CodeGenFunction::EmitAtomicExpr(AtomicExpr *E) {
     case AtomicExpr::AO__opencl_atomic_fetch_max:
     case AtomicExpr::AO__scoped_atomic_fetch_max:
     case AtomicExpr::AO__scoped_atomic_max_fetch:
+    case AtomicExpr::AO__atomic_fetch_fminimum:
+    case AtomicExpr::AO__atomic_fetch_fmaximum:
+    case AtomicExpr::AO__atomic_fetch_fminimum_num:
+    case AtomicExpr::AO__atomic_fetch_fmaximum_num:
+    case AtomicExpr::AO__scoped_atomic_fetch_fminimum:
+    case AtomicExpr::AO__scoped_atomic_fetch_fmaximum:
+    case AtomicExpr::AO__scoped_atomic_fetch_fminimum_num:
+    case AtomicExpr::AO__scoped_atomic_fetch_fmaximum_num:
+    case AtomicExpr::AO__atomic_fminimum_fetch:
+    case AtomicExpr::AO__atomic_fmaximum_fetch:
+    case AtomicExpr::AO__atomic_fminimum_num_fetch:
+    case AtomicExpr::AO__atomic_fmaximum_num_fetch:
+    case AtomicExpr::AO__scoped_atomic_fminimum_fetch:
+    case AtomicExpr::AO__scoped_atomic_fmaximum_fetch:
+    case AtomicExpr::AO__scoped_atomic_fminimum_num_fetch:
+    case AtomicExpr::AO__scoped_atomic_fmaximum_num_fetch:
     case AtomicExpr::AO__scoped_atomic_fetch_uinc:
     case AtomicExpr::AO__scoped_atomic_fetch_udec:
     case AtomicExpr::AO__atomic_test_and_set:
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 29add9d092e6b..e71ad1f21bcbb 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -4701,6 +4701,25 @@ ExprResult Sema::BuildAtomicExpr(SourceRange CallRange, SourceRange ExprRange,
     ArithAllows = AOEVT_Pointer | AOEVT_FP;
     Form = Arithmetic;
     break;
+  case AtomicExpr::AO__atomic_fetch_fminimum:
+  case AtomicExpr::AO__atomic_fetch_fmaximum:
+  case AtomicExpr::AO__atomic_fminimum_fetch:
+  case AtomicExpr::AO__atomic_fmaximum_fetch:
+  case AtomicExpr::AO__atomic_fetch_fminimum_num:
+  case AtomicExpr::AO__atomic_fetch_fmaximum_num:
+  case AtomicExpr::AO__atomic_fminimum_num_fetch:
+  case AtomicExpr::AO__atomic_fmaximum_num_fetch:
+  case AtomicExpr::AO__scoped_atomic_fetch_fminimum:
+  case AtomicExpr::AO__scoped_atomic_fetch_fmaximum:
+  case AtomicExpr::AO__scoped_atomic_fminimum_fetch:
+  case AtomicExpr::AO__scoped_atomic_fmaximum_fetch:
+  case AtomicExpr::AO__scoped_atomic_fetch_fminimum_num:
+  case AtomicExpr::AO__scoped_atomic_fetch_fmaximum_num:
+  case AtomicExpr::AO__scoped_atomic_fminimum_num_fetch:
+  case AtomicExpr::AO__scoped_atomic_fmaximum_num_fetch:
+    ArithAllows = AOEVT_FP;
+    Form = Arithmetic;
+    break;
   case AtomicExpr::AO__atomic_fetch_max:
   case AtomicExpr::AO__atomic_fetch_min:
   case AtomicExpr::AO__atomic_max_fetch:
@@ -4715,7 +4734,7 @@ ExprResult Sema::BuildAtomicExpr(SourceRange CallRange, SourceRange ExprRange,
   case AtomicExpr::AO__opencl_atomic_fetch_min:
   case AtomicExpr::AO__hip_atomic_fetch_max:
   case AtomicExpr::AO__hip_atomic_fetch_min:
-    ArithAllows = AOEVT_FP;
+    ArithAllows = AOEVT_Pointer | AOEVT_FP;
     Form = Arithmetic;
     break;
   case AtomicExpr::AO__c11_atomic_fetch_and:
@@ -4893,7 +4912,9 @@ ExprResult Sema::BuildAtomicExpr(SourceRange CallRange, SourceRange ExprRange,
           &Context.getTargetInfo().getLongDoubleFormat() ==
               &llvm::APFloat::x87DoubleExtended();
       if (ValType->isIntegerType())
-        return true;
+        // Special case: f-prefixed operations (AOEVT_FP exactly) reject
+        // integers
+        return AllowedType != AOEVT_FP;
       if (ValType->isPointerType())
         return AllowedType & AOEVT_Pointer;
       if (!(ValType->isFloatingType() && (AllowedType & AOEVT_FP)))
@@ -4904,13 +4925,16 @@ ExprResult Sema::BuildAtomicExpr(SourceRange CallRange, SourceRange ExprRange,
       return true;
     };
     if (!IsAllowedValueType(ValType, ArithAllows)) {
-      auto DID = ArithAllows & AOEVT_FP
+      auto DID =
+          ArithAllows == AOEVT_FP
+              ? diag::err_atomic_op_needs_atomic_fp
+              : (ArithAllows & AOEVT_FP
                      ? (ArithAllows & AOEVT_Pointer
                             ? diag::err_atomic_op_needs_atomic_int_ptr_or_fp
                             : diag::err_atomic_op_needs_atomic_int_or_fp)
                      : (ArithAllows & AOEVT_Pointer
                             ? diag::err_atomic_op_needs_atomic_int_or_ptr
-                            : diag::err_atomic_op_needs_atomic_int);
+                            : diag::err_atomic_op_needs_atomic_int));
       Diag(ExprRange.getBegin(), DID)
           << IsC11 << Ptr->getType() << Ptr->getSourceRange();
       return ExprError();
diff --git a/clang/test/CodeGen/AArch64/atomic-ops-float-check-minmax.c b/clang/test/CodeGen/AArch64/atomic-ops-float-check-minmax.c
index 4d9b29b789507..d525a6d86f1f1 100644
--- a/clang/test/CodeGen/AArch64/atomic-ops-float-check-minmax.c
+++ b/clang/test/CodeGen/AArch64/atomic-ops-float-check-minmax.c
@@ -115,3 +115,62 @@ void test_minmax_postop(float *f32, _Float16 *f16, __bf16 *bf16, double *f64) {
   *f16  = __atomic_min_fetch(f16,  42.1, memory_order_release);
   *bf16 = __atomic_min_fetch(bf16, 42.1, memory_order_release);
 }
+
+// CHECK-LABEL: define dso_local void @test_fminimum_fmaximum_postop(
+// CHECK-SAME: ptr noundef [[F32:%.*]], ptr noundef [[F16:%.*]], ptr noundef [[BF16:%.*]], ptr noundef [[F64:%.*]]) #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK:    [[TMP0:%.*]] = load ptr, ptr [[F64_ADDR]], align 8
+// CHECK:    store double 4.210000e+01, ptr [[DOTATOMICTMP]], align 8
+// CHECK:    [[TMP1:%.*]] = load double, ptr [[DOTATOMICTMP]], align 8
+// CHECK:    [[TMP2:%.*]] = atomicrmw fmaximum ptr [[TMP0]], double [[TMP1]] release, align 8
+// CHECK:    [[NEWVAL:%.*]] = call double @llvm.maximum.f64(double [[TMP2]], double [[TMP1]])
+// CHECK:    store double [[NEWVAL]], ptr [[ATOMIC_TEMP]], align 8
+// CHECK:    [[TMP3:%.*]] = load double, ptr [[ATOMIC_TEMP]], align 8
+// CHECK:    [[TMP4:%.*]] = load ptr, ptr [[F64_ADDR]], align 8
+// CHECK:    store double [[TMP3]], ptr [[TMP4]], align 8
+void test_fminimum_fmaximum_postop(float *f32, _Float16 *f16, __bf16 *bf16, double *f64) {
+  *f64  = __atomic_fmaximum_fetch(f64,  42.1, memory_order_release);
+  *f32  = __atomic_fmaximum_fetch(f32,  42.1, memory_order_release);
+  *f16  = __atomic_fmaximum_fetch(f16,  42.1, memory_order_release);
+  *bf16 = __atomic_fmaximum_fetch(bf16, 42.1, memory_order_release);
+  *f64  = __atomic_fminimum_fetch(f64,  42.1, memory_order_release);
+  *f32  = __atomic_fminimum_fetch(f32,  42.1, memory_order_release);
+  *f16  = __atomic_fminimum_fetch(f16,  42.1, memory_order_release);
+  *bf16 = __atomic_fminimum_fetch(bf16, 42.1, memory_order_release);
+}
+
+// CHECK-LABEL: define dso_local void @test_fminimumnum_fmaximumnum_postop(
+// CHECK-SAME: ptr noundef [[F32:%.*]], ptr noundef [[F16:%.*]], ptr noundef [[BF16:%.*]], ptr noundef [[F64:%.*]]) #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK:    [[TMP0:%.*]] = load ptr, ptr [[F64_ADDR]], align 8
+// CHECK:    store double 4.210000e+01, ptr [[DOTATOMICTMP]], align 8
+// CHECK:    [[TMP1:%.*]] = load double, ptr [[DOTATOMICTMP]], align 8
+// CHECK:    [[TMP2:%.*]] = atomicrmw fmaximumnum ptr [[TMP0]], double [[TMP1]] release, align 8
+// CHECK:    [[NEWVAL:%.*]] = call double @llvm.maximumnum.f64(double [[TMP2]], double [[TMP1]])
+// CHECK:    store double [[NEWVAL]], ptr [[ATOMIC_TEMP]], align 8
+// CHECK:    [[TMP3:%.*]] = load double, ptr [[ATOMIC_TEMP]], align 8
+// CHECK:    [[TMP4:%.*]] = load ptr, ptr [[F64_ADDR]], align 8
+// CHECK:    store double [[TMP3]], ptr [[TMP4]], align 8
+void test_fminimumnum_fmaximumnum_postop(float *f32, _Float16 *f16, __bf16 *bf16, double *f64) {
+  *f64  = __atomic_fmaximum_num_fetch(f64,  42.1, memory_order_release);
+  *f32  = __atomic_fmaximum_num_fetch(f32,  42.1, memory_order_release);
+  *f16  = __atomic_fmaximum_num_fetch(f16,  42.1, memory_order_release);
+  *bf16 = __atomic_fmaximum_num_fetch(bf16, 42.1, memory_order_release);
+  *f64  = __atomic_fminimum_num_fetch(f64,  42.1, memory_order_release);
+  *f32  = __atomic_fminimum_num_fetch(f32,  42.1, memory_order_release);
+  *f16  = __atomic_fminimum_num_fetch(f16,  42.1, memory_order_release);
+  *bf16 = __atomic_fminimum_num_fetch(bf16, 42.1, memory_order_release);
+}
+
+// CHECK-LABEL: define dso_local void @test_fetch_variants(
+// CHECK-SAME: ptr noundef [[F64:%.*]]) #[[ATTR0:[0-9]+]] {
+// CHECK:    [[TMP0:%.*]] = atomicrmw fminimum ptr {{%.*}}, double {{%.*}} release, align 8
+// CHECK:    [[TMP1:%.*]] = atomicrmw fmaximum ptr {{%.*}}, double {{%.*}} release, align 8
+// CHECK:    [[TMP2:%.*]] = atomicrmw fminimumnum ptr {{%.*}}, double {{%.*}} release, align 8
+// CHECK:    [[TMP3:%.*]] = atomicrmw fmaximumnum ptr {{%.*}}, double {{%.*}} release, align 8
+void test_fetch_variants(double *f64) {
+  double old1 = __atomic_fetch_fminimum(f64, 42.1, memory_order_release);
+  double old2 = __atomic_fetch_fmaximum(f64, 42.1, memory_order_release);
+  double old3 = __atomic_fetch_fminimum_num(f64, 42.1, memory_order_release);
+  double old4 = __atomic_fetch_fmaximum_num(f64, 42.1, memory_order_release);
+}
diff --git a/clang/test/Sema/atomic-ops.c b/clang/test/Sema/atomic-ops.c
index 3318e369f3e0d..d930af0d0e139 100644
--- a/clang/test/Sema/atomic-ops.c
+++ b/clang/test/Sema/atomic-ops.c
@@ -231,15 +231,15 @@ void f(_Atomic(int) *i, const _Atomic(int) *ci,
   __c11_atomic_fetch_add(d, 1.0, memory_order_seq_cst);
   __c11_atomic_fetch_add(ld, 1.0, memory_order_seq_cst); // fp80-error {{must be a pointer to atomic integer, pointer or supported floating point type}}
   __c11_atomic_fetch_min(i, 1, memory_order_seq_cst);
-  __c11_atomic_fetch_min(p, 1, memory_order_seq_cst); // expected-error {{must be a pointer to atomic integer or supported floating point type}}
+  __c11_atomic_fetch_min(p, 1, memory_order_seq_cst);
   __c11_atomic_fetch_min(f, 1.0f, memory_order_seq_cst);
   __c11_atomic_fetch_min(d, 1.0, memory_order_seq_cst);
-  __c11_atomic_fetch_min(ld, 1.0, memory_order_seq_cst); // fp80-error {{must be a pointer to atomic integer or supported floating point type}}
+  __c11_atomic_fetch_min(ld, 1.0, memory_order_seq_cst); // fp80-error {{must be a pointer to atomic integer, pointer or supported floating point type}}
   __c11_atomic_fetch_max(i, 1, memory_order_seq_cst);
-  __c11_atomic_fetch_max(p, 1, memory_order_seq_cst); // expected-error {{must be a pointer to atomic integer or supported floating point type}}
+  __c11_atomic_fetch_max(p, 1, memory_order_seq_cst);
   __c11_atomic_fetch_max(f, 1.0f, memory_order_seq_cst);
   __c11_atomic_fetch_max(d, 1.0, memory_order_seq_cst);
-  __c11_atomic_fetch_max(ld, 1.0, memory_order_seq_cst); // fp80-error {{must be a pointer to atomic integer or supported floating point type}}
+  __c11_atomic_fetch_max(ld, 1.0, memory_order_seq_cst); // fp80-error {{must be a pointer to atomic integer, pointer or supported floating point type}}
 
   __atomic_fetch_add(i, 3, memory_order_seq_cst); // expected-error {{pointer to integer, pointer or supported floating point type}}
   __atomic_fetch_sub(I, 3, memory_order_seq_cst);
@@ -250,8 +250,20 @@ void f(_Atomic(int) *i, const _Atomic(int) *ci,
   __atomic_fetch_min(D, 3, memory_order_seq_cst);
   __atomic_fetch_max(F, 3, memory_order_seq_cst);
   __atomic_fetch_max(D, 3, memory_order_seq_cst);
-  __atomic_fetch_max(P, 3, memory_order_seq_cst); // expected-error {{must be a pointer to integer or supported floating point type}}
+  __atomic_fetch_max(P, 3, memory_order_seq_cst);
   __atomic_fetch_max(p, 3);                       // expected-error {{too few arguments to function call, expected 3, have 2}}
+  __atomic_fetch_fminimum(F, 3, memory_order_seq_cst);
+  __atomic_fetch_fminimum(D, 3, memory_order_seq_cst);
+  __atomic_fetch_fmaximum(F, 3, memory_order_seq_cst);
+  __atomic_fetch_fmaximum(D, 3, memory_order_seq_cst);
+  __atomic_fetch_fmaximum(P, 3, memory_order_seq_cst); // expected-error {{must be a pointer to floating point type}}
+  __atomic_fetch_fmaximum(p, 3);                       // expected-error {{too few arguments to function call, expected 3, have 2}}
+  __atomic_fetch_fminimum_num(F, 3, memory_order_seq_cst);
+  __atomic_fetch_fminimum_num(D, 3, memory_order_seq_cst);
+  __atomic_fetch_fmaximum_num(F, 3, memory_order_seq_cst);
+  __atomic_fetch_fmaximum_num(D, 3, memory_order_seq_cst);
+  __atomic_fetch_fmaximum_num(P, 3, memory_order_seq_cst); // expected-error {{must be a pointer to floating point type}}
+  __atomic_fetch_fmaximum_num(p, 3);                       // expected-error {{too few arguments to function call, expected 3, have 2}}
 
   __atomic_fetch_uinc(F, 1, memory_order_seq_cst); // expected-error {{address argument to atomic operation must be a pointer to integer}}
   __atomic_fetch_udec(F, 1, memory_order_seq_cst); // expected-error {{address argument to atomic operation must be a pointer to integer}}
@@ -388,7 +400,7 @@ void PR16931(int* x) { // expected-note {{passing argument to parameter 'x' here
   PR16931(&flagvar); // expected-error {{incompatible pointer types}}
 }
 
-void memory_checks(_Atomic(int) *Ap, int *p, int val) {
+void memory_checks(_Atomic(int) *Ap, int *p, int val, float *fp, float fval) {
   (void)__c11_atomic_load(Ap, memory_order_relaxed);
   (void)__c11_atomic_load(Ap, memory_order_acquire);
   (void)__c11_atomic_load(Ap, memory_order_consume);
@@ -601,6 +613,20 @@ void memory_checks(_Atomic(int) *Ap, int *p, int val) {
   (void)__atomic_fetch_min(p, val, memory_order_acq_rel);
   (void)__atomic_fetch_min(p, val, memory_order_seq_cst);
 
+  (void)__atomic_fetch_fminimum(fp, fval, memory_order_relaxed);
+  (void)__atomic_fetch_fminimum(fp, fval, memory_order_acquire);
+  (void)__atomic_fetch_fminimum(fp, fval, memory_order_consume);
+  (void)__atomic_fetch_fminimum(fp, fval, memory_order_release);
+  (void)__atomic_fetch_fminimum(fp, fval, memory_order_acq_rel);
+  (void)__atomic_fetch_fminimum(fp, fval, memory_order_seq_cst);
+
+  (void)__atomic_fetch_fminimum_num(fp, fval, memory_order_relaxed);
+  (void)__atomic_fetch_fminimum_num(fp, fval, memory_order_acquire);
+  (void)__atomic_fetch_fminimum_num(fp, fval, memory_order_consume);
+  (void)__atomic_fetch_fminimum_num(fp, fval, memory_order_release);
+  (void)__atomic_fetch_fminimum_num(fp, fval, memory_order_acq_rel);
+  (void)__atomic_fetch_fminimum_num(fp, fval, memory_order_seq_cst);
+
   (void)__atomic_fetch_uinc(p, val, memory_order_relaxed);
   (void)__atomic_fetch_uinc(p, val, memory_order_acquire);
   (void)__atomic_fetch_uinc(p, val, memory_order_consume);
@@ -622,6 +648,20 @@ void memory_checks(_Atomic(int) *Ap, int *p, int val) {
   (void)__atomic_fetch_max(p, val, memory_order_acq_rel);
   (void)__atomic_fetch_max(p, val, memory_order_seq_cst);
 
+  (void)__atomic_fetch_fmaximum(fp, fval, memory_order_relaxed);
+  (void)__atomic_fetch_fmaximum(fp, fval, memory_order_acquire);
+  (void)__atomic_fetch_fmaximum(fp, fval, memory_order_consume);
+  (void)__atomic_fetch_fmaximum(fp, fval, memory_order_release);
+  (void)__atomic_fetch_fmaximum(fp, fval, memory_order_acq_rel);
+  (void)__atomic_fetch_fmaximum(fp, fval, memory_order_seq_cst);
+
+  (void)__atomic_fetch_fmaximum_num(fp, fval, memory_order_relaxed);
+  (void)__atomic_fetch_fmaximum_num(fp, fval, memory_order_acquire);
+  (void)__atomic_fetch_fmaximum_num(fp, fval, memory_order_consume);
+  (void)__atomic_fetch_fmaximum_num(fp, fval, memory_order_release);
+  (void)__atomic_fetch_fmaximum_num(fp, fval, memory_order_acq_rel);
+  (void)__atomic_fetch_fmaximum_num(fp, fval, memory_order_seq_cst);
+
   (void)__atomic_and_fetch(p, val, memory_order_relaxed);
   (void)__atomic_and_fetch(p, val, memory_order_acquire);
   (void)__atomic_and_fetch(p, val, memory_order_consume);
@@ -664,6 +704,34 @@ void memory_checks(_Atomic(int) *Ap, int *p, int val) {
   (void)__atomic_min_fetch(p, val, memory_order_acq_rel);
   (void)__atomic_min_fetch(p, val, memory_order_seq_cst);
 
+  (void)__atomic_fmaximum_fetch(fp, fval, memory_order_relaxed);
+  (void)__atomic_fmaximum_fetch(fp, fval, memory_order_acquire);
+  (void)__atomic_fmaximum_fetch(fp, fval, memory_order_consume);
+  (void)__atomic_fmaximum_fetch(fp, fval, memory_order_release);
+  (void)__atomic_fmaximum_fetch(fp, fval, memory_order_acq_rel);
+  (void)__atomic_fmaximum_fetch(fp, fval, memory_order_seq_cst);
+
+  (void)__atomic_fminimum_fetch(fp, fval, memory_order_relaxed);
+  (void)__atomic_fminimum_fetch(fp, fval, memory_order_acquire);
+  (void)__atomic_fminimum_fetch(fp, fval, memory_order_consume);
+  (void)__atomic_fminimum_fetch(fp, fval, memory_order_release);
+  (void)__atomic_fminimum_fetch(fp, fval, memory_order_acq_rel);
+  (void)__atomic_fminimum_fetch(fp, fval, memory_order_seq_cst);
+
+  (void)__atomic_fmaximum_num_fetch(fp, fval, memory_order_relaxed);
+  (void)__atomic_fmaximum_num_fetch(fp, fval, memory_order_acquire);
+  (void)__atomic_fmaximum_num_fetch(fp, fval, memory_order_consume);
+  (void)__atomic_fmaximum_num_fetch(fp, fval, memory_order_release);
+  (void)__atomic_fmaximum_num_fetch(fp, fval, memory_order_acq_rel);
+  (void)__atomic_fmaximum_num_fetch(fp, fval, memory_order_seq_cst);
+
+  (void)__atomic_fminimum_num_fetch(fp, fval, memory_order_relaxed);
+  (void)__atomic_fminimum_num_fetch(fp, fval, memory_order_acquire);
+  (void)__atomic_fminimum_num_fetch(fp, fval, memory_order_consume);
+  (void)__atomic_fminimum_num_fetch(fp, fval, memory_order_release);
+  (void)__atomic_fminimum_num_fetch(fp, fval, memory_order_acq_rel);
+  (void)__atomic_fminimum_num_fetch(fp, fval, memory_order_seq_cst);
+
   (void)__atomic_exchange_n(p, val, memory_order_relaxed);
   (void)__atomic_exchange_n(p, val, memory_order_acquire);
   (void)__atomic_exchange_n(p, val, memory_order_consume);
@@ -851,6 +919,14 @@ void nullPointerWarning(void) {
   (void)__atomic_fetch_min((int*)0, 42, memory_order_relaxed); // expected-warning {{null passed to a callee that requires a non-null argument}}
   (void)__atomic_fetch_max((volatile int*)0, 42, memory_order_relaxed); // expected-warning {{null passed to a callee that requires a non-null argument}}
   (void)__atomic_fetch_max((int*)0, 42, memory_order_relaxed); // expected-warning {{null passed to a callee that requires a non-null argument}}
+  (void)__atomic_fetch_fminimum((volatile float*)0, 42, memory_order_relaxed); // expected-warning {{null passed to a callee that requires a non-null argument}}
+  (void)__atomic_fetch_fminimum((float*)0, 42, memory_order_relaxed); // expected-warning {{null passed to a callee that requires a non-null argument}}
+  (void)__atomic_fetch_fmaximum((volatile float*)0, 42, memory_order_relaxed); // expected-warning {{null passed to a callee that requires a non-null argument}}
+  (void)__atomic_fetch_fmaximum((float*)0, 42, memory_order_relaxed); // expected-warning {{null passed to a callee that requires a non-null argument}}
+  (void)__atomic_fetch_fminimum_num((volatile float*)0, 42, memory_order_relaxed); // expected-warning {{null passed to a callee that requires a non-null argument}}
+  (void)__atomic_fetch_fminimum_num((float*)0, 42, memory_order_relaxed); // expected-warning {{null passed to a callee that requires a non-null argument}}
+  (void)__atomic_fetch_fmaximum_num((volatile float*)0, 42, memory_order_relaxed); // expected-warning {{null passed to a callee that requires a non-null argument}}
+  (void)__atomic_fetch_fmaximum_num((float*)0, 42, memory_order_relaxed); // expected-warning {{null passed to a callee that requires a non-null argument}}
 
   // These don't warn: the "desired" parameter is passed by value. Even for
   // atomic pointers the "desired" result can be NULL.
diff --git a/clang/test/Sema/scoped-atomic-ops.c b/clang/test/Sema/scoped-atomic-ops.c
index 49ddc64ce23eb..716cf14d0b4a3 100644
--- a/clang/test/Sema/scoped-atomic-ops.c
+++ b/clang/test/Sema/scoped-atomic-ops.c
@@ -93,6 +93,38 @@ void fi3e(float *a, float *b, float *c, float *d, float *e, float *f) {
   *e = __scoped_atomic_fetch_udec(e, 1u, __ATOMIC_RELAXED, 42); // expected-error {{address argument to atomic operation must be a pointer to integer ('float *' invalid)}}
 }
 
+void fi3f(float *a, float *b, float *c, float *d, double *e, double *f, double *g, double *h) {
+  *a = __scoped_atomic_fetch_fminimum(a, 1.0f, __ATOMIC_RELAXED, __MEMORY_SCOPE_SYSTEM);
+  *b = __scoped_atomic_fetch_fmaximum(b, 1.0f, __ATOMIC_RELAXED, __MEMORY_SCOPE_SYSTEM);
+  *c = __scoped_atomic_fetch_fminimum_num(c, 1.0f, __ATOMIC_RELAXED, __MEMORY_SCOPE_SYSTEM);
+  *d = __scoped_atomic_fetch_fmaximum_num(d, 1.0f, __ATOMIC_RELAXED, __MEMORY_SCOPE_SYSTEM);
+  *e = __scoped_atomic_fetch_fminimum(e, 1.0, __ATOMIC_RELAXED, __MEMORY_SCOPE_SYSTEM);
+  *f = __scoped_atomic_fetch_fmaximum(f, 1.0, __ATOMIC_RELAXED, __MEMORY_SCOPE_SYSTEM);
+  *g = __scoped_atomic_fetch_fminimum_num(g, 1.0, __ATOMIC_RELAXED, __MEMORY_SCOPE_SYSTEM);
+  *h = __scoped_atomic_fetch_fmaximum_num(h, 1.0, __ATOMIC_RELAXED, __MEMORY_SCOPE_SYSTEM);
+}
+
+void fi3g(float *a, float *b, float *c, float *d) {
+  *a = __scoped_atomic_fetch_fminimum(a, 1.0f, __ATOMIC_RELAXED); // expected-error {{too few arguments to function call, expected 4, have 3}}
+  *b = __scoped_atomic_fetch_fmaximum(b, 1.0f, __ATOMIC_RELAXED); // expected-error {{too few arguments to function call, expected 4, have 3}}
+  *c = __scoped_atomic_fetch_fminimum_num(c, 1.0f, __ATOMIC_RELAXED); // expected-error {{too few arguments to function call, expected 4, have 3}}
+  *d = __scoped_atomic_fetch_fmaximum_num(d, 1.0f, __ATOMIC_RELAXED); // expected-error {{too few arguments to function call, expected 4, have 3}}
+}
+
+void fi3h(float *a, float *b, float *c, float *d) {
+  *a = __scoped_atomic_fetch_fminimum(a, 1.0f, __ATOMIC_RELAXED, 42); // expected-error {{synchronization scope argument to atomic operation is invalid}}
+  *b = __scoped_atomic_fetch_fmaximum(b, 1.0f, __ATOMIC_RELAXED, 42); // expected-error {{synchronization scope argument to atomic operation is invalid}}
+  *c = __scoped_atomic_fetch_fminimum_num(c, 1.0f, __ATOMIC_RELAXED, 42); // expected-error {{synchronization scope argument to atomic operation is invalid}}
+  *d = __scoped_atomic_fetch_fmaximum_num(d, 1.0f, __ATOMIC_RELAXED, 42); // expected-error {{synchronization scope argument to atomic operation is invalid}}
+}
+
+void fi3i(int *a, int *b, int *c, int *d) {
+  *a = __scoped_atomic_fetch_fminimum(a, 1.0f, __ATOMIC_RELAXED, __MEMORY_SCOPE_SYSTEM); // expected-error {{address argument to atomic operation must be a pointer to floating point type}}
+  *b = __scoped_atomic_fetch_fmaximum(b, 1.0f, __ATOMIC_RELAXED, __MEMORY_SCOPE_SYSTEM); // expected-error {{address argument to atomic operation must be a pointer to floating point type}}
+  *c = __scoped_atomic_fetch_fminimum_num(c, 1.0f, __ATOMIC_RELAXED, __MEMORY_SCOPE_SYSTEM); // expected-error {{address argument to atomic operation must be a pointer to floating point type}}
+  *d = __scoped_atomic_fetch_fmaximum_num(d, 1.0f, __ATOMIC_RELAXED, __MEMORY_SCOPE_SYSTEM); // expected-error {{address argument to atomic operation must be a pointer to floating point type}}
+}
+
 int fi4a(int *i) {
   int cmp = 0;
   int desired = 1;
diff --git a/compiler-rt/test/builtins/Unit/atomic_fp_minmax_test.c b/compiler-rt/test/builtins/Unit/atomic_fp_minmax_test.c
new file mode 100644
index 0000000000000..b526e216be614
--- /dev/null
+++ b/compiler-rt/test/builtins/Unit/atomic_fp_minmax_test.c
@@ -0,0 +1,397 @@
+// RUN: %clang_builtins %s %librt -o %t && %run %t
+// REQUIRES: native-run
+//===-- atomic_fp_minmax_test.c - Test FP atomic min/max operations -------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file tests the floating-point atomic min/max builtins, focusing on
+// IEEE 754 corner cases: NaN, +/-infinity, +/-zero.
+//
+// There are three families of operations with different semantics:
+// 1. fminimum/fmaximum: IEEE 754-2019 minimum/maximum
+//    - Propagates NaN (any NaN input produces NaN output)
+//    - Distinguishes -0 and +0 (minimum(-0, +0) = -0, maximum(-0, +0) = +0)
+//
+// 2. fminimumnum/fmaximumnum: IEEE 754-2019 minimumNumber/maximumNumber
+//    - Propagates numbers over NaN (minimumNumber(2.0, NaN) = 2.0)
+//    - Treats -0 and +0 as equivalent
+//
+// 3. minnum/maxnum (existing __atomic_min_fetch for floats): IEEE 754-2008
+//    - Propagates numbers over NaN (minnum(2.0, NaN) = 2.0)
+//    - Treats -0 and +0 as equivalent
+//
+//===----------------------------------------------------------------------===//
+
+#include <float.h>
+#include <math.h>
+#include <stdatomic.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#undef NDEBUG
+#include <assert.h>
+
+// Memory order for all tests
+#define MO memory_order_seq_cst
+
+// Helper to check if a float is NaN
+static inline bool is_nan_f(float x) { return x != x; }
+static inline bool is_nan_d(double x) { return x != x; }
+
+// Helper to check if two floats have the same bit pattern (for +0/-0 distinction)
+static inline bool same_bits_f(float a, float b) {
+  uint32_t a_bits, b_bits;
+  memcpy(&a_bits, &a, sizeof(float));
+  memcpy(&b_bits, &b, sizeof(float));
+  return a_bits == b_bits;
+}
+
+static inline bool same_bits_d(double a, double b) {
+  uint64_t a_bits, b_bits;
+  memcpy(&a_bits, &a, sizeof(double));
+  memcpy(&b_bits, &b, sizeof(double));
+  return a_bits == b_bits;
+}
+
+// Helper to create negative zero
+static inline float neg_zero_f(void) { return -0.0f; }
+static inline double neg_zero_d(void) { return -0.0; }
+
+//===----------------------------------------------------------------------===//
+// Test fminimum_fetch and fetch_fminimum (propagates NaN, distinguishes zeros)
+//===----------------------------------------------------------------------===//
+
+void test_fminimum_float(void) {
+  printf("Testing __atomic_fminimum_fetch (float)...\n");
+
+  // Test 1: Normal values
+  {
+    float x = 5.0f;
+    float result = __atomic_fminimum_fetch(&x, 3.0f, MO);
+    assert(result == 3.0f && "fminimum(5.0, 3.0) should be 3.0");
+    assert(x == 3.0f && "stored value should be 3.0");
+  }
+
+  {
+    float x = 2.0f;
+    float result = __atomic_fminimum_fetch(&x, 7.0f, MO);
+    assert(result == 2.0f && "fminimum(2.0, 7.0) should be 2.0");
+    assert(x == 2.0f && "stored value should be 2.0");
+  }
+
+  // Test 2: NaN propagation - CRITICAL: fminimum propagates NaN
+  {
+    float x = 1.0f;
+    float result = __atomic_fminimum_fetch(&x, NAN, MO);
+    assert(is_nan_f(result) && "fminimum(1.0, NaN) should be NaN");
+    assert(is_nan_f(x) && "stored value should be NaN");
+  }
+
+  {
+    float x = NAN;
+    float result = __atomic_fminimum_fetch(&x, 1.0f, MO);
+    assert(is_nan_f(result) && "fminimum(NaN, 1.0) should be NaN");
+    assert(is_nan_f(x) && "stored value should be NaN");
+  }
+
+  // Test 3: Zero handling - CRITICAL: fminimum distinguishes -0 and +0
+  {
+    float x = 0.0f;
+    float result = __atomic_fminimum_fetch(&x, neg_zero_f(), MO);
+    assert(same_bits_f(result, neg_zero_f()) &&
+           "fminimum(+0, -0) should be -0");
+    assert(same_bits_f(x, neg_zero_f()) && "stored value should be -0");
+  }
+
+  {
+    float x = neg_zero_f();
+    float result = __atomic_fminimum_fetch(&x, 0.0f, MO);
+    assert(same_bits_f(result, neg_zero_f()) &&
+           "fminimum(-0, +0) should be -0");
+    assert(same_bits_f(x, neg_zero_f()) && "stored value should be -0");
+  }
+
+  // Test 4: Infinity
+  {
+    float x = INFINITY;
+    float result = __atomic_fminimum_fetch(&x, 1.0f, MO);
+    assert(result == 1.0f && "fminimum(+inf, 1.0) should be 1.0");
+  }
+
+  {
+    float x = -INFINITY;
+    float result = __atomic_fminimum_fetch(&x, 1.0f, MO);
+    assert(result == -INFINITY && "fminimum(-inf, 1.0) should be -inf");
+  }
+
+  // Test 5: fetch variant (returns old value)
+  {
+    float x = 5.0f;
+    float old = __atomic_fetch_fminimum(&x, 3.0f, MO);
+    assert(old == 5.0f && "fetch_fminimum should return old value");
+    assert(x == 3.0f && "stored value should be 3.0");
+  }
+
+  printf("  PASSED\n");
+}
+
+void test_fmaximum_float(void) {
+  printf("Testing __atomic_fmaximum_fetch (float)...\n");
+
+  // Test 1: Normal values
+  {
+    float x = 5.0f;
+    float result = __atomic_fmaximum_fetch(&x, 3.0f, MO);
+    assert(result == 5.0f && "fmaximum(5.0, 3.0) should be 5.0");
+  }
+
+  // Test 2: NaN propagation
+  {
+    float x = 1.0f;
+    float result = __atomic_fmaximum_fetch(&x, NAN, MO);
+    assert(is_nan_f(result) && "fmaximum(1.0, NaN) should be NaN");
+  }
+
+  // Test 3: Zero handling - fmaximum(+0, -0) should be +0
+  {
+    float x = 0.0f;
+    float result = __atomic_fmaximum_fetch(&x, neg_zero_f(), MO);
+    assert(same_bits_f(result, 0.0f) && "fmaximum(+0, -0) should be +0");
+  }
+
+  {
+    float x = neg_zero_f();
+    float result = __atomic_fmaximum_fetch(&x, 0.0f, MO);
+    assert(same_bits_f(result, 0.0f) && "fmaximum(-0, +0) should be +0");
+  }
+
+  // Test 4: Infinity
+  {
+    float x = INFINITY;
+    float result = __atomic_fmaximum_fetch(&x, 1.0f, MO);
+    assert(result == INFINITY && "fmaximum(+inf, 1.0) should be +inf");
+  }
+
+  printf("  PASSED\n");
+}
+
+//===----------------------------------------------------------------------===//
+// Test fminimumnum_fetch (propagates numbers, treats zeros as equivalent)
+//===----------------------------------------------------------------------===//
+
+void test_fminimum_num_float(void) {
+  printf("Testing __atomic_fminimum_num_fetch (float)...\n");
+
+  // Test 1: Normal values
+  {
+    float x = 5.0f;
+    float result = __atomic_fminimum_num_fetch(&x, 3.0f, MO);
+    assert(result == 3.0f && "fminimumnum(5.0, 3.0) should be 3.0");
+  }
+
+  // Test 2: NaN handling - CRITICAL: fminimumnum propagates NUMBER over NaN
+  {
+    float x = 1.0f;
+    float result = __atomic_fminimum_num_fetch(&x, NAN, MO);
+    assert(result == 1.0f &&
+           "fminimumnum(1.0, NaN) should be 1.0 (number over NaN)");
+    assert(x == 1.0f && "stored value should be 1.0");
+  }
+
+  {
+    float x = NAN;
+    float result = __atomic_fminimum_num_fetch(&x, 2.0f, MO);
+    assert(result == 2.0f &&
+           "fminimumnum(NaN, 2.0) should be 2.0 (number over NaN)");
+    assert(x == 2.0f && "stored value should be 2.0");
+  }
+
+  {
+    float x = NAN;
+    float result = __atomic_fminimum_num_fetch(&x, NAN, MO);
+    assert(is_nan_f(result) && "fminimumnum(NaN, NaN) should be NaN");
+  }
+
+  // Test 3: Zero handling - fminimumnum treats +0 and -0 as equivalent
+  // The result can be either, but should pick the minimum value
+  {
+    float x = 0.0f;
+    float result = __atomic_fminimum_num_fetch(&x, neg_zero_f(), MO);
+    // Result should be a zero (either +0 or -0 is acceptable per IEEE 754)
+    assert(result == 0.0f && "fminimumnum(+0, -0) should be zero");
+  }
+
+  // Test 4: Infinity
+  {
+    float x = INFINITY;
+    float result = __atomic_fminimum_num_fetch(&x, 1.0f, MO);
+    assert(result == 1.0f && "fminimumnum(+inf, 1.0) should be 1.0");
+  }
+
+  // Test 5: fetch variant
+  {
+    float x = NAN;
+    float old = __atomic_fetch_fminimum_num(&x, 3.0f, MO);
+    assert(is_nan_f(old) && "fetch_fminimum_num should return old value (NaN)");
+    assert(x == 3.0f && "stored value should be 3.0");
+  }
+
+  printf("  PASSED\n");
+}
+
+void test_fmaximum_num_float(void) {
+  printf("Testing __atomic_fmaximum_num_fetch (float)...\n");
+
+  // Test 1: Normal values
+  {
+    float x = 5.0f;
+    float result = __atomic_fmaximum_num_fetch(&x, 3.0f, MO);
+    assert(result == 5.0f && "fmaximumnum(5.0, 3.0) should be 5.0");
+  }
+
+  // Test 2: NaN handling - propagates number over NaN
+  {
+    float x = 1.0f;
+    float result = __atomic_fmaximum_num_fetch(&x, NAN, MO);
+    assert(result == 1.0f && "fmaximumnum(1.0, NaN) should be 1.0");
+  }
+
+  {
+    float x = NAN;
+    float result = __atomic_fmaximum_num_fetch(&x, 2.0f, MO);
+    assert(result == 2.0f && "fmaximumnum(NaN, 2.0) should be 2.0");
+  }
+
+  // Test 3: Zero handling - treats +0 and -0 as equivalent
+  {
+    float x = 0.0f;
+    float result = __atomic_fmaximum_num_fetch(&x, neg_zero_f(), MO);
+    assert(result == 0.0f && "fmaximumnum(+0, -0) should be zero");
+  }
+
+  printf("  PASSED\n");
+}
+
+//===----------------------------------------------------------------------===//
+// Double precision tests
+//===----------------------------------------------------------------------===//
+
+void test_fminimum_double(void) {
+  printf("Testing __atomic_fminimum_fetch (double)...\n");
+
+  // Test NaN propagation
+  {
+    double x = 1.0;
+    double result = __atomic_fminimum_fetch(&x, NAN, MO);
+    assert(is_nan_d(result) && "fminimum(1.0, NaN) should be NaN (double)");
+  }
+
+  // Test zero distinction
+  {
+    double x = 0.0;
+    double result = __atomic_fminimum_fetch(&x, neg_zero_d(), MO);
+    assert(same_bits_d(result, neg_zero_d()) &&
+           "fminimum(+0, -0) should be -0 (double)");
+  }
+
+  // Test normal values
+  {
+    double x = 3.14;
+    double result = __atomic_fminimum_fetch(&x, 2.71, MO);
+    assert(result == 2.71 && "fminimum(3.14, 2.71) should be 2.71");
+  }
+
+  printf("  PASSED\n");
+}
+
+void test_fmaximum_double(void) {
+  printf("Testing __atomic_fmaximum_fetch (double)...\n");
+
+  // Test NaN propagation
+  {
+    double x = 1.0;
+    double result = __atomic_fmaximum_fetch(&x, NAN, MO);
+    assert(is_nan_d(result) && "fmaximum(1.0, NaN) should be NaN (double)");
+  }
+
+  // Test zero distinction - fmaximum(+0, -0) = +0
+  {
+    double x = 0.0;
+    double result = __atomic_fmaximum_fetch(&x, neg_zero_d(), MO);
+    assert(same_bits_d(result, 0.0) &&
+           "fmaximum(+0, -0) should be +0 (double)");
+  }
+
+  printf("  PASSED\n");
+}
+
+void test_fminimum_num_double(void) {
+  printf("Testing __atomic_fminimum_num_fetch (double)...\n");
+
+  // Test number over NaN
+  {
+    double x = NAN;
+    double result = __atomic_fminimum_num_fetch(&x, 2.5, MO);
+    assert(result == 2.5 && "fminimumnum(NaN, 2.5) should be 2.5 (double)");
+  }
+
+  // Test normal values
+  {
+    double x = 10.5;
+    double result = __atomic_fminimum_num_fetch(&x, 8.3, MO);
+    assert(result == 8.3 && "fminimumnum(10.5, 8.3) should be 8.3");
+  }
+
+  printf("  PASSED\n");
+}
+
+void test_fmaximum_num_double(void) {
+  printf("Testing __atomic_fmaximum_num_fetch (double)...\n");
+
+  // Test number over NaN
+  {
+    double x = NAN;
+    double result = __atomic_fmaximum_num_fetch(&x, 2.5, MO);
+    assert(result == 2.5 && "fmaximumnum(NaN, 2.5) should be 2.5 (double)");
+  }
+
+  printf("  PASSED\n");
+}
+
+//===----------------------------------------------------------------------===//
+// Main test runner
+//===----------------------------------------------------------------------===//
+
+int main(void) {
+  printf("\n");
+  printf("=============================================================\n");
+  printf("Atomic Floating-Point Min/Max Tests\n");
+  printf("=============================================================\n");
+  printf("\n");
+
+  printf("--- fminimum/fmaximum (propagate NaN, distinguish zeros) ---\n");
+  test_fminimum_float();
+  test_fmaximum_float();
+  test_fminimum_double();
+  test_fmaximum_double();
+
+  printf("\n--- fminimumnum/fmaximumnum (prefer numbers, treat zeros equal) "
+         "---\n");
+  test_fminimum_num_float();
+  test_fmaximum_num_float();
+  test_fminimum_num_double();
+  test_fmaximum_num_double();
+
+  printf("\n");
+  printf("=============================================================\n");
+  printf("All tests PASSED!\n");
+  printf("=============================================================\n");
+  printf("\n");
+
+  return 0;
+}

>From ec49bd7f6c0c48085372a2a455c6149287b365e5 Mon Sep 17 00:00:00 2001
From: gonzalobg <65027571+gonzalobg at users.noreply.github.com>
Date: Wed, 25 Mar 2026 15:18:54 +0000
Subject: [PATCH 2/6] revert min/max pointer change

---
 clang/lib/Sema/SemaChecking.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index e71ad1f21bcbb..0ab6ddfd9e072 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -4734,7 +4734,7 @@ ExprResult Sema::BuildAtomicExpr(SourceRange CallRange, SourceRange ExprRange,
   case AtomicExpr::AO__opencl_atomic_fetch_min:
   case AtomicExpr::AO__hip_atomic_fetch_max:
   case AtomicExpr::AO__hip_atomic_fetch_min:
-    ArithAllows = AOEVT_Pointer | AOEVT_FP;
+    ArithAllows = AOEVT_FP;
     Form = Arithmetic;
     break;
   case AtomicExpr::AO__c11_atomic_fetch_and:

>From 7ca3cec5f38b5a574d4d8313b0f953744b87519b Mon Sep 17 00:00:00 2001
From: Gonzalo Brito Gadeschi <gonzalob at nvidia.com>
Date: Fri, 1 May 2026 11:00:48 -0700
Subject: [PATCH 3/6] remove the _fetch variants

---
 clang/include/clang/Basic/Builtins.td         | 48 -----------------
 clang/lib/AST/Expr.cpp                        |  8 ---
 clang/lib/CodeGen/CGAtomic.cpp                | 52 -------------------
 clang/lib/Sema/SemaChecking.cpp               | 15 ++----
 .../AArch64/atomic-ops-float-check-minmax.c   | 46 ----------------
 clang/test/Sema/atomic-ops.c                  | 37 ++-----------
 6 files changed, 9 insertions(+), 197 deletions(-)

diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index ea4b0cee32213..e37c586cc19a5 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -2139,30 +2139,6 @@ def AtomicMinFetch : AtomicBuiltin {
   let Prototype = "void(...)";
 }
 
-def AtomicFMinimumFetch : AtomicBuiltin {
-  let Spellings = ["__atomic_fminimum_fetch"];
-  let Attributes = [CustomTypeChecking];
-  let Prototype = "void(...)";
-}
-
-def AtomicFMaximumFetch : AtomicBuiltin {
-  let Spellings = ["__atomic_fmaximum_fetch"];
-  let Attributes = [CustomTypeChecking];
-  let Prototype = "void(...)";
-}
-
-def AtomicFMinimumNumFetch : AtomicBuiltin {
-  let Spellings = ["__atomic_fminimum_num_fetch"];
-  let Attributes = [CustomTypeChecking];
-  let Prototype = "void(...)";
-}
-
-def AtomicFMaximumNumFetch : AtomicBuiltin {
-  let Spellings = ["__atomic_fmaximum_num_fetch"];
-  let Attributes = [CustomTypeChecking];
-  let Prototype = "void(...)";
-}
-
 def AtomicNandFetch : AtomicBuiltin {
   let Spellings = ["__atomic_nand_fetch"];
   let Attributes = [CustomTypeChecking];
@@ -2380,30 +2356,6 @@ def ScopedAtomicMaxFetch : AtomicBuiltin {
   let Prototype = "void(...)";
 }
 
-def ScopedAtomicFMinimumFetch : AtomicBuiltin {
-  let Spellings = ["__scoped_atomic_fminimum_fetch"];
-  let Attributes = [CustomTypeChecking];
-  let Prototype = "void(...)";
-}
-
-def ScopedAtomicFMaximumFetch : AtomicBuiltin {
-  let Spellings = ["__scoped_atomic_fmaximum_fetch"];
-  let Attributes = [CustomTypeChecking];
-  let Prototype = "void(...)";
-}
-
-def ScopedAtomicFMinimumNumFetch : AtomicBuiltin {
-  let Spellings = ["__scoped_atomic_fminimum_num_fetch"];
-  let Attributes = [CustomTypeChecking];
-  let Prototype = "void(...)";
-}
-
-def ScopedAtomicFMaximumNumFetch : AtomicBuiltin {
-  let Spellings = ["__scoped_atomic_fmaximum_num_fetch"];
-  let Attributes = [CustomTypeChecking];
-  let Prototype = "void(...)";
-}
-
 def ScopedAtomicUInc : AtomicBuiltin {
   let Spellings = ["__scoped_atomic_fetch_uinc"];
   let Attributes = [CustomTypeChecking];
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index f05d4c6a495ac..46574e24d2e12 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -5288,10 +5288,6 @@ unsigned AtomicExpr::getNumSubExprs(AtomicOp Op) {
   case AO__atomic_nand_fetch:
   case AO__atomic_min_fetch:
   case AO__atomic_max_fetch:
-  case AO__atomic_fminimum_fetch:
-  case AO__atomic_fmaximum_fetch:
-  case AO__atomic_fminimum_num_fetch:
-  case AO__atomic_fmaximum_num_fetch:
   case AO__atomic_fetch_min:
   case AO__atomic_fetch_max:
   case AO__atomic_fetch_fminimum:
@@ -5319,10 +5315,6 @@ unsigned AtomicExpr::getNumSubExprs(AtomicOp Op) {
   case AO__scoped_atomic_nand_fetch:
   case AO__scoped_atomic_min_fetch:
   case AO__scoped_atomic_max_fetch:
-  case AO__scoped_atomic_fminimum_fetch:
-  case AO__scoped_atomic_fmaximum_fetch:
-  case AO__scoped_atomic_fminimum_num_fetch:
-  case AO__scoped_atomic_fmaximum_num_fetch:
   case AO__scoped_atomic_fetch_min:
   case AO__scoped_atomic_fetch_max:
   case AO__scoped_atomic_fetch_fminimum:
diff --git a/clang/lib/CodeGen/CGAtomic.cpp b/clang/lib/CodeGen/CGAtomic.cpp
index 4f02b27eb4f31..3e521ef8d7643 100644
--- a/clang/lib/CodeGen/CGAtomic.cpp
+++ b/clang/lib/CodeGen/CGAtomic.cpp
@@ -537,18 +537,6 @@ static llvm::Value *EmitPostAtomicMinMax(CGBuilderTy &Builder,
     else if (Op == AtomicExpr::AO__atomic_min_fetch ||
              Op == AtomicExpr::AO__scoped_atomic_min_fetch)
       IID = llvm::Intrinsic::minnum;
-    else if (Op == AtomicExpr::AO__atomic_fmaximum_fetch ||
-             Op == AtomicExpr::AO__scoped_atomic_fmaximum_fetch)
-      IID = llvm::Intrinsic::maximum;
-    else if (Op == AtomicExpr::AO__atomic_fminimum_fetch ||
-             Op == AtomicExpr::AO__scoped_atomic_fminimum_fetch)
-      IID = llvm::Intrinsic::minimum;
-    else if (Op == AtomicExpr::AO__atomic_fmaximum_num_fetch ||
-             Op == AtomicExpr::AO__scoped_atomic_fmaximum_num_fetch)
-      IID = llvm::Intrinsic::maximumnum;
-    else if (Op == AtomicExpr::AO__atomic_fminimum_num_fetch ||
-             Op == AtomicExpr::AO__scoped_atomic_fminimum_num_fetch)
-      IID = llvm::Intrinsic::minimumnum;
     else
       llvm_unreachable("Unexpected atomic FP min/max operation");
 
@@ -562,18 +550,10 @@ static llvm::Value *EmitPostAtomicMinMax(CGBuilderTy &Builder,
     llvm_unreachable("Unexpected min/max operation");
   case AtomicExpr::AO__atomic_max_fetch:
   case AtomicExpr::AO__scoped_atomic_max_fetch:
-  case AtomicExpr::AO__atomic_fmaximum_fetch:
-  case AtomicExpr::AO__scoped_atomic_fmaximum_fetch:
-  case AtomicExpr::AO__atomic_fmaximum_num_fetch:
-  case AtomicExpr::AO__scoped_atomic_fmaximum_num_fetch:
     Pred = IsSigned ? llvm::CmpInst::ICMP_SGT : llvm::CmpInst::ICMP_UGT;
     break;
   case AtomicExpr::AO__atomic_min_fetch:
   case AtomicExpr::AO__scoped_atomic_min_fetch:
-  case AtomicExpr::AO__atomic_fminimum_fetch:
-  case AtomicExpr::AO__scoped_atomic_fminimum_fetch:
-  case AtomicExpr::AO__atomic_fminimum_num_fetch:
-  case AtomicExpr::AO__scoped_atomic_fminimum_num_fetch:
     Pred = IsSigned ? llvm::CmpInst::ICMP_SLT : llvm::CmpInst::ICMP_ULT;
     break;
   }
@@ -730,10 +710,6 @@ static void EmitAtomicOp(CodeGenFunction &CGF, AtomicExpr *E, Address Dest,
                     : llvm::AtomicRMWInst::UMin);
     break;
 
-  case AtomicExpr::AO__atomic_fminimum_fetch:
-  case AtomicExpr::AO__scoped_atomic_fminimum_fetch:
-    PostOpMinMax = true;
-    [[fallthrough]];
   case AtomicExpr::AO__atomic_fetch_fminimum:
   case AtomicExpr::AO__scoped_atomic_fetch_fminimum:
     assert(E->getValueType()->isFloatingType() &&
@@ -741,10 +717,6 @@ static void EmitAtomicOp(CodeGenFunction &CGF, AtomicExpr *E, Address Dest,
     Op = llvm::AtomicRMWInst::FMinimum;
     break;
 
-  case AtomicExpr::AO__atomic_fminimum_num_fetch:
-  case AtomicExpr::AO__scoped_atomic_fminimum_num_fetch:
-    PostOpMinMax = true;
-    [[fallthrough]];
   case AtomicExpr::AO__atomic_fetch_fminimum_num:
   case AtomicExpr::AO__scoped_atomic_fetch_fminimum_num:
     assert(E->getValueType()->isFloatingType() &&
@@ -768,10 +740,6 @@ static void EmitAtomicOp(CodeGenFunction &CGF, AtomicExpr *E, Address Dest,
                     : llvm::AtomicRMWInst::UMax);
     break;
 
-  case AtomicExpr::AO__atomic_fmaximum_fetch:
-  case AtomicExpr::AO__scoped_atomic_fmaximum_fetch:
-    PostOpMinMax = true;
-    [[fallthrough]];
   case AtomicExpr::AO__atomic_fetch_fmaximum:
   case AtomicExpr::AO__scoped_atomic_fetch_fmaximum:
     assert(E->getValueType()->isFloatingType() &&
@@ -779,10 +747,6 @@ static void EmitAtomicOp(CodeGenFunction &CGF, AtomicExpr *E, Address Dest,
     Op = llvm::AtomicRMWInst::FMaximum;
     break;
 
-  case AtomicExpr::AO__atomic_fmaximum_num_fetch:
-  case AtomicExpr::AO__scoped_atomic_fmaximum_num_fetch:
-    PostOpMinMax = true;
-    [[fallthrough]];
   case AtomicExpr::AO__atomic_fetch_fmaximum_num:
   case AtomicExpr::AO__scoped_atomic_fetch_fmaximum_num:
     assert(E->getValueType()->isFloatingType() &&
@@ -1124,10 +1088,6 @@ RValue CodeGenFunction::EmitAtomicExpr(AtomicExpr *E) {
   case AtomicExpr::AO__scoped_atomic_max_fetch:
   case AtomicExpr::AO__scoped_atomic_min_fetch:
   case AtomicExpr::AO__scoped_atomic_sub_fetch:
-  case AtomicExpr::AO__scoped_atomic_fminimum_fetch:
-  case AtomicExpr::AO__scoped_atomic_fmaximum_fetch:
-  case AtomicExpr::AO__scoped_atomic_fminimum_num_fetch:
-  case AtomicExpr::AO__scoped_atomic_fmaximum_num_fetch:
     [[fallthrough]];
 
   case AtomicExpr::AO__atomic_fetch_and:
@@ -1174,10 +1134,6 @@ RValue CodeGenFunction::EmitAtomicExpr(AtomicExpr *E) {
   case AtomicExpr::AO__atomic_fetch_fmaximum:
   case AtomicExpr::AO__atomic_fetch_fminimum_num:
   case AtomicExpr::AO__atomic_fetch_fmaximum_num:
-  case AtomicExpr::AO__atomic_fminimum_fetch:
-  case AtomicExpr::AO__atomic_fmaximum_fetch:
-  case AtomicExpr::AO__atomic_fminimum_num_fetch:
-  case AtomicExpr::AO__atomic_fmaximum_num_fetch:
     Val1 = EmitValToTemp(*this, E->getVal1());
     break;
   }
@@ -1385,14 +1341,6 @@ RValue CodeGenFunction::EmitAtomicExpr(AtomicExpr *E) {
     case AtomicExpr::AO__scoped_atomic_fetch_fmaximum:
     case AtomicExpr::AO__scoped_atomic_fetch_fminimum_num:
     case AtomicExpr::AO__scoped_atomic_fetch_fmaximum_num:
-    case AtomicExpr::AO__atomic_fminimum_fetch:
-    case AtomicExpr::AO__atomic_fmaximum_fetch:
-    case AtomicExpr::AO__atomic_fminimum_num_fetch:
-    case AtomicExpr::AO__atomic_fmaximum_num_fetch:
-    case AtomicExpr::AO__scoped_atomic_fminimum_fetch:
-    case AtomicExpr::AO__scoped_atomic_fmaximum_fetch:
-    case AtomicExpr::AO__scoped_atomic_fminimum_num_fetch:
-    case AtomicExpr::AO__scoped_atomic_fmaximum_num_fetch:
     case AtomicExpr::AO__scoped_atomic_fetch_uinc:
     case AtomicExpr::AO__scoped_atomic_fetch_udec:
     case AtomicExpr::AO__atomic_test_and_set:
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 0ab6ddfd9e072..cf5c02ad7d861 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -4650,6 +4650,7 @@ ExprResult Sema::BuildAtomicExpr(SourceRange CallRange, SourceRange ExprRange,
     AOEVT_None = 0,
     AOEVT_Pointer = 1,
     AOEVT_FP = 2,
+    AOEVT_Int = 4,
   };
   unsigned ArithAllows = AOEVT_None;
 
@@ -4703,20 +4704,12 @@ ExprResult Sema::BuildAtomicExpr(SourceRange CallRange, SourceRange ExprRange,
     break;
   case AtomicExpr::AO__atomic_fetch_fminimum:
   case AtomicExpr::AO__atomic_fetch_fmaximum:
-  case AtomicExpr::AO__atomic_fminimum_fetch:
-  case AtomicExpr::AO__atomic_fmaximum_fetch:
   case AtomicExpr::AO__atomic_fetch_fminimum_num:
   case AtomicExpr::AO__atomic_fetch_fmaximum_num:
-  case AtomicExpr::AO__atomic_fminimum_num_fetch:
-  case AtomicExpr::AO__atomic_fmaximum_num_fetch:
   case AtomicExpr::AO__scoped_atomic_fetch_fminimum:
   case AtomicExpr::AO__scoped_atomic_fetch_fmaximum:
-  case AtomicExpr::AO__scoped_atomic_fminimum_fetch:
-  case AtomicExpr::AO__scoped_atomic_fmaximum_fetch:
   case AtomicExpr::AO__scoped_atomic_fetch_fminimum_num:
   case AtomicExpr::AO__scoped_atomic_fetch_fmaximum_num:
-  case AtomicExpr::AO__scoped_atomic_fminimum_num_fetch:
-  case AtomicExpr::AO__scoped_atomic_fmaximum_num_fetch:
     ArithAllows = AOEVT_FP;
     Form = Arithmetic;
     break;
@@ -4734,7 +4727,7 @@ ExprResult Sema::BuildAtomicExpr(SourceRange CallRange, SourceRange ExprRange,
   case AtomicExpr::AO__opencl_atomic_fetch_min:
   case AtomicExpr::AO__hip_atomic_fetch_max:
   case AtomicExpr::AO__hip_atomic_fetch_min:
-    ArithAllows = AOEVT_FP;
+    ArithAllows = AOEVT_Int | AOEVT_FP;
     Form = Arithmetic;
     break;
   case AtomicExpr::AO__c11_atomic_fetch_and:
@@ -4913,8 +4906,8 @@ ExprResult Sema::BuildAtomicExpr(SourceRange CallRange, SourceRange ExprRange,
               &llvm::APFloat::x87DoubleExtended();
       if (ValType->isIntegerType())
         // Special case: f-prefixed operations (AOEVT_FP exactly) reject
-        // integers
-        return AllowedType != AOEVT_FP;
+        // integers. Explicit AOEVT_Int or other combinations allow integers.
+        return (AllowedType & AOEVT_Int) || AllowedType != AOEVT_FP;
       if (ValType->isPointerType())
         return AllowedType & AOEVT_Pointer;
       if (!(ValType->isFloatingType() && (AllowedType & AOEVT_FP)))
diff --git a/clang/test/CodeGen/AArch64/atomic-ops-float-check-minmax.c b/clang/test/CodeGen/AArch64/atomic-ops-float-check-minmax.c
index d525a6d86f1f1..46d9e387c0326 100644
--- a/clang/test/CodeGen/AArch64/atomic-ops-float-check-minmax.c
+++ b/clang/test/CodeGen/AArch64/atomic-ops-float-check-minmax.c
@@ -116,52 +116,6 @@ void test_minmax_postop(float *f32, _Float16 *f16, __bf16 *bf16, double *f64) {
   *bf16 = __atomic_min_fetch(bf16, 42.1, memory_order_release);
 }
 
-// CHECK-LABEL: define dso_local void @test_fminimum_fmaximum_postop(
-// CHECK-SAME: ptr noundef [[F32:%.*]], ptr noundef [[F16:%.*]], ptr noundef [[BF16:%.*]], ptr noundef [[F64:%.*]]) #[[ATTR0:[0-9]+]] {
-// CHECK-NEXT:  [[ENTRY:.*:]]
-// CHECK:    [[TMP0:%.*]] = load ptr, ptr [[F64_ADDR]], align 8
-// CHECK:    store double 4.210000e+01, ptr [[DOTATOMICTMP]], align 8
-// CHECK:    [[TMP1:%.*]] = load double, ptr [[DOTATOMICTMP]], align 8
-// CHECK:    [[TMP2:%.*]] = atomicrmw fmaximum ptr [[TMP0]], double [[TMP1]] release, align 8
-// CHECK:    [[NEWVAL:%.*]] = call double @llvm.maximum.f64(double [[TMP2]], double [[TMP1]])
-// CHECK:    store double [[NEWVAL]], ptr [[ATOMIC_TEMP]], align 8
-// CHECK:    [[TMP3:%.*]] = load double, ptr [[ATOMIC_TEMP]], align 8
-// CHECK:    [[TMP4:%.*]] = load ptr, ptr [[F64_ADDR]], align 8
-// CHECK:    store double [[TMP3]], ptr [[TMP4]], align 8
-void test_fminimum_fmaximum_postop(float *f32, _Float16 *f16, __bf16 *bf16, double *f64) {
-  *f64  = __atomic_fmaximum_fetch(f64,  42.1, memory_order_release);
-  *f32  = __atomic_fmaximum_fetch(f32,  42.1, memory_order_release);
-  *f16  = __atomic_fmaximum_fetch(f16,  42.1, memory_order_release);
-  *bf16 = __atomic_fmaximum_fetch(bf16, 42.1, memory_order_release);
-  *f64  = __atomic_fminimum_fetch(f64,  42.1, memory_order_release);
-  *f32  = __atomic_fminimum_fetch(f32,  42.1, memory_order_release);
-  *f16  = __atomic_fminimum_fetch(f16,  42.1, memory_order_release);
-  *bf16 = __atomic_fminimum_fetch(bf16, 42.1, memory_order_release);
-}
-
-// CHECK-LABEL: define dso_local void @test_fminimumnum_fmaximumnum_postop(
-// CHECK-SAME: ptr noundef [[F32:%.*]], ptr noundef [[F16:%.*]], ptr noundef [[BF16:%.*]], ptr noundef [[F64:%.*]]) #[[ATTR0:[0-9]+]] {
-// CHECK-NEXT:  [[ENTRY:.*:]]
-// CHECK:    [[TMP0:%.*]] = load ptr, ptr [[F64_ADDR]], align 8
-// CHECK:    store double 4.210000e+01, ptr [[DOTATOMICTMP]], align 8
-// CHECK:    [[TMP1:%.*]] = load double, ptr [[DOTATOMICTMP]], align 8
-// CHECK:    [[TMP2:%.*]] = atomicrmw fmaximumnum ptr [[TMP0]], double [[TMP1]] release, align 8
-// CHECK:    [[NEWVAL:%.*]] = call double @llvm.maximumnum.f64(double [[TMP2]], double [[TMP1]])
-// CHECK:    store double [[NEWVAL]], ptr [[ATOMIC_TEMP]], align 8
-// CHECK:    [[TMP3:%.*]] = load double, ptr [[ATOMIC_TEMP]], align 8
-// CHECK:    [[TMP4:%.*]] = load ptr, ptr [[F64_ADDR]], align 8
-// CHECK:    store double [[TMP3]], ptr [[TMP4]], align 8
-void test_fminimumnum_fmaximumnum_postop(float *f32, _Float16 *f16, __bf16 *bf16, double *f64) {
-  *f64  = __atomic_fmaximum_num_fetch(f64,  42.1, memory_order_release);
-  *f32  = __atomic_fmaximum_num_fetch(f32,  42.1, memory_order_release);
-  *f16  = __atomic_fmaximum_num_fetch(f16,  42.1, memory_order_release);
-  *bf16 = __atomic_fmaximum_num_fetch(bf16, 42.1, memory_order_release);
-  *f64  = __atomic_fminimum_num_fetch(f64,  42.1, memory_order_release);
-  *f32  = __atomic_fminimum_num_fetch(f32,  42.1, memory_order_release);
-  *f16  = __atomic_fminimum_num_fetch(f16,  42.1, memory_order_release);
-  *bf16 = __atomic_fminimum_num_fetch(bf16, 42.1, memory_order_release);
-}
-
 // CHECK-LABEL: define dso_local void @test_fetch_variants(
 // CHECK-SAME: ptr noundef [[F64:%.*]]) #[[ATTR0:[0-9]+]] {
 // CHECK:    [[TMP0:%.*]] = atomicrmw fminimum ptr {{%.*}}, double {{%.*}} release, align 8
diff --git a/clang/test/Sema/atomic-ops.c b/clang/test/Sema/atomic-ops.c
index d930af0d0e139..a3bc8d2ec75e7 100644
--- a/clang/test/Sema/atomic-ops.c
+++ b/clang/test/Sema/atomic-ops.c
@@ -231,15 +231,15 @@ void f(_Atomic(int) *i, const _Atomic(int) *ci,
   __c11_atomic_fetch_add(d, 1.0, memory_order_seq_cst);
   __c11_atomic_fetch_add(ld, 1.0, memory_order_seq_cst); // fp80-error {{must be a pointer to atomic integer, pointer or supported floating point type}}
   __c11_atomic_fetch_min(i, 1, memory_order_seq_cst);
-  __c11_atomic_fetch_min(p, 1, memory_order_seq_cst);
+  __c11_atomic_fetch_min(p, 1, memory_order_seq_cst); // expected-error {{must be a pointer to atomic integer or supported floating point type}}
   __c11_atomic_fetch_min(f, 1.0f, memory_order_seq_cst);
   __c11_atomic_fetch_min(d, 1.0, memory_order_seq_cst);
-  __c11_atomic_fetch_min(ld, 1.0, memory_order_seq_cst); // fp80-error {{must be a pointer to atomic integer, pointer or supported floating point type}}
+  __c11_atomic_fetch_min(ld, 1.0, memory_order_seq_cst); // fp80-error {{must be a pointer to atomic integer or supported floating point type}}
   __c11_atomic_fetch_max(i, 1, memory_order_seq_cst);
-  __c11_atomic_fetch_max(p, 1, memory_order_seq_cst);
+  __c11_atomic_fetch_max(p, 1, memory_order_seq_cst); // expected-error {{must be a pointer to atomic integer or supported floating point type}}
   __c11_atomic_fetch_max(f, 1.0f, memory_order_seq_cst);
   __c11_atomic_fetch_max(d, 1.0, memory_order_seq_cst);
-  __c11_atomic_fetch_max(ld, 1.0, memory_order_seq_cst); // fp80-error {{must be a pointer to atomic integer, pointer or supported floating point type}}
+  __c11_atomic_fetch_max(ld, 1.0, memory_order_seq_cst); // fp80-error {{must be a pointer to atomic integer or supported floating point type}}
 
   __atomic_fetch_add(i, 3, memory_order_seq_cst); // expected-error {{pointer to integer, pointer or supported floating point type}}
   __atomic_fetch_sub(I, 3, memory_order_seq_cst);
@@ -250,7 +250,7 @@ void f(_Atomic(int) *i, const _Atomic(int) *ci,
   __atomic_fetch_min(D, 3, memory_order_seq_cst);
   __atomic_fetch_max(F, 3, memory_order_seq_cst);
   __atomic_fetch_max(D, 3, memory_order_seq_cst);
-  __atomic_fetch_max(P, 3, memory_order_seq_cst);
+  __atomic_fetch_max(P, 3, memory_order_seq_cst); // expected-error {{must be a pointer to integer or supported floating point type}}
   __atomic_fetch_max(p, 3);                       // expected-error {{too few arguments to function call, expected 3, have 2}}
   __atomic_fetch_fminimum(F, 3, memory_order_seq_cst);
   __atomic_fetch_fminimum(D, 3, memory_order_seq_cst);
@@ -704,33 +704,6 @@ void memory_checks(_Atomic(int) *Ap, int *p, int val, float *fp, float fval) {
   (void)__atomic_min_fetch(p, val, memory_order_acq_rel);
   (void)__atomic_min_fetch(p, val, memory_order_seq_cst);
 
-  (void)__atomic_fmaximum_fetch(fp, fval, memory_order_relaxed);
-  (void)__atomic_fmaximum_fetch(fp, fval, memory_order_acquire);
-  (void)__atomic_fmaximum_fetch(fp, fval, memory_order_consume);
-  (void)__atomic_fmaximum_fetch(fp, fval, memory_order_release);
-  (void)__atomic_fmaximum_fetch(fp, fval, memory_order_acq_rel);
-  (void)__atomic_fmaximum_fetch(fp, fval, memory_order_seq_cst);
-
-  (void)__atomic_fminimum_fetch(fp, fval, memory_order_relaxed);
-  (void)__atomic_fminimum_fetch(fp, fval, memory_order_acquire);
-  (void)__atomic_fminimum_fetch(fp, fval, memory_order_consume);
-  (void)__atomic_fminimum_fetch(fp, fval, memory_order_release);
-  (void)__atomic_fminimum_fetch(fp, fval, memory_order_acq_rel);
-  (void)__atomic_fminimum_fetch(fp, fval, memory_order_seq_cst);
-
-  (void)__atomic_fmaximum_num_fetch(fp, fval, memory_order_relaxed);
-  (void)__atomic_fmaximum_num_fetch(fp, fval, memory_order_acquire);
-  (void)__atomic_fmaximum_num_fetch(fp, fval, memory_order_consume);
-  (void)__atomic_fmaximum_num_fetch(fp, fval, memory_order_release);
-  (void)__atomic_fmaximum_num_fetch(fp, fval, memory_order_acq_rel);
-  (void)__atomic_fmaximum_num_fetch(fp, fval, memory_order_seq_cst);
-
-  (void)__atomic_fminimum_num_fetch(fp, fval, memory_order_relaxed);
-  (void)__atomic_fminimum_num_fetch(fp, fval, memory_order_acquire);
-  (void)__atomic_fminimum_num_fetch(fp, fval, memory_order_consume);
-  (void)__atomic_fminimum_num_fetch(fp, fval, memory_order_release);
-  (void)__atomic_fminimum_num_fetch(fp, fval, memory_order_acq_rel);
-  (void)__atomic_fminimum_num_fetch(fp, fval, memory_order_seq_cst);
 
   (void)__atomic_exchange_n(p, val, memory_order_relaxed);
   (void)__atomic_exchange_n(p, val, memory_order_acquire);

>From 13a2cc68809320c819010001e77b9302e4e77021 Mon Sep 17 00:00:00 2001
From: Gonzalo Brito Gadeschi <gonzalob at nvidia.com>
Date: Fri, 1 May 2026 11:14:08 -0700
Subject: [PATCH 4/6] move test to cross-project-tests

---
 .../builtins/Unit/atomic_fp_minmax_test.c     | 397 -----------------
 .../atomics/atomic_fp_minmax_test.c           | 398 ++++++++++++++++++
 cross-project-tests/atomics/lit.local.cfg     |   2 +
 3 files changed, 400 insertions(+), 397 deletions(-)
 delete mode 100644 compiler-rt/test/builtins/Unit/atomic_fp_minmax_test.c
 create mode 100644 cross-project-tests/atomics/atomic_fp_minmax_test.c
 create mode 100644 cross-project-tests/atomics/lit.local.cfg

diff --git a/compiler-rt/test/builtins/Unit/atomic_fp_minmax_test.c b/compiler-rt/test/builtins/Unit/atomic_fp_minmax_test.c
deleted file mode 100644
index b526e216be614..0000000000000
--- a/compiler-rt/test/builtins/Unit/atomic_fp_minmax_test.c
+++ /dev/null
@@ -1,397 +0,0 @@
-// RUN: %clang_builtins %s %librt -o %t && %run %t
-// REQUIRES: native-run
-//===-- atomic_fp_minmax_test.c - Test FP atomic min/max operations -------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-//
-// This file tests the floating-point atomic min/max builtins, focusing on
-// IEEE 754 corner cases: NaN, +/-infinity, +/-zero.
-//
-// There are three families of operations with different semantics:
-// 1. fminimum/fmaximum: IEEE 754-2019 minimum/maximum
-//    - Propagates NaN (any NaN input produces NaN output)
-//    - Distinguishes -0 and +0 (minimum(-0, +0) = -0, maximum(-0, +0) = +0)
-//
-// 2. fminimumnum/fmaximumnum: IEEE 754-2019 minimumNumber/maximumNumber
-//    - Propagates numbers over NaN (minimumNumber(2.0, NaN) = 2.0)
-//    - Treats -0 and +0 as equivalent
-//
-// 3. minnum/maxnum (existing __atomic_min_fetch for floats): IEEE 754-2008
-//    - Propagates numbers over NaN (minnum(2.0, NaN) = 2.0)
-//    - Treats -0 and +0 as equivalent
-//
-//===----------------------------------------------------------------------===//
-
-#include <float.h>
-#include <math.h>
-#include <stdatomic.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-#undef NDEBUG
-#include <assert.h>
-
-// Memory order for all tests
-#define MO memory_order_seq_cst
-
-// Helper to check if a float is NaN
-static inline bool is_nan_f(float x) { return x != x; }
-static inline bool is_nan_d(double x) { return x != x; }
-
-// Helper to check if two floats have the same bit pattern (for +0/-0 distinction)
-static inline bool same_bits_f(float a, float b) {
-  uint32_t a_bits, b_bits;
-  memcpy(&a_bits, &a, sizeof(float));
-  memcpy(&b_bits, &b, sizeof(float));
-  return a_bits == b_bits;
-}
-
-static inline bool same_bits_d(double a, double b) {
-  uint64_t a_bits, b_bits;
-  memcpy(&a_bits, &a, sizeof(double));
-  memcpy(&b_bits, &b, sizeof(double));
-  return a_bits == b_bits;
-}
-
-// Helper to create negative zero
-static inline float neg_zero_f(void) { return -0.0f; }
-static inline double neg_zero_d(void) { return -0.0; }
-
-//===----------------------------------------------------------------------===//
-// Test fminimum_fetch and fetch_fminimum (propagates NaN, distinguishes zeros)
-//===----------------------------------------------------------------------===//
-
-void test_fminimum_float(void) {
-  printf("Testing __atomic_fminimum_fetch (float)...\n");
-
-  // Test 1: Normal values
-  {
-    float x = 5.0f;
-    float result = __atomic_fminimum_fetch(&x, 3.0f, MO);
-    assert(result == 3.0f && "fminimum(5.0, 3.0) should be 3.0");
-    assert(x == 3.0f && "stored value should be 3.0");
-  }
-
-  {
-    float x = 2.0f;
-    float result = __atomic_fminimum_fetch(&x, 7.0f, MO);
-    assert(result == 2.0f && "fminimum(2.0, 7.0) should be 2.0");
-    assert(x == 2.0f && "stored value should be 2.0");
-  }
-
-  // Test 2: NaN propagation - CRITICAL: fminimum propagates NaN
-  {
-    float x = 1.0f;
-    float result = __atomic_fminimum_fetch(&x, NAN, MO);
-    assert(is_nan_f(result) && "fminimum(1.0, NaN) should be NaN");
-    assert(is_nan_f(x) && "stored value should be NaN");
-  }
-
-  {
-    float x = NAN;
-    float result = __atomic_fminimum_fetch(&x, 1.0f, MO);
-    assert(is_nan_f(result) && "fminimum(NaN, 1.0) should be NaN");
-    assert(is_nan_f(x) && "stored value should be NaN");
-  }
-
-  // Test 3: Zero handling - CRITICAL: fminimum distinguishes -0 and +0
-  {
-    float x = 0.0f;
-    float result = __atomic_fminimum_fetch(&x, neg_zero_f(), MO);
-    assert(same_bits_f(result, neg_zero_f()) &&
-           "fminimum(+0, -0) should be -0");
-    assert(same_bits_f(x, neg_zero_f()) && "stored value should be -0");
-  }
-
-  {
-    float x = neg_zero_f();
-    float result = __atomic_fminimum_fetch(&x, 0.0f, MO);
-    assert(same_bits_f(result, neg_zero_f()) &&
-           "fminimum(-0, +0) should be -0");
-    assert(same_bits_f(x, neg_zero_f()) && "stored value should be -0");
-  }
-
-  // Test 4: Infinity
-  {
-    float x = INFINITY;
-    float result = __atomic_fminimum_fetch(&x, 1.0f, MO);
-    assert(result == 1.0f && "fminimum(+inf, 1.0) should be 1.0");
-  }
-
-  {
-    float x = -INFINITY;
-    float result = __atomic_fminimum_fetch(&x, 1.0f, MO);
-    assert(result == -INFINITY && "fminimum(-inf, 1.0) should be -inf");
-  }
-
-  // Test 5: fetch variant (returns old value)
-  {
-    float x = 5.0f;
-    float old = __atomic_fetch_fminimum(&x, 3.0f, MO);
-    assert(old == 5.0f && "fetch_fminimum should return old value");
-    assert(x == 3.0f && "stored value should be 3.0");
-  }
-
-  printf("  PASSED\n");
-}
-
-void test_fmaximum_float(void) {
-  printf("Testing __atomic_fmaximum_fetch (float)...\n");
-
-  // Test 1: Normal values
-  {
-    float x = 5.0f;
-    float result = __atomic_fmaximum_fetch(&x, 3.0f, MO);
-    assert(result == 5.0f && "fmaximum(5.0, 3.0) should be 5.0");
-  }
-
-  // Test 2: NaN propagation
-  {
-    float x = 1.0f;
-    float result = __atomic_fmaximum_fetch(&x, NAN, MO);
-    assert(is_nan_f(result) && "fmaximum(1.0, NaN) should be NaN");
-  }
-
-  // Test 3: Zero handling - fmaximum(+0, -0) should be +0
-  {
-    float x = 0.0f;
-    float result = __atomic_fmaximum_fetch(&x, neg_zero_f(), MO);
-    assert(same_bits_f(result, 0.0f) && "fmaximum(+0, -0) should be +0");
-  }
-
-  {
-    float x = neg_zero_f();
-    float result = __atomic_fmaximum_fetch(&x, 0.0f, MO);
-    assert(same_bits_f(result, 0.0f) && "fmaximum(-0, +0) should be +0");
-  }
-
-  // Test 4: Infinity
-  {
-    float x = INFINITY;
-    float result = __atomic_fmaximum_fetch(&x, 1.0f, MO);
-    assert(result == INFINITY && "fmaximum(+inf, 1.0) should be +inf");
-  }
-
-  printf("  PASSED\n");
-}
-
-//===----------------------------------------------------------------------===//
-// Test fminimumnum_fetch (propagates numbers, treats zeros as equivalent)
-//===----------------------------------------------------------------------===//
-
-void test_fminimum_num_float(void) {
-  printf("Testing __atomic_fminimum_num_fetch (float)...\n");
-
-  // Test 1: Normal values
-  {
-    float x = 5.0f;
-    float result = __atomic_fminimum_num_fetch(&x, 3.0f, MO);
-    assert(result == 3.0f && "fminimumnum(5.0, 3.0) should be 3.0");
-  }
-
-  // Test 2: NaN handling - CRITICAL: fminimumnum propagates NUMBER over NaN
-  {
-    float x = 1.0f;
-    float result = __atomic_fminimum_num_fetch(&x, NAN, MO);
-    assert(result == 1.0f &&
-           "fminimumnum(1.0, NaN) should be 1.0 (number over NaN)");
-    assert(x == 1.0f && "stored value should be 1.0");
-  }
-
-  {
-    float x = NAN;
-    float result = __atomic_fminimum_num_fetch(&x, 2.0f, MO);
-    assert(result == 2.0f &&
-           "fminimumnum(NaN, 2.0) should be 2.0 (number over NaN)");
-    assert(x == 2.0f && "stored value should be 2.0");
-  }
-
-  {
-    float x = NAN;
-    float result = __atomic_fminimum_num_fetch(&x, NAN, MO);
-    assert(is_nan_f(result) && "fminimumnum(NaN, NaN) should be NaN");
-  }
-
-  // Test 3: Zero handling - fminimumnum treats +0 and -0 as equivalent
-  // The result can be either, but should pick the minimum value
-  {
-    float x = 0.0f;
-    float result = __atomic_fminimum_num_fetch(&x, neg_zero_f(), MO);
-    // Result should be a zero (either +0 or -0 is acceptable per IEEE 754)
-    assert(result == 0.0f && "fminimumnum(+0, -0) should be zero");
-  }
-
-  // Test 4: Infinity
-  {
-    float x = INFINITY;
-    float result = __atomic_fminimum_num_fetch(&x, 1.0f, MO);
-    assert(result == 1.0f && "fminimumnum(+inf, 1.0) should be 1.0");
-  }
-
-  // Test 5: fetch variant
-  {
-    float x = NAN;
-    float old = __atomic_fetch_fminimum_num(&x, 3.0f, MO);
-    assert(is_nan_f(old) && "fetch_fminimum_num should return old value (NaN)");
-    assert(x == 3.0f && "stored value should be 3.0");
-  }
-
-  printf("  PASSED\n");
-}
-
-void test_fmaximum_num_float(void) {
-  printf("Testing __atomic_fmaximum_num_fetch (float)...\n");
-
-  // Test 1: Normal values
-  {
-    float x = 5.0f;
-    float result = __atomic_fmaximum_num_fetch(&x, 3.0f, MO);
-    assert(result == 5.0f && "fmaximumnum(5.0, 3.0) should be 5.0");
-  }
-
-  // Test 2: NaN handling - propagates number over NaN
-  {
-    float x = 1.0f;
-    float result = __atomic_fmaximum_num_fetch(&x, NAN, MO);
-    assert(result == 1.0f && "fmaximumnum(1.0, NaN) should be 1.0");
-  }
-
-  {
-    float x = NAN;
-    float result = __atomic_fmaximum_num_fetch(&x, 2.0f, MO);
-    assert(result == 2.0f && "fmaximumnum(NaN, 2.0) should be 2.0");
-  }
-
-  // Test 3: Zero handling - treats +0 and -0 as equivalent
-  {
-    float x = 0.0f;
-    float result = __atomic_fmaximum_num_fetch(&x, neg_zero_f(), MO);
-    assert(result == 0.0f && "fmaximumnum(+0, -0) should be zero");
-  }
-
-  printf("  PASSED\n");
-}
-
-//===----------------------------------------------------------------------===//
-// Double precision tests
-//===----------------------------------------------------------------------===//
-
-void test_fminimum_double(void) {
-  printf("Testing __atomic_fminimum_fetch (double)...\n");
-
-  // Test NaN propagation
-  {
-    double x = 1.0;
-    double result = __atomic_fminimum_fetch(&x, NAN, MO);
-    assert(is_nan_d(result) && "fminimum(1.0, NaN) should be NaN (double)");
-  }
-
-  // Test zero distinction
-  {
-    double x = 0.0;
-    double result = __atomic_fminimum_fetch(&x, neg_zero_d(), MO);
-    assert(same_bits_d(result, neg_zero_d()) &&
-           "fminimum(+0, -0) should be -0 (double)");
-  }
-
-  // Test normal values
-  {
-    double x = 3.14;
-    double result = __atomic_fminimum_fetch(&x, 2.71, MO);
-    assert(result == 2.71 && "fminimum(3.14, 2.71) should be 2.71");
-  }
-
-  printf("  PASSED\n");
-}
-
-void test_fmaximum_double(void) {
-  printf("Testing __atomic_fmaximum_fetch (double)...\n");
-
-  // Test NaN propagation
-  {
-    double x = 1.0;
-    double result = __atomic_fmaximum_fetch(&x, NAN, MO);
-    assert(is_nan_d(result) && "fmaximum(1.0, NaN) should be NaN (double)");
-  }
-
-  // Test zero distinction - fmaximum(+0, -0) = +0
-  {
-    double x = 0.0;
-    double result = __atomic_fmaximum_fetch(&x, neg_zero_d(), MO);
-    assert(same_bits_d(result, 0.0) &&
-           "fmaximum(+0, -0) should be +0 (double)");
-  }
-
-  printf("  PASSED\n");
-}
-
-void test_fminimum_num_double(void) {
-  printf("Testing __atomic_fminimum_num_fetch (double)...\n");
-
-  // Test number over NaN
-  {
-    double x = NAN;
-    double result = __atomic_fminimum_num_fetch(&x, 2.5, MO);
-    assert(result == 2.5 && "fminimumnum(NaN, 2.5) should be 2.5 (double)");
-  }
-
-  // Test normal values
-  {
-    double x = 10.5;
-    double result = __atomic_fminimum_num_fetch(&x, 8.3, MO);
-    assert(result == 8.3 && "fminimumnum(10.5, 8.3) should be 8.3");
-  }
-
-  printf("  PASSED\n");
-}
-
-void test_fmaximum_num_double(void) {
-  printf("Testing __atomic_fmaximum_num_fetch (double)...\n");
-
-  // Test number over NaN
-  {
-    double x = NAN;
-    double result = __atomic_fmaximum_num_fetch(&x, 2.5, MO);
-    assert(result == 2.5 && "fmaximumnum(NaN, 2.5) should be 2.5 (double)");
-  }
-
-  printf("  PASSED\n");
-}
-
-//===----------------------------------------------------------------------===//
-// Main test runner
-//===----------------------------------------------------------------------===//
-
-int main(void) {
-  printf("\n");
-  printf("=============================================================\n");
-  printf("Atomic Floating-Point Min/Max Tests\n");
-  printf("=============================================================\n");
-  printf("\n");
-
-  printf("--- fminimum/fmaximum (propagate NaN, distinguish zeros) ---\n");
-  test_fminimum_float();
-  test_fmaximum_float();
-  test_fminimum_double();
-  test_fmaximum_double();
-
-  printf("\n--- fminimumnum/fmaximumnum (prefer numbers, treat zeros equal) "
-         "---\n");
-  test_fminimum_num_float();
-  test_fmaximum_num_float();
-  test_fminimum_num_double();
-  test_fmaximum_num_double();
-
-  printf("\n");
-  printf("=============================================================\n");
-  printf("All tests PASSED!\n");
-  printf("=============================================================\n");
-  printf("\n");
-
-  return 0;
-}
diff --git a/cross-project-tests/atomics/atomic_fp_minmax_test.c b/cross-project-tests/atomics/atomic_fp_minmax_test.c
new file mode 100644
index 0000000000000..0efee31de0f13
--- /dev/null
+++ b/cross-project-tests/atomics/atomic_fp_minmax_test.c
@@ -0,0 +1,398 @@
+// RUN: %clang %s -O2 -o %t && %run %t
+//===-- atomic_fp_minmax_test.c - Test FP atomic min/max operations -------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file tests the floating-point atomic fetch-before min/max builtins,
+// focusing on IEEE 754 corner cases: NaN, +/-infinity, +/-zero.
+//
+// Two families of operations:
+// 1. fetch_fminimum/fetch_fmaximum: IEEE 754-2019 minimum/maximum
+//    - Propagates NaN (any NaN input produces NaN output)
+//    - Distinguishes -0 and +0 (minimum(-0, +0) = -0, maximum(-0, +0) = +0)
+//
+// 2. fetch_fminimum_num/fetch_fmaximum_num: IEEE 754-2019 minimumNumber/maximumNumber
+//    - Propagates numbers over NaN (minimumNumber(2.0, NaN) = 2.0)
+//    - Treats -0 and +0 as equivalent
+//
+// All builtins return the old (pre-operation) value.
+//
+//===----------------------------------------------------------------------===//
+
+#include <float.h>
+#include <math.h>
+#include <stdatomic.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#undef NDEBUG
+#include <assert.h>
+
+// Memory order for all tests
+#define MO memory_order_seq_cst
+
+// Helper to check if a float is NaN
+static inline bool is_nan_f(float x) { return x != x; }
+static inline bool is_nan_d(double x) { return x != x; }
+
+// Helper to check if two floats have the same bit pattern (for +0/-0
+// distinction)
+static inline bool same_bits_f(float a, float b) {
+  uint32_t a_bits, b_bits;
+  memcpy(&a_bits, &a, sizeof(float));
+  memcpy(&b_bits, &b, sizeof(float));
+  return a_bits == b_bits;
+}
+
+static inline bool same_bits_d(double a, double b) {
+  uint64_t a_bits, b_bits;
+  memcpy(&a_bits, &a, sizeof(double));
+  memcpy(&b_bits, &b, sizeof(double));
+  return a_bits == b_bits;
+}
+
+// Helper to create negative zero
+static inline float neg_zero_f(void) { return -0.0f; }
+static inline double neg_zero_d(void) { return -0.0; }
+
+//===----------------------------------------------------------------------===//
+// fetch_fminimum / fetch_fmaximum (propagates NaN, distinguishes zeros)
+//===----------------------------------------------------------------------===//
+
+void test_fetch_fminimum_float(void) {
+  printf("Testing __atomic_fetch_fminimum (float)...\n");
+
+  // Returns old value, stores minimum
+  {
+    float x = 5.0f;
+    float old = __atomic_fetch_fminimum(&x, 3.0f, MO);
+    assert(old == 5.0f && "fetch_fminimum should return old value");
+    assert(x == 3.0f && "stored value should be 3.0");
+  }
+
+  {
+    float x = 2.0f;
+    float old = __atomic_fetch_fminimum(&x, 7.0f, MO);
+    assert(old == 2.0f && "fetch_fminimum should return old value");
+    assert(x == 2.0f && "stored value should be 2.0 (unchanged)");
+  }
+
+  // NaN propagation: fminimum(1.0, NaN) = NaN
+  {
+    float x = 1.0f;
+    float old = __atomic_fetch_fminimum(&x, NAN, MO);
+    assert(old == 1.0f && "fetch_fminimum should return old value");
+    assert(is_nan_f(x) && "stored value should be NaN");
+  }
+
+  // NaN propagation: fminimum(NaN, 1.0) = NaN
+  {
+    float x = NAN;
+    float old = __atomic_fetch_fminimum(&x, 1.0f, MO);
+    assert(is_nan_f(old) && "fetch_fminimum should return old NaN");
+    assert(is_nan_f(x) && "stored value should be NaN");
+  }
+
+  // Zero distinction: fminimum(+0, -0) = -0
+  {
+    float x = 0.0f;
+    float old = __atomic_fetch_fminimum(&x, neg_zero_f(), MO);
+    assert(same_bits_f(old, 0.0f) && "fetch_fminimum should return old +0");
+    assert(same_bits_f(x, neg_zero_f()) && "stored value should be -0");
+  }
+
+  // Zero distinction: fminimum(-0, +0) = -0
+  {
+    float x = neg_zero_f();
+    float old = __atomic_fetch_fminimum(&x, 0.0f, MO);
+    assert(same_bits_f(old, neg_zero_f()) && "fetch_fminimum should return old -0");
+    assert(same_bits_f(x, neg_zero_f()) && "stored value should be -0");
+  }
+
+  // Infinity
+  {
+    float x = INFINITY;
+    float old = __atomic_fetch_fminimum(&x, 1.0f, MO);
+    assert(old == INFINITY && "fetch_fminimum should return old +inf");
+    assert(x == 1.0f && "stored value should be 1.0");
+  }
+
+  {
+    float x = -INFINITY;
+    float old = __atomic_fetch_fminimum(&x, 1.0f, MO);
+    assert(old == -INFINITY && "fetch_fminimum should return old -inf");
+    assert(x == -INFINITY && "stored value should be -inf (unchanged)");
+  }
+
+  printf("  PASSED\n");
+}
+
+void test_fetch_fmaximum_float(void) {
+  printf("Testing __atomic_fetch_fmaximum (float)...\n");
+
+  // Returns old value, stores maximum
+  {
+    float x = 5.0f;
+    float old = __atomic_fetch_fmaximum(&x, 3.0f, MO);
+    assert(old == 5.0f && "fetch_fmaximum should return old value");
+    assert(x == 5.0f && "stored value should be 5.0 (unchanged)");
+  }
+
+  {
+    float x = 2.0f;
+    float old = __atomic_fetch_fmaximum(&x, 7.0f, MO);
+    assert(old == 2.0f && "fetch_fmaximum should return old value");
+    assert(x == 7.0f && "stored value should be 7.0");
+  }
+
+  // NaN propagation: fmaximum(1.0, NaN) = NaN
+  {
+    float x = 1.0f;
+    float old = __atomic_fetch_fmaximum(&x, NAN, MO);
+    assert(old == 1.0f && "fetch_fmaximum should return old value");
+    assert(is_nan_f(x) && "stored value should be NaN");
+  }
+
+  // Zero distinction: fmaximum(+0, -0) = +0
+  {
+    float x = 0.0f;
+    float old = __atomic_fetch_fmaximum(&x, neg_zero_f(), MO);
+    assert(same_bits_f(old, 0.0f) && "fetch_fmaximum should return old +0");
+    assert(same_bits_f(x, 0.0f) && "stored value should be +0");
+  }
+
+  // Zero distinction: fmaximum(-0, +0) = +0
+  {
+    float x = neg_zero_f();
+    float old = __atomic_fetch_fmaximum(&x, 0.0f, MO);
+    assert(same_bits_f(old, neg_zero_f()) && "fetch_fmaximum should return old -0");
+    assert(same_bits_f(x, 0.0f) && "stored value should be +0");
+  }
+
+  // Infinity
+  {
+    float x = INFINITY;
+    float old = __atomic_fetch_fmaximum(&x, 1.0f, MO);
+    assert(old == INFINITY && "fetch_fmaximum should return old +inf");
+    assert(x == INFINITY && "stored value should be +inf (unchanged)");
+  }
+
+  printf("  PASSED\n");
+}
+
+//===----------------------------------------------------------------------===//
+// fetch_fminimum_num / fetch_fmaximum_num (propagates numbers, zeros equal)
+//===----------------------------------------------------------------------===//
+
+void test_fetch_fminimum_num_float(void) {
+  printf("Testing __atomic_fetch_fminimum_num (float)...\n");
+
+  // Returns old value, stores minimumnum
+  {
+    float x = 5.0f;
+    float old = __atomic_fetch_fminimum_num(&x, 3.0f, MO);
+    assert(old == 5.0f && "fetch_fminimum_num should return old value");
+    assert(x == 3.0f && "stored value should be 3.0");
+  }
+
+  // Number over NaN: fminimumnum(1.0, NaN) = 1.0
+  {
+    float x = 1.0f;
+    float old = __atomic_fetch_fminimum_num(&x, NAN, MO);
+    assert(old == 1.0f && "fetch_fminimum_num should return old value");
+    assert(x == 1.0f && "stored value should be 1.0 (number over NaN)");
+  }
+
+  // Number over NaN: fminimumnum(NaN, 2.0) = 2.0
+  {
+    float x = NAN;
+    float old = __atomic_fetch_fminimum_num(&x, 2.0f, MO);
+    assert(is_nan_f(old) && "fetch_fminimum_num should return old NaN");
+    assert(x == 2.0f && "stored value should be 2.0 (number over NaN)");
+  }
+
+  // NaN + NaN = NaN
+  {
+    float x = NAN;
+    float old = __atomic_fetch_fminimum_num(&x, NAN, MO);
+    assert(is_nan_f(old) && "fetch_fminimum_num should return old NaN");
+    assert(is_nan_f(x) && "stored value should be NaN");
+  }
+
+  // Infinity
+  {
+    float x = INFINITY;
+    float old = __atomic_fetch_fminimum_num(&x, 1.0f, MO);
+    assert(old == INFINITY && "fetch_fminimum_num should return old +inf");
+    assert(x == 1.0f && "stored value should be 1.0");
+  }
+
+  printf("  PASSED\n");
+}
+
+void test_fetch_fmaximum_num_float(void) {
+  printf("Testing __atomic_fetch_fmaximum_num (float)...\n");
+
+  // Returns old value, stores maximumnum
+  {
+    float x = 5.0f;
+    float old = __atomic_fetch_fmaximum_num(&x, 3.0f, MO);
+    assert(old == 5.0f && "fetch_fmaximum_num should return old value");
+    assert(x == 5.0f && "stored value should be 5.0 (unchanged)");
+  }
+
+  // Number over NaN: fmaximumnum(1.0, NaN) = 1.0
+  {
+    float x = 1.0f;
+    float old = __atomic_fetch_fmaximum_num(&x, NAN, MO);
+    assert(old == 1.0f && "fetch_fmaximum_num should return old value");
+    assert(x == 1.0f && "stored value should be 1.0 (number over NaN)");
+  }
+
+  // Number over NaN: fmaximumnum(NaN, 2.0) = 2.0
+  {
+    float x = NAN;
+    float old = __atomic_fetch_fmaximum_num(&x, 2.0f, MO);
+    assert(is_nan_f(old) && "fetch_fmaximum_num should return old NaN");
+    assert(x == 2.0f && "stored value should be 2.0 (number over NaN)");
+  }
+
+  printf("  PASSED\n");
+}
+
+//===----------------------------------------------------------------------===//
+// Double precision tests
+//===----------------------------------------------------------------------===//
+
+void test_fetch_fminimum_double(void) {
+  printf("Testing __atomic_fetch_fminimum (double)...\n");
+
+  // NaN propagation
+  {
+    double x = 1.0;
+    double old = __atomic_fetch_fminimum(&x, NAN, MO);
+    assert(old == 1.0 && "fetch_fminimum should return old value");
+    assert(is_nan_d(x) && "stored value should be NaN");
+  }
+
+  // Zero distinction: fminimum(+0, -0) = -0
+  {
+    double x = 0.0;
+    double old = __atomic_fetch_fminimum(&x, neg_zero_d(), MO);
+    assert(same_bits_d(old, 0.0) && "fetch_fminimum should return old +0");
+    assert(same_bits_d(x, neg_zero_d()) && "stored value should be -0");
+  }
+
+  // Normal values
+  {
+    double x = 3.14;
+    double old = __atomic_fetch_fminimum(&x, 2.71, MO);
+    assert(old == 3.14 && "fetch_fminimum should return old value");
+    assert(x == 2.71 && "stored value should be 2.71");
+  }
+
+  printf("  PASSED\n");
+}
+
+void test_fetch_fmaximum_double(void) {
+  printf("Testing __atomic_fetch_fmaximum (double)...\n");
+
+  // NaN propagation
+  {
+    double x = 1.0;
+    double old = __atomic_fetch_fmaximum(&x, NAN, MO);
+    assert(old == 1.0 && "fetch_fmaximum should return old value");
+    assert(is_nan_d(x) && "stored value should be NaN");
+  }
+
+  // Zero distinction: fmaximum(-0, +0) = +0
+  {
+    double x = neg_zero_d();
+    double old = __atomic_fetch_fmaximum(&x, 0.0, MO);
+    assert(same_bits_d(old, neg_zero_d()) && "fetch_fmaximum should return old -0");
+    assert(same_bits_d(x, 0.0) && "stored value should be +0");
+  }
+
+  printf("  PASSED\n");
+}
+
+void test_fetch_fminimum_num_double(void) {
+  printf("Testing __atomic_fetch_fminimum_num (double)...\n");
+
+  // Number over NaN
+  {
+    double x = NAN;
+    double old = __atomic_fetch_fminimum_num(&x, 2.5, MO);
+    assert(is_nan_d(old) && "fetch_fminimum_num should return old NaN");
+    assert(x == 2.5 && "stored value should be 2.5");
+  }
+
+  // Normal values
+  {
+    double x = 10.5;
+    double old = __atomic_fetch_fminimum_num(&x, 8.3, MO);
+    assert(old == 10.5 && "fetch_fminimum_num should return old value");
+    assert(x == 8.3 && "stored value should be 8.3");
+  }
+
+  printf("  PASSED\n");
+}
+
+void test_fetch_fmaximum_num_double(void) {
+  printf("Testing __atomic_fetch_fmaximum_num (double)...\n");
+
+  // Number over NaN
+  {
+    double x = NAN;
+    double old = __atomic_fetch_fmaximum_num(&x, 2.5, MO);
+    assert(is_nan_d(old) && "fetch_fmaximum_num should return old NaN");
+    assert(x == 2.5 && "stored value should be 2.5");
+  }
+
+  // Normal values
+  {
+    double x = 1.0;
+    double old = __atomic_fetch_fmaximum_num(&x, 5.5, MO);
+    assert(old == 1.0 && "fetch_fmaximum_num should return old value");
+    assert(x == 5.5 && "stored value should be 5.5");
+  }
+
+  printf("  PASSED\n");
+}
+
+//===----------------------------------------------------------------------===//
+// Main test runner
+//===----------------------------------------------------------------------===//
+
+int main(void) {
+  printf("\n");
+  printf("=============================================================\n");
+  printf("Atomic Floating-Point Min/Max Tests\n");
+  printf("=============================================================\n");
+  printf("\n");
+
+  printf("--- fetch_fminimum/fetch_fmaximum (propagate NaN, distinguish zeros) ---\n");
+  test_fetch_fminimum_float();
+  test_fetch_fmaximum_float();
+  test_fetch_fminimum_double();
+  test_fetch_fmaximum_double();
+
+  printf("\n--- fetch_fminimum_num/fetch_fmaximum_num (prefer numbers, treat zeros equal) ---\n");
+  test_fetch_fminimum_num_float();
+  test_fetch_fmaximum_num_float();
+  test_fetch_fminimum_num_double();
+  test_fetch_fmaximum_num_double();
+
+  printf("\n");
+  printf("=============================================================\n");
+  printf("All tests PASSED!\n");
+  printf("=============================================================\n");
+  printf("\n");
+
+  return 0;
+}
diff --git a/cross-project-tests/atomics/lit.local.cfg b/cross-project-tests/atomics/lit.local.cfg
new file mode 100644
index 0000000000000..530f4c01646ff
--- /dev/null
+++ b/cross-project-tests/atomics/lit.local.cfg
@@ -0,0 +1,2 @@
+if "clang" not in config.available_features:
+    config.unsupported = True

>From 2abd3c1beef3bfed5565e3e42004e924f6fa1dcd Mon Sep 17 00:00:00 2001
From: Gonzalo Brito Gadeschi <gonzalob at nvidia.com>
Date: Fri, 1 May 2026 11:31:18 -0700
Subject: [PATCH 5/6] more clean up

---
 clang/lib/CodeGen/CGAtomic.cpp | 15 +++++----------
 1 file changed, 5 insertions(+), 10 deletions(-)

diff --git a/clang/lib/CodeGen/CGAtomic.cpp b/clang/lib/CodeGen/CGAtomic.cpp
index 3e521ef8d7643..6f883212b2cd3 100644
--- a/clang/lib/CodeGen/CGAtomic.cpp
+++ b/clang/lib/CodeGen/CGAtomic.cpp
@@ -530,16 +530,11 @@ static llvm::Value *EmitPostAtomicMinMax(CGBuilderTy &Builder,
   const bool IsFP = OldVal->getType()->isFloatingPointTy();
 
   if (IsFP) {
-    llvm::Intrinsic::ID IID;
-    if (Op == AtomicExpr::AO__atomic_max_fetch ||
-        Op == AtomicExpr::AO__scoped_atomic_max_fetch)
-      IID = llvm::Intrinsic::maxnum;
-    else if (Op == AtomicExpr::AO__atomic_min_fetch ||
-             Op == AtomicExpr::AO__scoped_atomic_min_fetch)
-      IID = llvm::Intrinsic::minnum;
-    else
-      llvm_unreachable("Unexpected atomic FP min/max operation");
-
+    llvm::Intrinsic::ID IID =
+        (Op == AtomicExpr::AO__atomic_max_fetch ||
+         Op == AtomicExpr::AO__scoped_atomic_max_fetch)
+            ? llvm::Intrinsic::maxnum
+            : llvm::Intrinsic::minnum;
     return Builder.CreateBinaryIntrinsic(IID, OldVal, RHS, llvm::FMFSource(),
                                          "newval");
   }

>From d84c48d53f2d6b1af772ed8bafbab4150da736a8 Mon Sep 17 00:00:00 2001
From: Gonzalo Brito Gadeschi <gonzalob at nvidia.com>
Date: Fri, 1 May 2026 11:43:19 -0700
Subject: [PATCH 6/6] formatting

---
 clang/lib/CodeGen/CGAtomic.cpp                 |  9 ++++-----
 .../AArch64/atomic-ops-float-check-minmax.c    |  4 ++++
 .../atomics/atomic_fp_minmax_test.c            | 18 ++++++++++++------
 3 files changed, 20 insertions(+), 11 deletions(-)

diff --git a/clang/lib/CodeGen/CGAtomic.cpp b/clang/lib/CodeGen/CGAtomic.cpp
index 6f883212b2cd3..25fc7c1d32800 100644
--- a/clang/lib/CodeGen/CGAtomic.cpp
+++ b/clang/lib/CodeGen/CGAtomic.cpp
@@ -530,11 +530,10 @@ static llvm::Value *EmitPostAtomicMinMax(CGBuilderTy &Builder,
   const bool IsFP = OldVal->getType()->isFloatingPointTy();
 
   if (IsFP) {
-    llvm::Intrinsic::ID IID =
-        (Op == AtomicExpr::AO__atomic_max_fetch ||
-         Op == AtomicExpr::AO__scoped_atomic_max_fetch)
-            ? llvm::Intrinsic::maxnum
-            : llvm::Intrinsic::minnum;
+    llvm::Intrinsic::ID IID = (Op == AtomicExpr::AO__atomic_max_fetch ||
+                               Op == AtomicExpr::AO__scoped_atomic_max_fetch)
+                                  ? llvm::Intrinsic::maxnum
+                                  : llvm::Intrinsic::minnum;
     return Builder.CreateBinaryIntrinsic(IID, OldVal, RHS, llvm::FMFSource(),
                                          "newval");
   }
diff --git a/clang/test/CodeGen/AArch64/atomic-ops-float-check-minmax.c b/clang/test/CodeGen/AArch64/atomic-ops-float-check-minmax.c
index 46d9e387c0326..7d70577eb6a25 100644
--- a/clang/test/CodeGen/AArch64/atomic-ops-float-check-minmax.c
+++ b/clang/test/CodeGen/AArch64/atomic-ops-float-check-minmax.c
@@ -122,6 +122,10 @@ void test_minmax_postop(float *f32, _Float16 *f16, __bf16 *bf16, double *f64) {
 // CHECK:    [[TMP1:%.*]] = atomicrmw fmaximum ptr {{%.*}}, double {{%.*}} release, align 8
 // CHECK:    [[TMP2:%.*]] = atomicrmw fminimumnum ptr {{%.*}}, double {{%.*}} release, align 8
 // CHECK:    [[TMP3:%.*]] = atomicrmw fmaximumnum ptr {{%.*}}, double {{%.*}} release, align 8
+// CHECK-NOT: call {{.*}} @llvm.minimum
+// CHECK-NOT: call {{.*}} @llvm.maximum
+// CHECK-NOT: call {{.*}} @llvm.minimumnum
+// CHECK-NOT: call {{.*}} @llvm.maximumnum
 void test_fetch_variants(double *f64) {
   double old1 = __atomic_fetch_fminimum(f64, 42.1, memory_order_release);
   double old2 = __atomic_fetch_fmaximum(f64, 42.1, memory_order_release);
diff --git a/cross-project-tests/atomics/atomic_fp_minmax_test.c b/cross-project-tests/atomics/atomic_fp_minmax_test.c
index 0efee31de0f13..7eb24dc493e43 100644
--- a/cross-project-tests/atomics/atomic_fp_minmax_test.c
+++ b/cross-project-tests/atomics/atomic_fp_minmax_test.c
@@ -15,7 +15,8 @@
 //    - Propagates NaN (any NaN input produces NaN output)
 //    - Distinguishes -0 and +0 (minimum(-0, +0) = -0, maximum(-0, +0) = +0)
 //
-// 2. fetch_fminimum_num/fetch_fmaximum_num: IEEE 754-2019 minimumNumber/maximumNumber
+// 2. fetch_fminimum_num/fetch_fmaximum_num: IEEE 754-2019
+// minimumNumber/maximumNumber
 //    - Propagates numbers over NaN (minimumNumber(2.0, NaN) = 2.0)
 //    - Treats -0 and +0 as equivalent
 //
@@ -110,7 +111,8 @@ void test_fetch_fminimum_float(void) {
   {
     float x = neg_zero_f();
     float old = __atomic_fetch_fminimum(&x, 0.0f, MO);
-    assert(same_bits_f(old, neg_zero_f()) && "fetch_fminimum should return old -0");
+    assert(same_bits_f(old, neg_zero_f()) &&
+           "fetch_fminimum should return old -0");
     assert(same_bits_f(x, neg_zero_f()) && "stored value should be -0");
   }
 
@@ -170,7 +172,8 @@ void test_fetch_fmaximum_float(void) {
   {
     float x = neg_zero_f();
     float old = __atomic_fetch_fmaximum(&x, 0.0f, MO);
-    assert(same_bits_f(old, neg_zero_f()) && "fetch_fmaximum should return old -0");
+    assert(same_bits_f(old, neg_zero_f()) &&
+           "fetch_fmaximum should return old -0");
     assert(same_bits_f(x, 0.0f) && "stored value should be +0");
   }
 
@@ -314,7 +317,8 @@ void test_fetch_fmaximum_double(void) {
   {
     double x = neg_zero_d();
     double old = __atomic_fetch_fmaximum(&x, 0.0, MO);
-    assert(same_bits_d(old, neg_zero_d()) && "fetch_fmaximum should return old -0");
+    assert(same_bits_d(old, neg_zero_d()) &&
+           "fetch_fmaximum should return old -0");
     assert(same_bits_d(x, 0.0) && "stored value should be +0");
   }
 
@@ -376,13 +380,15 @@ int main(void) {
   printf("=============================================================\n");
   printf("\n");
 
-  printf("--- fetch_fminimum/fetch_fmaximum (propagate NaN, distinguish zeros) ---\n");
+  printf("--- fetch_fminimum/fetch_fmaximum (propagate NaN, distinguish zeros) "
+         "---\n");
   test_fetch_fminimum_float();
   test_fetch_fmaximum_float();
   test_fetch_fminimum_double();
   test_fetch_fmaximum_double();
 
-  printf("\n--- fetch_fminimum_num/fetch_fmaximum_num (prefer numbers, treat zeros equal) ---\n");
+  printf("\n--- fetch_fminimum_num/fetch_fmaximum_num (prefer numbers, treat "
+         "zeros equal) ---\n");
   test_fetch_fminimum_num_float();
   test_fetch_fmaximum_num_float();
   test_fetch_fminimum_num_double();



More information about the cfe-commits mailing list