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 <[email protected]>
Date: Tue, 17 Mar 2026 15:24:47 -0700
Subject: [PATCH] [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;
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to