[Mlir-commits] [flang] [llvm] [mlir] Revert "Revert "[Flang] [OpenMP] atomic compare (#184761)"" (PR #198978)

CHANDRA GHALE llvmlistbot at llvm.org
Thu May 21 01:06:28 PDT 2026


https://github.com/chandraghale created https://github.com/llvm/llvm-project/pull/198978

Reverts llvm/llvm-project#198848

Reverting the reverted PR [https://github.com/llvm/llvm-project/pull/198848](https://github.com/llvm/llvm-project/pull/198848) , since the actual issue was different and the original PR [https://github.com/llvm/llvm-project/pull/184761](https://github.com/llvm/llvm-project/pull/184761) was reverted incorrectly.

Ref :
https://github.com/llvm/llvm-project/pull/184761#issuecomment-4501760965


>From 0173d9c2db5c51d6a4f01ee0d61be87195b6d52d Mon Sep 17 00:00:00 2001
From: CHANDRA GHALE <chandra.nitdgp at gmail.com>
Date: Thu, 21 May 2026 13:22:34 +0530
Subject: [PATCH] Revert "Revert "[Flang] [OpenMP] atomic compare (#184761)"
 (#198848)"

This reverts commit 28d6673e21f72d5b4b8238c220afa9abaa6b91c6.
---
 flang/include/flang/Lower/ConvertType.h       |   8 +
 flang/lib/Lower/ConvertExprToHLFIR.cpp        |  60 +-
 flang/lib/Lower/ConvertType.cpp               |  66 +++
 flang/lib/Lower/OpenMP/Atomic.cpp             | 144 ++++-
 .../Integration/OpenMP/atomic-compare.f90     | 250 +++++++++
 .../Lower/OpenMP/Todo/atomic-compare-fail.f90 |  21 +-
 .../test/Lower/OpenMP/Todo/atomic-compare.f90 |  11 -
 flang/test/Lower/OpenMP/atomic-compare.f90    | 142 +++++
 .../test/Semantics/OpenMP/atomic-compare.f90  |  71 +++
 .../llvm/Frontend/OpenMP/OMPIRBuilder.h       |  12 +
 llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp     | 297 +++++++---
 .../Interfaces/AtomicInterfaces.h             |   1 +
 .../Interfaces/AtomicInterfaces.td            | 171 ++++++
 mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td |  52 +-
 .../OpenACCMPCommon/Interfaces/CMakeLists.txt |   2 +
 mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp  |  26 +
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      | 359 ++++++++++++
 mlir/test/Dialect/OpenMP/invalid.mlir         | 517 ++++++++++++++++++
 mlir/test/Target/LLVMIR/openmp-llvm.mlir      | 209 +++++++
 19 files changed, 2287 insertions(+), 132 deletions(-)
 create mode 100644 flang/test/Integration/OpenMP/atomic-compare.f90
 delete mode 100644 flang/test/Lower/OpenMP/Todo/atomic-compare.f90
 create mode 100644 flang/test/Lower/OpenMP/atomic-compare.f90

diff --git a/flang/include/flang/Lower/ConvertType.h b/flang/include/flang/Lower/ConvertType.h
index 3c726595c0f76..16b299e2ab319 100644
--- a/flang/include/flang/Lower/ConvertType.h
+++ b/flang/include/flang/Lower/ConvertType.h
@@ -23,6 +23,7 @@
 
 #include "flang/Evaluate/type.h"
 #include "flang/Support/Fortran.h"
+#include "mlir/Dialect/Arith/IR/Arith.h"
 #include "mlir/IR/BuiltinTypes.h"
 
 namespace mlir {
@@ -130,6 +131,13 @@ class ComponentReverseIterator {
   name_iterator componentIt{};
   name_iterator componentItEnd{};
 };
+
+mlir::arith::CmpIPredicate
+translateSignedRelational(Fortran::common::RelationalOperator rop);
+mlir::arith::CmpIPredicate
+translateUnsignedRelational(Fortran::common::RelationalOperator rop);
+mlir::arith::CmpFPredicate
+translateFloatRelational(Fortran::common::RelationalOperator rop);
 } // namespace lower
 } // namespace Fortran
 
diff --git a/flang/lib/Lower/ConvertExprToHLFIR.cpp b/flang/lib/Lower/ConvertExprToHLFIR.cpp
index a57fce53c0ca5..21436bd1b8d15 100644
--- a/flang/lib/Lower/ConvertExprToHLFIR.cpp
+++ b/flang/lib/Lower/ConvertExprToHLFIR.cpp
@@ -1191,52 +1191,6 @@ translateSignedRelational(Fortran::common::RelationalOperator rop) {
   llvm_unreachable("unhandled INTEGER relational operator");
 }
 
-static mlir::arith::CmpIPredicate
-translateUnsignedRelational(Fortran::common::RelationalOperator rop) {
-  switch (rop) {
-  case Fortran::common::RelationalOperator::LT:
-    return mlir::arith::CmpIPredicate::ult;
-  case Fortran::common::RelationalOperator::LE:
-    return mlir::arith::CmpIPredicate::ule;
-  case Fortran::common::RelationalOperator::EQ:
-    return mlir::arith::CmpIPredicate::eq;
-  case Fortran::common::RelationalOperator::NE:
-    return mlir::arith::CmpIPredicate::ne;
-  case Fortran::common::RelationalOperator::GT:
-    return mlir::arith::CmpIPredicate::ugt;
-  case Fortran::common::RelationalOperator::GE:
-    return mlir::arith::CmpIPredicate::uge;
-  }
-  llvm_unreachable("unhandled UNSIGNED relational operator");
-}
-
-/// Convert parser's REAL relational operators to MLIR.
-/// The choice of order (O prefix) vs unorder (U prefix) follows Fortran 2018
-/// requirements in the IEEE context (table 17.1 of F2018). This choice is
-/// also applied in other contexts because it is easier and in line with
-/// other Fortran compilers.
-/// FIXME: The signaling/quiet aspect of the table 17.1 requirement is not
-/// fully enforced. FIR and LLVM `fcmp` instructions do not give any guarantee
-/// whether the comparison will signal or not in case of quiet NaN argument.
-static mlir::arith::CmpFPredicate
-translateFloatRelational(Fortran::common::RelationalOperator rop) {
-  switch (rop) {
-  case Fortran::common::RelationalOperator::LT:
-    return mlir::arith::CmpFPredicate::OLT;
-  case Fortran::common::RelationalOperator::LE:
-    return mlir::arith::CmpFPredicate::OLE;
-  case Fortran::common::RelationalOperator::EQ:
-    return mlir::arith::CmpFPredicate::OEQ;
-  case Fortran::common::RelationalOperator::NE:
-    return mlir::arith::CmpFPredicate::UNE;
-  case Fortran::common::RelationalOperator::GT:
-    return mlir::arith::CmpFPredicate::OGT;
-  case Fortran::common::RelationalOperator::GE:
-    return mlir::arith::CmpFPredicate::OGE;
-  }
-  llvm_unreachable("unhandled REAL relational operator");
-}
-
 template <int KIND>
 struct BinaryOp<Fortran::evaluate::Relational<
     Fortran::evaluate::Type<Fortran::common::TypeCategory::Integer, KIND>>> {
@@ -1247,7 +1201,8 @@ struct BinaryOp<Fortran::evaluate::Relational<
                                          const Op &op, hlfir::Entity lhs,
                                          hlfir::Entity rhs) {
     auto cmp = mlir::arith::CmpIOp::create(
-        builder, loc, translateSignedRelational(op.opr), lhs, rhs);
+        builder, loc, Fortran::lower::translateSignedRelational(op.opr), lhs,
+        rhs);
     return hlfir::EntityWithAttributes{cmp};
   }
 };
@@ -1269,7 +1224,8 @@ struct BinaryOp<Fortran::evaluate::Relational<
     mlir::Value lhsSL = builder.createConvert(loc, signlessType, lhs);
     mlir::Value rhsSL = builder.createConvert(loc, signlessType, rhs);
     auto cmp = mlir::arith::CmpIOp::create(
-        builder, loc, translateUnsignedRelational(op.opr), lhsSL, rhsSL);
+        builder, loc, Fortran::lower::translateUnsignedRelational(op.opr),
+        lhsSL, rhsSL);
     return hlfir::EntityWithAttributes{cmp};
   }
 };
@@ -1284,7 +1240,8 @@ struct BinaryOp<Fortran::evaluate::Relational<
                                          const Op &op, hlfir::Entity lhs,
                                          hlfir::Entity rhs) {
     auto cmp = mlir::arith::CmpFOp::create(
-        builder, loc, translateFloatRelational(op.opr), lhs, rhs);
+        builder, loc, Fortran::lower::translateFloatRelational(op.opr), lhs,
+        rhs);
     return hlfir::EntityWithAttributes{cmp};
   }
 };
@@ -1298,8 +1255,9 @@ struct BinaryOp<Fortran::evaluate::Relational<
                                          fir::FirOpBuilder &builder,
                                          const Op &op, hlfir::Entity lhs,
                                          hlfir::Entity rhs) {
-    auto cmp = fir::CmpcOp::create(builder, loc,
-                                   translateFloatRelational(op.opr), lhs, rhs);
+    auto cmp = fir::CmpcOp::create(
+        builder, loc, Fortran::lower::translateFloatRelational(op.opr), lhs,
+        rhs);
     return hlfir::EntityWithAttributes{cmp};
   }
 };
diff --git a/flang/lib/Lower/ConvertType.cpp b/flang/lib/Lower/ConvertType.cpp
index d2a5a978756fd..974fcd44e3b05 100644
--- a/flang/lib/Lower/ConvertType.cpp
+++ b/flang/lib/Lower/ConvertType.cpp
@@ -671,3 +671,69 @@ void Fortran::lower::ComponentReverseIterator::setCurrentType(
 using namespace Fortran::evaluate;
 using namespace Fortran::common;
 FOR_EACH_SPECIFIC_TYPE(template class Fortran::lower::TypeBuilder, )
+
+/// Convert parser's INTEGER relational operators to MLIR.
+mlir::arith::CmpIPredicate
+Fortran::lower::translateSignedRelational(RelationalOperator rop) {
+  switch (rop) {
+  case RelationalOperator::LT:
+    return mlir::arith::CmpIPredicate::slt;
+  case RelationalOperator::LE:
+    return mlir::arith::CmpIPredicate::sle;
+  case RelationalOperator::EQ:
+    return mlir::arith::CmpIPredicate::eq;
+  case RelationalOperator::NE:
+    return mlir::arith::CmpIPredicate::ne;
+  case RelationalOperator::GT:
+    return mlir::arith::CmpIPredicate::sgt;
+  case RelationalOperator::GE:
+    return mlir::arith::CmpIPredicate::sge;
+  }
+  llvm_unreachable("unhandled INTEGER relational operator");
+}
+
+mlir::arith::CmpIPredicate
+Fortran::lower::translateUnsignedRelational(RelationalOperator rop) {
+  switch (rop) {
+  case RelationalOperator::LT:
+    return mlir::arith::CmpIPredicate::ult;
+  case RelationalOperator::LE:
+    return mlir::arith::CmpIPredicate::ule;
+  case RelationalOperator::EQ:
+    return mlir::arith::CmpIPredicate::eq;
+  case RelationalOperator::NE:
+    return mlir::arith::CmpIPredicate::ne;
+  case RelationalOperator::GT:
+    return mlir::arith::CmpIPredicate::ugt;
+  case RelationalOperator::GE:
+    return mlir::arith::CmpIPredicate::uge;
+  }
+  llvm_unreachable("unhandled UNSIGNED relational operator");
+}
+
+/// Convert parser's REAL relational operators to MLIR.
+/// The choice of order (O prefix) vs unorder (U prefix) follows Fortran 2018
+/// requirements in the IEEE context (table 17.1 of F2018). This choice is
+/// also applied in other contexts because it is easier and in line with
+/// other Fortran compilers.
+/// FIXME: The signaling/quiet aspect of the table 17.1 requirement is not
+/// fully enforced. FIR and LLVM `fcmp` instructions do not give any guarantee
+/// whether the comparison will signal or not in case of quiet NaN argument.
+mlir::arith::CmpFPredicate
+Fortran::lower::translateFloatRelational(RelationalOperator rop) {
+  switch (rop) {
+  case RelationalOperator::LT:
+    return mlir::arith::CmpFPredicate::OLT;
+  case RelationalOperator::LE:
+    return mlir::arith::CmpFPredicate::OLE;
+  case RelationalOperator::EQ:
+    return mlir::arith::CmpFPredicate::OEQ;
+  case RelationalOperator::NE:
+    return mlir::arith::CmpFPredicate::UNE;
+  case RelationalOperator::GT:
+    return mlir::arith::CmpFPredicate::OGT;
+  case RelationalOperator::GE:
+    return mlir::arith::CmpFPredicate::OGE;
+  }
+  llvm_unreachable("unhandled REAL relational operator");
+}
diff --git a/flang/lib/Lower/OpenMP/Atomic.cpp b/flang/lib/Lower/OpenMP/Atomic.cpp
index f31de82fc2a5f..cbca2fc52b93f 100644
--- a/flang/lib/Lower/OpenMP/Atomic.cpp
+++ b/flang/lib/Lower/OpenMP/Atomic.cpp
@@ -13,6 +13,7 @@
 #include "flang/Evaluate/traverse.h"
 #include "flang/Evaluate/type.h"
 #include "flang/Lower/AbstractConverter.h"
+#include "flang/Lower/ConvertType.h"
 #include "flang/Lower/OpenMP/Clauses.h"
 #include "flang/Lower/PFTBuilder.h"
 #include "flang/Lower/StatementContext.h"
@@ -485,6 +486,25 @@ genAtomicOperation(lower::AbstractConverter &converter,
   }
 }
 
+/// Reverse a relational operator as if the operands were swapped.
+/// e.g. LT becomes GT, LE becomes GE. Symmetric operators (EQ, NE)
+/// are returned unchanged.
+static common::RelationalOperator reverseRelOp(common::RelationalOperator op) {
+  using RO = common::RelationalOperator;
+  switch (op) {
+  case RO::LT:
+    return RO::GT;
+  case RO::LE:
+    return RO::GE;
+  case RO::GT:
+    return RO::LT;
+  case RO::GE:
+    return RO::LE;
+  default:
+    return op;
+  }
+}
+
 void Fortran::lower::omp::lowerAtomic(
     AbstractConverter &converter, SymMap &symTable,
     semantics::SemanticsContext &semaCtx, pft::Evaluation &eval,
@@ -521,8 +541,128 @@ void Fortran::lower::omp::lowerAtomic(
     memOrder = makeValidForAction(memOrder, action0, action1, version);
 
   if (auto *cond = get(analysis.cond)) {
-    (void)cond;
-    TODO(loc, "OpenMP ATOMIC COMPARE");
+    // atomic compare: if (x == e) x = d
+    // e : expecteVal
+    // d : desiredVal
+
+    // Check for compound clauses (fail, capture, weak) that are not yet
+    // supported with atomic compare.
+    if (llvm::any_of(clauses, [](const omp::Clause &clause) {
+          return clause.id == llvm::omp::Clause::OMPC_fail ||
+                 clause.id == llvm::omp::Clause::OMPC_capture ||
+                 clause.id == llvm::omp::Clause::OMPC_weak;
+        })) {
+      TODO(loc, "Compound clauses of OpenMP ATOMIC COMPARE");
+    }
+
+    common::RelationalOperator relOpr = common::RelationalOperator::EQ;
+    std::optional<semantics::SomeExpr> expectedExprStorage;
+    bool isUnsigned = false;
+
+    if (const auto *rel =
+            evaluate::UnwrapExpr<evaluate::Relational<evaluate::SomeType>>(
+                *cond)) {
+      std::visit(
+          [&](const auto &relImpl) {
+            relOpr = relImpl.opr;
+            using Operand = typename std::decay_t<decltype(relImpl)>::Operand;
+            isUnsigned = Operand::category == common::TypeCategory::Unsigned;
+            auto leftExpr = evaluate::AsGenericExpr(
+                evaluate::Expr<Operand>{relImpl.left()});
+            auto rightExpr = evaluate::AsGenericExpr(
+                evaluate::Expr<Operand>{relImpl.right()});
+            if (evaluate::IsSameOrConvertOf(rightExpr, atom)) {
+              // e.g. e == x  (atom is on the right)
+              // left operand is expected value (e)
+              // reverse the operator so that the comparison becomes
+              // x <reversed-op> e.
+              expectedExprStorage = std::move(leftExpr);
+              relOpr = reverseRelOp(relOpr);
+            } else {
+              // Form: x == e  (atom is on the left, or default)
+              expectedExprStorage = std::move(rightExpr);
+            }
+          },
+          rel->u);
+    }
+    if (!expectedExprStorage) {
+      mlir::emitError(loc, "internal error: atomic compare condition is not a "
+                           "recognized relational expression");
+      return;
+    }
+
+    mlir::Type elemTypeOfX = fir::unwrapRefType(atomAddr.getType());
+    mlir::Value expectedVal = fir::getBase(
+        converter.genExprValue(*expectedExprStorage, stmtCtx, &loc));
+    if (expectedVal.getType() != elemTypeOfX) {
+      expectedVal = builder.createConvert(loc, elemTypeOfX, expectedVal);
+    }
+
+    mlir::UnitAttr weakAttr = nullptr;
+    mlir::Operation *atomicOp = mlir::omp::AtomicCompareOp::create(
+        builder, loc, atomAddr, weakAttr, hint,
+        makeMemOrderAttr(converter, memOrder));
+    mlir::Block *block = builder.createBlock(&atomicOp->getRegion(0));
+    mlir::Value blockArg = block->addArgument(elemTypeOfX, loc);
+    builder.setInsertionPointToEnd(block);
+
+    // Generate comparison: e.g. x == e
+    mlir::Value cmpResult;
+    if (mlir::isa<mlir::IntegerType>(elemTypeOfX)) {
+      auto pred = isUnsigned ? lower::translateUnsignedRelational(relOpr)
+                             : lower::translateSignedRelational(relOpr);
+      cmpResult = mlir::arith::CmpIOp::create(builder, loc, pred, blockArg,
+                                              expectedVal);
+    } else if (mlir::isa<mlir::FloatType>(elemTypeOfX)) {
+      auto pred = lower::translateFloatRelational(relOpr);
+      cmpResult = mlir::arith::CmpFOp::create(builder, loc, pred, blockArg,
+                                              expectedVal);
+    } else if (fir::isa_complex(elemTypeOfX)) {
+      auto pred = lower::translateFloatRelational(relOpr);
+      cmpResult =
+          fir::CmpcOp::create(builder, loc, pred, blockArg, expectedVal);
+    } else {
+      mlir::emitError(loc, "unsupported type for atomic compare");
+      return;
+    }
+
+    // Check for presence of Assignment (x = d) and wether it is being invoked
+    // only for IfTrue condition.
+
+    // writeActionCond is a bitmask combining the following flags:
+    //  1) the action type (Read/Write/Update)
+    //  2) condition (IfTrue/IfFalse)
+    int writeActionCond = 0;
+    const evaluate::Assignment *writeAssign = nullptr;
+    if (analysis.op0.what & analysis.Write) {
+      writeAssign = get(analysis.op0.assign);
+      writeActionCond = analysis.op0.what;
+    }
+    if (!writeAssign && (analysis.op1.what & analysis.Write)) {
+      writeAssign = get(analysis.op1.assign);
+      writeActionCond = analysis.op1.what;
+    }
+    if (!writeAssign) {
+      mlir::emitError(loc,
+                      "internal error: atomic compare has no write assignment");
+      return;
+    }
+    assert((writeActionCond & analysis.IfTrue) &&
+           "atomic compare write should be conditioned on IfTrue");
+
+    // Generate new/desired value of x e.g. x = d
+    mlir::Value desiredVal =
+        fir::getBase(converter.genExprValue(writeAssign->rhs, stmtCtx, &loc));
+    if (desiredVal.getType() != elemTypeOfX)
+      desiredVal = builder.createConvert(loc, elemTypeOfX, desiredVal);
+    mlir::Value newVal = mlir::arith::SelectOp::create(builder, loc, cmpResult,
+                                                       desiredVal, blockArg);
+
+    // Generate omp.yield
+    mlir::omp::YieldOp::create(builder, loc, newVal);
+    builder.setInsertionPointAfter(atomicOp);
+
+    // END omp atomic compare
   } else {
     mlir::Operation *captureOp = nullptr;
     fir::FirOpBuilder::InsertPoint preAt = builder.saveInsertionPoint();
diff --git a/flang/test/Integration/OpenMP/atomic-compare.f90 b/flang/test/Integration/OpenMP/atomic-compare.f90
new file mode 100644
index 0000000000000..247f79d243c83
--- /dev/null
+++ b/flang/test/Integration/OpenMP/atomic-compare.f90
@@ -0,0 +1,250 @@
+!===----------------------------------------------------------------------===!
+! This directory can be used to add Integration tests involving multiple
+! stages of the compiler (for eg. from Fortran to LLVM IR). It should not
+! contain executable tests. We should only add tests here sparingly and only
+! if there is no other way to test. Repeat this message in each test that is
+! added to this directory and sub-directories.
+!===----------------------------------------------------------------------===!
+
+!REQUIRES: x86-registered-target
+!RUN: %flang_fc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fopenmp -fopenmp-version=51 %s -o - | FileCheck %s
+
+! Int "==" → cmpxchg, default (monotonic) ordering
+!CHECK-LABEL: define void @atomic_compare_integer_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]])
+!CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]], align 4
+!CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]], align 4
+!CHECK: cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
+subroutine atomic_compare_integer(x, e, d)
+  integer :: x, e, d
+  !$omp atomic compare
+  if (x == e) x = d
+end
+
+! seq_cst ordering → cmpxchg seq_cst + flush
+!CHECK-LABEL: define void @atomic_compare_seq_cst_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]])
+!CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]], align 4
+!CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]], align 4
+!CHECK: cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] seq_cst seq_cst
+!CHECK: call void @__kmpc_flush(
+subroutine atomic_compare_seq_cst(x, e, d)
+  integer :: x, e, d
+  !$omp atomic compare seq_cst
+  if (x == e) x = d
+end
+
+! acquire ordering → cmpxchg acquire
+!CHECK-LABEL: define void @atomic_compare_acquire_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]])
+!CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]], align 4
+!CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]], align 4
+!CHECK: cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] acquire acquire
+subroutine atomic_compare_acquire(x, e, d)
+  integer :: x, e, d
+  !$omp atomic compare acquire
+  if (x == e) x = d
+end
+
+! release ordering → cmpxchg release + flush
+!CHECK-LABEL: define void @atomic_compare_release_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]])
+!CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]], align 4
+!CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]], align 4
+!CHECK: cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] release monotonic
+!CHECK: call void @__kmpc_flush(
+subroutine atomic_compare_release(x, e, d)
+  integer :: x, e, d
+  !$omp atomic compare release
+  if (x == e) x = d
+end
+
+! relaxed ordering → cmpxchg monotonic
+!CHECK-LABEL: define void @atomic_compare_relaxed_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]])
+!CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]], align 4
+!CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]], align 4
+!CHECK: cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
+subroutine atomic_compare_relaxed(x, e, d)
+  integer :: x, e, d
+  !$omp atomic compare relaxed
+  if (x == e) x = d
+end
+
+! Less-than comparison → atomicrmw max (signed)
+!CHECK-LABEL: define void @atomic_compare_lt_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]])
+!CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]], align 4
+!CHECK: atomicrmw max ptr %[[X]], i32 %[[EVAL]] monotonic
+subroutine atomic_compare_lt(x, e)
+  integer :: x, e
+  !$omp atomic compare
+  if (x < e) x = e
+end
+
+! Less-than with seq_cst → atomicrmw max seq_cst + flush (signed)
+!CHECK-LABEL: define void @atomic_compare_lt_seq_cst_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]])
+!CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]], align 4
+!CHECK: atomicrmw max ptr %[[X]], i32 %[[EVAL]] seq_cst
+!CHECK: call void @__kmpc_flush(
+subroutine atomic_compare_lt_seq_cst(x, e)
+  integer :: x, e
+  !$omp atomic compare seq_cst
+  if (x < e) x = e
+end
+
+! Less-than with acquire → atomicrmw max acquire (signed)
+!CHECK-LABEL: define void @atomic_compare_lt_acquire_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]])
+!CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]], align 4
+!CHECK: atomicrmw max ptr %[[X]], i32 %[[EVAL]] acquire
+subroutine atomic_compare_lt_acquire(x, e)
+  integer :: x, e
+  !$omp atomic compare acquire
+  if (x < e) x = e
+end
+
+! Greater-than comparison → atomicrmw min (signed)
+!CHECK-LABEL: define void @atomic_compare_gt_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]])
+!CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]], align 4
+!CHECK: atomicrmw min ptr %[[X]], i32 %[[EVAL]] monotonic
+subroutine atomic_compare_gt(x, e)
+  integer :: x, e
+  !$omp atomic compare
+  if (x > e) x = e
+end
+
+! Real "==" → NaN guard + ±0.0 guard + cmpxchg
+! IEEE 754 special cases for cmpxchg (which is bitwise):
+!   1. NaN != NaN but identical NaN bit patterns would match → skip cmpxchg
+!   2. -0.0 == +0.0 but different bit patterns → use loaded bit-pattern
+!CHECK-LABEL: define void @atomic_compare_real_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]])
+!CHECK: %[[EVAL:.*]] = load float, ptr %[[E]], align 4
+!CHECK: %[[DVAL:.*]] = load float, ptr %[[D]], align 4
+!CHECK: %[[EBC:.*]] = bitcast float %[[EVAL]] to i32
+!CHECK: %[[DBC:.*]] = bitcast float %[[DVAL]] to i32
+!CHECK: load atomic i32, ptr %[[X]] monotonic
+!CHECK: %[[EISNAN:.*]] = fcmp uno float %[[EVAL]], %[[EVAL]]
+!CHECK: %[[XISNAN:.*]] = fcmp uno float %{{.*}}, %{{.*}}
+!CHECK: %[[EITHERNAN:.*]] = or i1 %[[EISNAN]], %[[XISNAN]]
+!CHECK: br i1 %[[EITHERNAN]], label %[[NANBB:[^,]+]], label %[[NOTNANBB:[^,]+]]
+!CHECK: [[NANBB]]:
+!CHECK-NEXT: br label %[[EXIT:[^ ]+]]
+!CHECK: [[NOTNANBB]]:
+!CHECK: %[[XISZERO:.*]] = fcmp oeq float %{{.*}}, 0.000000e+00
+!CHECK: %[[EISZERO:.*]] = fcmp oeq float %[[EVAL]], 0.000000e+00
+!CHECK: %[[BOTH:.*]] = and i1 %[[XISZERO]], %[[EISZERO]]
+!CHECK: br i1 %[[BOTH]], label %[[ZERO:[^,]+]], label %[[NORMAL:[^,]+]]
+!CHECK: [[ZERO]]:
+!CHECK: cmpxchg ptr %[[X]], i32 %{{.*}}, i32 %[[DBC]] monotonic monotonic
+!CHECK: br label %[[EXIT]]
+!CHECK: [[NORMAL]]:
+!CHECK: cmpxchg ptr %[[X]], i32 %[[EBC]], i32 %[[DBC]] monotonic monotonic
+!CHECK: br label %[[EXIT]]
+subroutine atomic_compare_real(x, e, d)
+  real :: x, e, d
+  !$omp atomic compare
+  if (x == e) x = d
+end
+
+! Complex(4) equality → type-punned i64 cmpxchg with consistent alignment
+!CHECK-LABEL: define void @atomic_compare_complex4_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]])
+!CHECK: %[[EALLOCA:.*]] = alloca { float, float }, align [[ALIGN:[0-9]+]]
+!CHECK: %[[DALLOCA:.*]] = alloca { float, float }, align [[ALIGN]]
+!CHECK: store { float, float } %{{.*}}, ptr %[[EALLOCA]], align [[ALIGN]]
+!CHECK: %[[EINT:.*]] = load i64, ptr %[[EALLOCA]], align [[ALIGN]]
+!CHECK: store { float, float } %{{.*}}, ptr %[[DALLOCA]], align [[ALIGN]]
+!CHECK: %[[DINT:.*]] = load i64, ptr %[[DALLOCA]], align [[ALIGN]]
+!CHECK: cmpxchg ptr %[[X]], i64 %[[EINT]], i64 %[[DINT]] monotonic monotonic, align [[ALIGN]]
+subroutine atomic_compare_complex4(x, e, d)
+  complex :: x, e, d
+  !$omp atomic compare
+  if (x == e) x = d
+end
+
+! Complex(8) equality → type-punned i128 cmpxchg with consistent alignment
+!CHECK-LABEL: define void @atomic_compare_complex8_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]])
+!CHECK: %[[EALLOCA:.*]] = alloca { double, double }, align [[ALIGN:[0-9]+]]
+!CHECK: %[[DALLOCA:.*]] = alloca { double, double }, align [[ALIGN]]
+!CHECK: store { double, double } %{{.*}}, ptr %[[EALLOCA]], align [[ALIGN]]
+!CHECK: %[[EINT:.*]] = load i128, ptr %[[EALLOCA]], align [[ALIGN]]
+!CHECK: store { double, double } %{{.*}}, ptr %[[DALLOCA]], align [[ALIGN]]
+!CHECK: %[[DINT:.*]] = load i128, ptr %[[DALLOCA]], align [[ALIGN]]
+!CHECK: cmpxchg ptr %[[X]], i128 %[[EINT]], i128 %[[DINT]] monotonic monotonic, align [[ALIGN]]
+subroutine atomic_compare_complex8(x, e, d)
+  complex(8) :: x, e, d
+  !$omp atomic compare
+  if (x == e) x = d
+end
+
+! Complex(4) equality with seq_cst → type-punned i64 cmpxchg seq_cst + flush
+!CHECK-LABEL: define void @atomic_compare_complex4_seq_cst_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]])
+!CHECK: cmpxchg ptr %[[X]], i64 %{{.*}}, i64 %{{.*}} seq_cst seq_cst
+!CHECK: call void @__kmpc_flush(
+subroutine atomic_compare_complex4_seq_cst(x, e, d)
+  complex :: x, e, d
+  !$omp atomic compare seq_cst
+  if (x == e) x = d
+end
+
+! CHECK-LABEL: define void @omp_atomic_compare_ptr_
+! CHECK-SAME: (ptr %[[X_DESC_ARG:.*]], ptr %[[E_DESC_ARG:.*]], ptr %[[D_DESC_ARG:.*]])
+
+! CHECK: %[[D_COPY:.*]] = alloca { ptr, {{.*}} }
+! CHECK: %[[E_COPY:.*]] = alloca { ptr, {{.*}} }
+! CHECK: %[[X_COPY:.*]] = alloca { ptr, {{.*}} }
+! CHECK: %[[D_TMP_DESC:.*]] = alloca { ptr, {{.*}} }
+! CHECK: %[[E_TMP_DESC:.*]] = alloca { ptr, {{.*}} }
+! CHECK: %[[X_TMP_DESC:.*]] = alloca { ptr, {{.*}} }
+! CHECK: %[[TX:.*]] = alloca i32
+! CHECK: %[[TE:.*]] = alloca i32
+! CHECK: %[[TD:.*]] = alloca i32
+
+! CHECK: %[[X_INIT:.*]] = insertvalue { ptr, {{.*}} } {{.*}}, ptr %[[TX]], 0
+! CHECK: store { ptr, {{.*}} } %[[X_INIT]], ptr %[[X_TMP_DESC]]
+! CHECK: call void @llvm.memcpy.{{.*}}(ptr{{.*}} %[[X_DESC_ARG]], ptr{{.*}} %[[X_TMP_DESC]], {{.*}})
+
+! CHECK: %[[E_INIT:.*]] = insertvalue { ptr, {{.*}} } {{.*}}, ptr %[[TE]], 0
+! CHECK: store { ptr, {{.*}} } %[[E_INIT]], ptr %[[E_TMP_DESC]]
+! CHECK: call void @llvm.memcpy.{{.*}}(ptr{{.*}} %[[E_DESC_ARG]], ptr{{.*}} %[[E_TMP_DESC]], {{.*}})
+
+! CHECK: %[[D_INIT:.*]] = insertvalue { ptr, {{.*}} } {{.*}}, ptr %[[TD]], 0
+! CHECK: store { ptr, {{.*}} } %[[D_INIT]], ptr %[[D_TMP_DESC]]
+! CHECK: call void @llvm.memcpy.{{.*}}(ptr{{.*}} %[[D_DESC_ARG]], ptr{{.*}} %[[D_TMP_DESC]], {{.*}})
+
+! CHECK: call void @llvm.memcpy.{{.*}}(ptr{{.*}} %[[X_COPY]], ptr{{.*}} %[[X_DESC_ARG]], {{.*}})
+! CHECK: %[[X_FIELD:.*]] = getelementptr { ptr, {{.*}} }, ptr %[[X_COPY]], {{.*}}
+! CHECK: %[[X_ADDR:.*]] = load ptr, ptr %[[X_FIELD]]
+
+! CHECK: call void @llvm.memcpy.{{.*}}(ptr{{.*}} %[[E_COPY]], ptr{{.*}} %[[E_DESC_ARG]], {{.*}})
+! CHECK: %[[E_FIELD:.*]] = getelementptr { ptr, {{.*}} }, ptr %[[E_COPY]], {{.*}}
+! CHECK: %[[E_ADDR:.*]] = load ptr, ptr %[[E_FIELD]]
+! CHECK: %[[E_VAL:.*]] = load i32, ptr %[[E_ADDR]]
+
+! CHECK: call void @llvm.memcpy.{{.*}}(ptr{{.*}} %[[D_COPY]], ptr{{.*}} %[[D_DESC_ARG]], {{.*}})
+! CHECK: %[[D_FIELD:.*]] = getelementptr { ptr, {{.*}} }, ptr %[[D_COPY]], {{.*}}
+! CHECK: %[[D_ADDR:.*]] = load ptr, ptr %[[D_FIELD]]
+! CHECK: %[[D_VAL:.*]] = load i32, ptr %[[D_ADDR]]
+
+! CHECK: cmpxchg ptr %[[X_ADDR]], i32 %[[E_VAL]], i32 %[[D_VAL]] monotonic monotonic{{.*}}
+! CHECK: ret void
+subroutine omp_atomic_compare_ptr(x, e, d)
+  implicit none
+  integer, target :: tx, te, td
+  integer, pointer :: x, e, d
+
+  x => tx
+  e => te
+  d => td
+
+  !$omp atomic compare
+    if (x == e) x = d
+  !$omp end atomic
+
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/atomic-compare-fail.f90 b/flang/test/Lower/OpenMP/Todo/atomic-compare-fail.f90
index 6f58e0939a787..27c7b1fb1a432 100644
--- a/flang/test/Lower/OpenMP/Todo/atomic-compare-fail.f90
+++ b/flang/test/Lower/OpenMP/Todo/atomic-compare-fail.f90
@@ -1,11 +1,28 @@
 ! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -fopenmp-version=51 -o - %s 2>&1 | FileCheck %s
 
-! CHECK: not yet implemented: OpenMP ATOMIC COMPARE
+! CHECK: not yet implemented: Compound clauses of OpenMP ATOMIC COMPARE
 program p
   integer :: x
-  logical :: r
+  integer :: r
+  integer :: d
+  integer :: v
   !$omp atomic compare fail(relaxed)
   if (x .eq. 0) then
      x = 2
   end if
+  !$omp end atomic
+
+  !$omp atomic compare capture
+  v = x
+  if (x > r) then
+     x = d
+  end if
+  !$omp end atomic
+
+  !$omp atomic compare fail(relaxed)
+  if (x > r) then
+     x = d
+  end if
+  !$omp end atomic
+
 end program p
diff --git a/flang/test/Lower/OpenMP/Todo/atomic-compare.f90 b/flang/test/Lower/OpenMP/Todo/atomic-compare.f90
deleted file mode 100644
index 6729be6e5cf8b..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/atomic-compare.f90
+++ /dev/null
@@ -1,11 +0,0 @@
-! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -fopenmp-version=51 -o - %s 2>&1 | FileCheck %s
-
-! CHECK: not yet implemented: OpenMP ATOMIC COMPARE
-program p
-  integer :: x
-  logical :: r
-  !$omp atomic compare
-  if (x .eq. 0) then
-     x = 2
-  end if
-end program p
diff --git a/flang/test/Lower/OpenMP/atomic-compare.f90 b/flang/test/Lower/OpenMP/atomic-compare.f90
new file mode 100644
index 0000000000000..62388d11f491c
--- /dev/null
+++ b/flang/test/Lower/OpenMP/atomic-compare.f90
@@ -0,0 +1,142 @@
+! This test checks lowering of atomic compare constructs.
+! RUN: bbc %openmp_flags -fopenmp-version=51 -emit-hlfir %s -o - | FileCheck %s
+! RUN: %flang_fc1 -emit-hlfir %openmp_flags -fopenmp-version=51 %s -o - | FileCheck %s
+
+! CHECK-LABEL: func.func @_QPatomic_compare_int_eq(
+! CHECK-SAME:    %[[X:.*]]: !fir.ref<i32> {fir.bindc_name = "x"},
+! CHECK-SAME:    %[[E:.*]]: !fir.ref<i32> {fir.bindc_name = "e"},
+! CHECK-SAME:    %[[D:.*]]: !fir.ref<i32> {fir.bindc_name = "d"})
+! CHECK:         %[[D_DECL:.*]]:2 = hlfir.declare %[[D]] {{.*}}
+! CHECK:         %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK:         %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK:         %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK:         omp.atomic.compare memory_order(relaxed) %[[X_DECL]]#0 : !fir.ref<i32> {
+! CHECK:         ^bb0(%[[XVAL:.*]]: i32):
+! CHECK:           %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %[[EVAL]] : i32
+! CHECK:           %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
+! CHECK:           %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : i32
+! CHECK:           omp.yield(%[[SEL]] : i32)
+! CHECK:         }
+subroutine atomic_compare_int_eq(x, e, d)
+  integer :: x, e, d
+  !$omp atomic compare
+  if (x .eq. e) x = d
+end
+
+! CHECK-LABEL: func.func @_QPatomic_compare_float_eq(
+! CHECK-SAME:    %[[X:.*]]: !fir.ref<f32> {fir.bindc_name = "x"},
+! CHECK-SAME:    %[[E:.*]]: !fir.ref<f32> {fir.bindc_name = "e"},
+! CHECK-SAME:    %[[D:.*]]: !fir.ref<f32> {fir.bindc_name = "d"})
+! CHECK:         %[[D_DECL:.*]]:2 = hlfir.declare %[[D]] {{.*}}
+! CHECK:         %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK:         %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK:         %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<f32>
+! CHECK:         omp.atomic.compare memory_order(relaxed) %[[X_DECL]]#0 : !fir.ref<f32> {
+! CHECK:         ^bb0(%[[XVAL:.*]]: f32):
+! CHECK:           %[[CMP:.*]] = arith.cmpf oeq, %[[XVAL]], %[[EVAL]] fastmath<contract> : f32
+! CHECK:           %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<f32>
+! CHECK:           %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : f32
+! CHECK:           omp.yield(%[[SEL]] : f32)
+! CHECK:         }
+subroutine atomic_compare_float_eq(x, e, d)
+  real :: x, e, d
+  !$omp atomic compare
+  if (x .eq. e) x = d
+end
+
+! CHECK-LABEL: func.func @_QPatomic_compare_complex_eq(
+! CHECK-SAME:    %[[X:.*]]: !fir.ref<complex<f32>> {fir.bindc_name = "x"},
+! CHECK-SAME:    %[[E:.*]]: !fir.ref<complex<f32>> {fir.bindc_name = "e"},
+! CHECK-SAME:    %[[D:.*]]: !fir.ref<complex<f32>> {fir.bindc_name = "d"})
+! CHECK:         %[[D_DECL:.*]]:2 = hlfir.declare %[[D]] {{.*}}
+! CHECK:         %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK:         %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK:         %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<complex<f32>>
+! CHECK:         omp.atomic.compare memory_order(relaxed) %[[X_DECL]]#0 : !fir.ref<complex<f32>> {
+! CHECK:         ^bb0(%[[XVAL:.*]]: complex<f32>):
+! CHECK:           %[[CMP:.*]] = fir.cmpc "oeq", %[[XVAL]], %[[EVAL]] {fastmath = #arith.fastmath<contract>} : complex<f32>
+! CHECK:           %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<complex<f32>>
+! CHECK:           %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : complex<f32>
+! CHECK:           omp.yield(%[[SEL]] : complex<f32>)
+! CHECK:         }
+subroutine atomic_compare_complex_eq(x, e, d)
+  complex :: x, e, d
+  !$omp atomic compare
+  if (x .eq. e) x = d
+end
+
+! CHECK-LABEL: func.func @_QPatomic_compare_int_lt(
+! CHECK-SAME:    %[[X:.*]]: !fir.ref<i32> {fir.bindc_name = "x"},
+! CHECK-SAME:    %[[E:.*]]: !fir.ref<i32> {fir.bindc_name = "e"})
+! CHECK:         %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK:         %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK:         %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK:         omp.atomic.compare memory_order(relaxed) %[[X_DECL]]#0 : !fir.ref<i32> {
+! CHECK:         ^bb0(%[[XVAL:.*]]: i32):
+! CHECK:           %[[CMP:.*]] = arith.cmpi slt, %[[XVAL]], %[[EVAL]] : i32
+! CHECK:           %[[EVAL2:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK:           %[[SEL:.*]] = arith.select %[[CMP]], %[[EVAL2]], %[[XVAL]] : i32
+! CHECK:           omp.yield(%[[SEL]] : i32)
+! CHECK:         }
+subroutine atomic_compare_int_lt(x, e)
+  integer :: x, e
+  !$omp atomic compare
+  if (x .lt. e) x = e
+end
+
+! CHECK-LABEL: func.func @_QPatomic_compare_int_gt(
+! CHECK-SAME:    %[[X:.*]]: !fir.ref<i32> {fir.bindc_name = "x"},
+! CHECK-SAME:    %[[E:.*]]: !fir.ref<i32> {fir.bindc_name = "e"})
+! CHECK:         %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK:         %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK:         %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK:         omp.atomic.compare memory_order(relaxed) %[[X_DECL]]#0 : !fir.ref<i32> {
+! CHECK:         ^bb0(%[[XVAL:.*]]: i32):
+! CHECK:           %[[CMP:.*]] = arith.cmpi sgt, %[[XVAL]], %[[EVAL]] : i32
+! CHECK:           %[[EVAL2:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK:           %[[SEL:.*]] = arith.select %[[CMP]], %[[EVAL2]], %[[XVAL]] : i32
+! CHECK:           omp.yield(%[[SEL]] : i32)
+! CHECK:         }
+subroutine atomic_compare_int_gt(x, e)
+  integer :: x, e
+  !$omp atomic compare
+  if (x .gt. e) x = e
+end
+
+! CHECK-LABEL: func.func @_QPatomic_compare_float_lt(
+! CHECK-SAME:    %[[X:.*]]: !fir.ref<f32> {fir.bindc_name = "x"},
+! CHECK-SAME:    %[[E:.*]]: !fir.ref<f32> {fir.bindc_name = "e"})
+! CHECK:         %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK:         %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK:         %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<f32>
+! CHECK:         omp.atomic.compare memory_order(relaxed) %[[X_DECL]]#0 : !fir.ref<f32> {
+! CHECK:         ^bb0(%[[XVAL:.*]]: f32):
+! CHECK:           %[[CMP:.*]] = arith.cmpf olt, %[[XVAL]], %[[EVAL]] fastmath<contract> : f32
+! CHECK:           %[[EVAL2:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<f32>
+! CHECK:           %[[SEL:.*]] = arith.select %[[CMP]], %[[EVAL2]], %[[XVAL]] : f32
+! CHECK:           omp.yield(%[[SEL]] : f32)
+! CHECK:         }
+subroutine atomic_compare_float_lt(x, e)
+  real :: x, e
+  !$omp atomic compare
+  if (x .lt. e) x = e
+end
+
+! CHECK-LABEL: func.func @_QPatomic_compare_float_gt(
+! CHECK-SAME:    %[[X:.*]]: !fir.ref<f32> {fir.bindc_name = "x"},
+! CHECK-SAME:    %[[E:.*]]: !fir.ref<f32> {fir.bindc_name = "e"})
+! CHECK:         %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK:         %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK:         %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<f32>
+! CHECK:         omp.atomic.compare memory_order(relaxed) %[[X_DECL]]#0 : !fir.ref<f32> {
+! CHECK:         ^bb0(%[[XVAL:.*]]: f32):
+! CHECK:           %[[CMP:.*]] = arith.cmpf ogt, %[[XVAL]], %[[EVAL]] fastmath<contract> : f32
+! CHECK:           %[[EVAL2:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<f32>
+! CHECK:           %[[SEL:.*]] = arith.select %[[CMP]], %[[EVAL2]], %[[XVAL]] : f32
+! CHECK:           omp.yield(%[[SEL]] : f32)
+! CHECK:         }
+subroutine atomic_compare_float_gt(x, e)
+  real :: x, e
+  !$omp atomic compare
+  if (x .gt. e) x = e
+end
diff --git a/flang/test/Semantics/OpenMP/atomic-compare.f90 b/flang/test/Semantics/OpenMP/atomic-compare.f90
index 6a4fbe7ffe81b..0e53729c2f02a 100644
--- a/flang/test/Semantics/OpenMP/atomic-compare.f90
+++ b/flang/test/Semantics/OpenMP/atomic-compare.f90
@@ -8,6 +8,7 @@
 
 
   real a, b, c
+  logical :: r, s
   a = 1.0
   b = 2.0
   c = 3.0
@@ -43,6 +44,20 @@
   if (c .eq. a) a = b
   !$omp end atomic
 
+  ! Less-than comparison.
+  !$omp atomic compare
+  if (b .lt. a) b = c
+
+  ! Greater-than comparison.
+  !$omp atomic compare
+  if (b .gt. a) b = c
+
+  ! Two-statement form: r = cond; if (r) update.
+  !$omp atomic compare
+  r = b .eq. a
+  if (r) b = c
+  !$omp end atomic
+
   ! Check for error conditions:
   !ERROR: At most one SEQ_CST clause can appear on the ATOMIC directive
   !$omp atomic seq_cst seq_cst compare
@@ -79,5 +94,61 @@
   if (c .eq. a) a = b
   !$omp end atomic
 
+  ! The /= operator is not valid for atomic compare.
+  !$omp atomic compare
+  !ERROR: The /= operator is not a valid condition for ATOMIC operation
+  if (b .ne. a) b = c
+
+  ! The <= operator is not valid for atomic compare.
+  !$omp atomic compare
+  !ERROR: The <= operator is not a valid condition for ATOMIC operation
+  if (b .le. a) b = c
+
+  ! The >= operator is not valid for atomic compare.
+  !$omp atomic compare
+  !ERROR: The >= operator is not a valid condition for ATOMIC operation
+  if (b .ge. a) b = c
+
+  ! ELSE branch is not allowed.
+  !$omp atomic compare
+  if (b .eq. a) then
+    b = c
+  else
+  !ERROR: In ATOMIC UPDATE COMPARE the update statement should not have an ELSE branch
+    a = b
+  end if
+
+  ! Not a conditional statement.
+  !ERROR: In ATOMIC UPDATE COMPARE the update statement should be a conditional statement
+  !$omp atomic compare
+  b = c
+
+  ! Too many statements.
+  !ERROR: ATOMIC UPDATE COMPARE operation should contain one or two statements
+  !$omp atomic compare
+  r = b .eq. a
+  if (r) b = c
+  a = b
+  !$omp end atomic
+
+  ! Two-statement form with wrong condition variable.
+  !$omp atomic compare
+  r = b .eq. a
+  !ERROR: In ATOMIC UPDATE COMPARE the conditional statement must use r as the condition
+  if (s) b = c
+  !$omp end atomic
+
+  ! Neither argument of the condition is the target of the assignment.
+  !$omp atomic compare
+  !ERROR: An argument of the == operator should be the target of the assignment
+  if (a .eq. c) b = c
+
+  ! First statement is not a comparison, condition uses wrong variable.
+  !$omp atomic compare
+  b = c
+  !ERROR: In ATOMIC UPDATE COMPARE the conditional statement must use b as the condition
+  if (r) b = c
+  !$omp end atomic
+
   !$omp end parallel
 end
diff --git a/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h b/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
index 396b95b701d08..2b790458f3c32 100644
--- a/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
+++ b/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
@@ -4009,6 +4009,18 @@ class OpenMPIRBuilder {
   ///                     the case the comparison is '=='.
   ///
   /// \return Insertion point after generated atomic capture IR.
+  /// Whether to emit special handling for IEEE 754 -0.0 == +0.0 in
+  /// atomic compare operations on floating-point types.
+  bool HandleFPNegZero = false;
+
+  /// Set whether atomic compare should handle -0.0/+0.0 equivalence.
+  /// Returns the previous value so callers can save and restore it.
+  bool setHandleFPNegZero(bool FPNegZero) {
+    bool Old = HandleFPNegZero;
+    HandleFPNegZero = FPNegZero;
+    return Old;
+  }
+
   LLVM_ABI InsertPointTy
   createAtomicCompare(const LocationDescription &Loc, AtomicOpValue &X,
                       AtomicOpValue &V, AtomicOpValue &R, Value *E, Value *D,
diff --git a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
index 8bbdc3cc8c913..09c4df10a5d7b 100644
--- a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
+++ b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
@@ -11156,80 +11156,251 @@ OpenMPIRBuilder::InsertPointTy OpenMPIRBuilder::createAtomicCompare(
   bool IsInteger = E->getType()->isIntegerTy();
 
   if (Op == OMPAtomicCompareOp::EQ) {
-    AtomicCmpXchgInst *Result = nullptr;
-    if (!IsInteger) {
+    // OldValue and SuccessOrFail are set below and used in the shared V.Var /
+    // R.Var handling.
+    Value *OldValue = nullptr;
+    Value *SuccessOrFail = nullptr;
+
+    if (!IsInteger && HandleFPNegZero) {
+      // IEEE 754 special cases for cmpxchg (which is bitwise):
+      //   1. -0.0 == +0.0 but they have different bit patterns.
+      //   2. NaN != NaN but identical NaN bit patterns would match.
+      //
+      //   CurBB:
+      //     %e_int      = bitcast E to intN
+      //     %d_int      = bitcast D to intN
+      //     %x_curr     = load atomic intN, X
+      //     %x_fp       = bitcast %x_curr to FP
+      //     %e_is_nan   = fcmp uno E, E
+      //     %x_is_nan   = fcmp uno %x_fp, %x_fp
+      //     %either_nan = or %e_is_nan, %x_is_nan
+      //     br %either_nan, NaNBB, NotNaNBB
+      //   NaNBB:            ; NaN == anything is always false
+      //     br ExitBB
+      //   NotNaNBB:
+      //     %x_is_zero  = fcmp oeq %x_fp, 0.0
+      //     %e_is_zero  = fcmp oeq E, 0.0
+      //     %both_zero  = and %x_is_zero, %e_is_zero
+      //     br %both_zero, ZeroBB, NormalBB
+      //   ZeroBB:           ; both ±0.0  →  x = d
+      //     cmpxchg X, %x_curr, %d_int
+      //     br ExitBB
+      //   NormalBB:         ; original path
+      //     cmpxchg X, %e_int, %d_int
+      //     br ExitBB
+      //   ExitBB:
+      //     phi merge
       IntegerType *IntCastTy =
           IntegerType::get(M.getContext(), X.ElemTy->getScalarSizeInBits());
       Value *EBCast = Builder.CreateBitCast(E, IntCastTy);
       Value *DBCast = Builder.CreateBitCast(D, IntCastTy);
-      Result = Builder.CreateAtomicCmpXchg(X.Var, EBCast, DBCast, MaybeAlign(),
-                                           AO, Failure);
-    } else {
-      Result =
-          Builder.CreateAtomicCmpXchg(X.Var, E, D, MaybeAlign(), AO, Failure);
-    }
 
-    if (V.Var) {
-      Value *OldValue = Builder.CreateExtractValue(Result, /*Idxs=*/0);
-      if (!IsInteger)
-        OldValue = Builder.CreateBitCast(OldValue, X.ElemTy);
-      assert(OldValue->getType() == V.ElemTy &&
-             "OldValue and V must be of same type");
-      if (IsPostfixUpdate) {
-        Builder.CreateStore(OldValue, V.Var, V.IsVolatile);
+      // Load X atomically.
+      LoadInst *XCurr = Builder.CreateLoad(IntCastTy, X.Var,
+                                           X.Var->getName() + ".atomic.load");
+      XCurr->setAtomic(AtomicOrdering::Monotonic);
+      Value *XFP = Builder.CreateBitCast(XCurr, X.ElemTy);
+
+      // IEEE 754: NaN != NaN, but cmpxchg would succeed if E and X have
+      // the same NaN bit pattern.  Skip cmpxchg when either is NaN.
+      Value *EIsNaN = Builder.CreateFCmpUNO(E, E, "atomic.e.isnan");
+      Value *XIsNaN = Builder.CreateFCmpUNO(XFP, XFP, "atomic.x.isnan");
+      Value *EitherNaN = Builder.CreateOr(EIsNaN, XIsNaN, "atomic.either.nan");
+
+      BasicBlock *CurBB = Builder.GetInsertBlock();
+      Function *F = CurBB->getParent();
+      Instruction *CurBBTI = CurBB->getTerminatorOrNull();
+      CurBBTI = CurBBTI ? CurBBTI : Builder.CreateUnreachable();
+      BasicBlock *ExitBB =
+          CurBB->splitBasicBlock(CurBBTI, X.Var->getName() + ".atomic.exit");
+      BasicBlock *NaNBB = BasicBlock::Create(
+          M.getContext(), X.Var->getName() + ".atomic.nan", F, ExitBB);
+      BasicBlock *NotNaNBB = BasicBlock::Create(
+          M.getContext(), X.Var->getName() + ".atomic.notnan", F, ExitBB);
+      BasicBlock *ZeroBB = BasicBlock::Create(
+          M.getContext(), X.Var->getName() + ".atomic.zero", F, ExitBB);
+      BasicBlock *NormalBB = BasicBlock::Create(
+          M.getContext(), X.Var->getName() + ".atomic.normal", F, ExitBB);
+
+      // If either E or X is NaN → NaNBB (always fails), else check for ±0.0.
+      CurBB->getTerminator()->eraseFromParent();
+      Builder.SetInsertPoint(CurBB);
+      Builder.CreateCondBr(EitherNaN, NaNBB, NotNaNBB);
+
+      // NaNBB: NaN == anything is always false; skip cmpxchg.
+      Builder.SetInsertPoint(NaNBB);
+      Builder.CreateBr(ExitBB);
+
+      // NotNaNBB: check both X and E for ±0.0.
+      Builder.SetInsertPoint(NotNaNBB);
+      Value *XIsZero =
+          Builder.CreateFCmpOEQ(XFP, ConstantFP::getZero(X.ElemTy),
+                                X.Var->getName() + ".atomic.xiszero");
+      Value *EIsZero = Builder.CreateFCmpOEQ(E, ConstantFP::getZero(X.ElemTy),
+                                             "atomic.e.iszero");
+      Value *BothZero = Builder.CreateAnd(XIsZero, EIsZero, "atomic.both.zero");
+      Builder.CreateCondBr(BothZero, ZeroBB, NormalBB);
+
+      // ZeroBB: cmpxchg with X's loaded bit-pattern.
+      Builder.SetInsertPoint(ZeroBB);
+      AtomicCmpXchgInst *ResZero = Builder.CreateAtomicCmpXchg(
+          X.Var, XCurr, DBCast, MaybeAlign(), AO, Failure);
+      Value *OldZero = Builder.CreateExtractValue(ResZero, /*Idxs=*/0);
+      Value *OkZero = Builder.CreateExtractValue(ResZero, /*Idxs=*/1);
+      Builder.CreateBr(ExitBB);
+
+      // NormalBB: original bitwise cmpxchg.
+      Builder.SetInsertPoint(NormalBB);
+      AtomicCmpXchgInst *ResNormal = Builder.CreateAtomicCmpXchg(
+          X.Var, EBCast, DBCast, MaybeAlign(), AO, Failure);
+      Value *OldNormal = Builder.CreateExtractValue(ResNormal, /*Idxs=*/0);
+      Value *OkNormal = Builder.CreateExtractValue(ResNormal, /*Idxs=*/1);
+      Builder.CreateBr(ExitBB);
+
+      // ExitBB: merge results from NaN, Zero, and Normal paths.
+      Builder.SetInsertPoint(ExitBB, ExitBB->begin());
+      PHINode *OldIntPHI =
+          Builder.CreatePHI(IntCastTy, 3, X.Var->getName() + ".atomic.old");
+      OldIntPHI->addIncoming(XCurr, NaNBB);
+      OldIntPHI->addIncoming(OldZero, ZeroBB);
+      OldIntPHI->addIncoming(OldNormal, NormalBB);
+      PHINode *SuccessPHI = Builder.CreatePHI(Builder.getInt1Ty(), 3,
+                                              X.Var->getName() + ".atomic.ok");
+      SuccessPHI->addIncoming(Builder.getFalse(), NaNBB);
+      SuccessPHI->addIncoming(OkZero, ZeroBB);
+      SuccessPHI->addIncoming(OkNormal, NormalBB);
+
+      if (isa<UnreachableInst>(ExitBB->getTerminator())) {
+        CurBBTI->eraseFromParent();
+        Builder.SetInsertPoint(ExitBB);
+      } else {
+        Builder.SetInsertPoint(&*ExitBB->getFirstNonPHIIt());
+      }
+
+      OldValue = Builder.CreateBitCast(OldIntPHI, X.ElemTy,
+                                       X.Var->getName() + ".atomic.old.fp");
+      SuccessOrFail = SuccessPHI;
+    } else {
+      AtomicCmpXchgInst *Result = nullptr;
+      if (!IsInteger) {
+        IntegerType *IntCastTy =
+            IntegerType::get(M.getContext(), X.ElemTy->getScalarSizeInBits());
+        Value *EBCast = Builder.CreateBitCast(E, IntCastTy);
+        Value *DBCast = Builder.CreateBitCast(D, IntCastTy);
+        Result = Builder.CreateAtomicCmpXchg(X.Var, EBCast, DBCast,
+                                             MaybeAlign(), AO, Failure);
       } else {
-        Value *SuccessOrFail = Builder.CreateExtractValue(Result, /*Idxs=*/1);
-        if (IsFailOnly) {
-          // CurBB----
-          //   |     |
-          //   v     |
-          // ContBB  |
-          //   |     |
-          //   v     |
-          // ExitBB <-
-          //
-          // where ContBB only contains the store of old value to 'v'.
-          BasicBlock *CurBB = Builder.GetInsertBlock();
-          Instruction *CurBBTI = CurBB->getTerminatorOrNull();
-          CurBBTI = CurBBTI ? CurBBTI : Builder.CreateUnreachable();
-          BasicBlock *ExitBB = CurBB->splitBasicBlock(
-              CurBBTI, X.Var->getName() + ".atomic.exit");
-          BasicBlock *ContBB = CurBB->splitBasicBlock(
-              CurBB->getTerminator(), X.Var->getName() + ".atomic.cont");
-          ContBB->getTerminator()->eraseFromParent();
-          CurBB->getTerminator()->eraseFromParent();
-
-          Builder.CreateCondBr(SuccessOrFail, ExitBB, ContBB);
-
-          Builder.SetInsertPoint(ContBB);
-          Builder.CreateStore(OldValue, V.Var);
-          Builder.CreateBr(ExitBB);
-
-          if (UnreachableInst *ExitTI =
-                  dyn_cast<UnreachableInst>(ExitBB->getTerminator())) {
-            CurBBTI->eraseFromParent();
-            Builder.SetInsertPoint(ExitBB);
+        Result =
+            Builder.CreateAtomicCmpXchg(X.Var, E, D, MaybeAlign(), AO, Failure);
+      }
+
+      if (V.Var) {
+        OldValue = Builder.CreateExtractValue(Result, /*Idxs=*/0);
+        if (!IsInteger)
+          OldValue = Builder.CreateBitCast(OldValue, X.ElemTy);
+        assert(OldValue->getType() == V.ElemTy &&
+               "OldValue and V must be of same type");
+        if (IsPostfixUpdate) {
+          Builder.CreateStore(OldValue, V.Var, V.IsVolatile);
+        } else {
+          SuccessOrFail = Builder.CreateExtractValue(Result, /*Idxs=*/1);
+          if (IsFailOnly) {
+            BasicBlock *CurBB = Builder.GetInsertBlock();
+            Instruction *CurBBTI = CurBB->getTerminatorOrNull();
+            CurBBTI = CurBBTI ? CurBBTI : Builder.CreateUnreachable();
+            BasicBlock *ExitBB = CurBB->splitBasicBlock(
+                CurBBTI, X.Var->getName() + ".atomic.exit");
+            BasicBlock *ContBB = CurBB->splitBasicBlock(
+                CurBB->getTerminator(), X.Var->getName() + ".atomic.cont");
+            ContBB->getTerminator()->eraseFromParent();
+            CurBB->getTerminator()->eraseFromParent();
+
+            Builder.CreateCondBr(SuccessOrFail, ExitBB, ContBB);
+
+            Builder.SetInsertPoint(ContBB);
+            Builder.CreateStore(OldValue, V.Var);
+            Builder.CreateBr(ExitBB);
+
+            if (UnreachableInst *ExitTI =
+                    dyn_cast<UnreachableInst>(ExitBB->getTerminator())) {
+              CurBBTI->eraseFromParent();
+              Builder.SetInsertPoint(ExitBB);
+            } else {
+              Builder.SetInsertPoint(ExitTI);
+            }
           } else {
-            Builder.SetInsertPoint(ExitTI);
+            Value *CapturedValue =
+                Builder.CreateSelect(SuccessOrFail, E, OldValue);
+            Builder.CreateStore(CapturedValue, V.Var, V.IsVolatile);
           }
-        } else {
-          Value *CapturedValue =
-              Builder.CreateSelect(SuccessOrFail, E, OldValue);
-          Builder.CreateStore(CapturedValue, V.Var, V.IsVolatile);
         }
       }
+      // The comparison result has to be stored.
+      if (R.Var) {
+        assert(R.Var->getType()->isPointerTy() &&
+               "r.var must be of pointer type");
+        assert(R.ElemTy->isIntegerTy() && "r must be of integral type");
+
+        Value *SuccessFailureVal =
+            Builder.CreateExtractValue(Result, /*Idxs=*/1);
+        Value *ResultCast =
+            R.IsSigned ? Builder.CreateSExt(SuccessFailureVal, R.ElemTy)
+                       : Builder.CreateZExt(SuccessFailureVal, R.ElemTy);
+        Builder.CreateStore(ResultCast, R.Var, R.IsVolatile);
+      }
     }
-    // The comparison result has to be stored.
-    if (R.Var) {
-      assert(R.Var->getType()->isPointerTy() &&
-             "r.var must be of pointer type");
-      assert(R.ElemTy->isIntegerTy() && "r must be of integral type");
-
-      Value *SuccessFailureVal = Builder.CreateExtractValue(Result, /*Idxs=*/1);
-      Value *ResultCast = R.IsSigned
-                              ? Builder.CreateSExt(SuccessFailureVal, R.ElemTy)
-                              : Builder.CreateZExt(SuccessFailureVal, R.ElemTy);
-      Builder.CreateStore(ResultCast, R.Var, R.IsVolatile);
+
+    // For the HandleFPNegZero path, handle V.Var and R.Var using the
+    // pre-computed OldValue and SuccessOrFail.
+    if (HandleFPNegZero && !IsInteger) {
+      if (V.Var) {
+        assert(OldValue->getType() == V.ElemTy &&
+               "OldValue and V must be of same type");
+        if (IsPostfixUpdate) {
+          Builder.CreateStore(OldValue, V.Var, V.IsVolatile);
+        } else {
+          if (IsFailOnly) {
+            BasicBlock *CurBB = Builder.GetInsertBlock();
+            Instruction *CurBBTI = CurBB->getTerminatorOrNull();
+            CurBBTI = CurBBTI ? CurBBTI : Builder.CreateUnreachable();
+            BasicBlock *ExitBB = CurBB->splitBasicBlock(
+                CurBBTI, X.Var->getName() + ".atomic.exit");
+            BasicBlock *ContBB = CurBB->splitBasicBlock(
+                CurBB->getTerminator(), X.Var->getName() + ".atomic.cont");
+            ContBB->getTerminator()->eraseFromParent();
+            CurBB->getTerminator()->eraseFromParent();
+
+            Builder.CreateCondBr(SuccessOrFail, ExitBB, ContBB);
+
+            Builder.SetInsertPoint(ContBB);
+            Builder.CreateStore(OldValue, V.Var);
+            Builder.CreateBr(ExitBB);
+
+            if (UnreachableInst *ExitTI =
+                    dyn_cast<UnreachableInst>(ExitBB->getTerminator())) {
+              CurBBTI->eraseFromParent();
+              Builder.SetInsertPoint(ExitBB);
+            } else {
+              Builder.SetInsertPoint(ExitTI);
+            }
+          } else {
+            Value *CapturedValue =
+                Builder.CreateSelect(SuccessOrFail, E, OldValue);
+            Builder.CreateStore(CapturedValue, V.Var, V.IsVolatile);
+          }
+        }
+      }
+      // The comparison result has to be stored.
+      if (R.Var) {
+        assert(R.Var->getType()->isPointerTy() &&
+               "r.var must be of pointer type");
+        assert(R.ElemTy->isIntegerTy() && "r must be of integral type");
+
+        Value *ResultCast = R.IsSigned
+                                ? Builder.CreateSExt(SuccessOrFail, R.ElemTy)
+                                : Builder.CreateZExt(SuccessOrFail, R.ElemTy);
+        Builder.CreateStore(ResultCast, R.Var, R.IsVolatile);
+      }
     }
   } else {
     assert((Op == OMPAtomicCompareOp::MAX || Op == OMPAtomicCompareOp::MIN) &&
diff --git a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.h b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.h
index cfe0ec5185bc8..c27ec7cf29c74 100644
--- a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.h
+++ b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.h
@@ -14,6 +14,7 @@
 #ifndef OPENACC_MP_COMMON_INTERFACES_ATOMICINTERFACES_H_
 #define OPENACC_MP_COMMON_INTERFACES_ATOMICINTERFACES_H_
 
+#include "mlir/Dialect/Arith/IR/Arith.h"
 #include "mlir/IR/OpDefinition.h"
 #include "mlir/Interfaces/ControlFlowInterfaces.h"
 
diff --git a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
index 223bee9ab1c27..abb21705b3c1c 100644
--- a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
+++ b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
@@ -317,4 +317,175 @@ def AtomicCaptureOpInterface : OpInterface<"AtomicCaptureOpInterface"> {
   ];
 }
 
+def AtomicCompareOpInterface : OpInterface<"AtomicCompareOpInterface"> {
+  let description = [{
+    This interface is used for OpenMP dialect operation that performs an
+    atomic compare.
+
+    The interface terminology uses `x`, `e`, and `d` like the directive
+    specifications:
+      `if (x == e) x = d`
+    `x` is the address of the variable that is being compared and updated.
+    The region describes the comparison and update logic. It takes
+    the current value of `x` as a single block argument.
+
+    The region has the following structure:
+    ```
+      atomic.compare {
+      ^bb0(%val_x):
+        <compare %val_x with e>
+        <conditionally yield d or %val_x>
+      }
+    ```
+  }];
+  let cppNamespace = "::mlir::accomp";
+
+  let methods = [
+    InterfaceMethod<[{
+        Obtains `x` which is the address of the variable that is being
+        compared and potentially updated.
+      }],
+      /*retTy=*/"::mlir::Value",
+      /*methodName=*/"getX",
+      /*args=*/(ins)
+    >,
+    InterfaceMethod<[{
+        Returns the first operation in the atomic compare region.
+      }],
+      /*retTy=*/"::mlir::Operation *",
+      /*methodName=*/"getFirstOp",
+      /*args=*/(ins),
+      /*methodBody=*/"",
+      /*defaultImplementation=*/[{
+        return &($_op.getRegion().front().getOperations().front());
+      }]
+    >,
+    InterfaceMethod<[{
+        Common verifier for operation that implements atomic compare interface.
+      }],
+      /*retTy=*/"::llvm::LogicalResult",
+      /*methodName=*/"verifyCommon",
+      /*args=*/(ins),
+      /*methodBody=*/"",
+      /*defaultImplementation=*/[{
+        if ($_op.getRegion().getNumArguments() != 1)
+          return $_op.emitError("the region must accept exactly one argument");
+
+        Type elementType = $_op.getX().getType().getElementType();
+        if (elementType && elementType != $_op.getRegion().getArgument(0).getType()) {
+          return $_op.emitError("the type of the operand must be a pointer type whose "
+                          "element type is the same as that of the region argument");
+        }
+
+        return mlir::success();
+      }]
+    >,
+    InterfaceMethod<[{
+        Common verifier of the required region for operation that implements
+        atomic compare interface.
+      }],
+      /*retTy=*/"::llvm::LogicalResult",
+      /*methodName=*/"verifyRegionsCommon",
+      /*args=*/(ins),
+      /*methodBody=*/"",
+      /*defaultImplementation=*/[{
+        mlir::Region &region = $_op.getRegion();
+        if (region.empty())
+          return $_op.emitError(
+            "region for atomic compare must not be empty");
+
+        mlir::Block &block = region.front();
+        if (block.empty())
+          return $_op.emitError(
+            "region body for atomic compare must not be empty");
+
+        // The region must contain at least a comparison operation and a
+        // terminator. A region with only a terminator is missing the
+        // required comparison logic.
+        if (block.getOperations().size() < 2)
+          return $_op.emitError(
+            "region must contain a comparison operation");
+
+        return mlir::success();
+      }]
+    >,
+    InterfaceMethod<[{
+        Common verifier for operator that implements atomic compare interface.
+        Checks that the comparison operation in the region uses:
+        1) supported predicate for integer comparison : eq, slt, or sgt
+        2) supported predicate for float comparison : oeq, oglt or lsgt
+      }],
+      /*retTy=*/"::llvm::LogicalResult",
+      /*methodName=*/"verifyOperator",
+      /*args=*/(ins),
+      /*methodBody=*/"",
+      /*defaultImplementation=*/[{
+        mlir::Region &region = $_op.getRegion();
+        if (region.empty())
+          return $_op.emitError(
+            "region for atomic compare must not be empty");
+
+        mlir::Block &block = region.front();
+        bool foundComparison = false;
+        for (mlir::Operation &op : block.getOperations()) {
+          llvm::StringRef opName = op.getName().getStringRef();
+          if (opName == "arith.cmpi" || opName == "llvm.icmp") {
+            foundComparison = true;
+            auto predAttr = op.getAttrOfType<mlir::IntegerAttr>("predicate");
+            if (predAttr) {
+	      auto predName = mlir::arith::stringifyCmpIPredicate(
+                  static_cast<mlir::arith::CmpIPredicate>(predAttr.getInt()));
+              if (predName != "eq" && predName != "slt" && predName != "sgt") { 
+                return $_op.emitError(
+                  "unsupported comparison operator '")
+                  << predName
+                  << "' in atomic compare region, "
+                     "supported operators are: eq, slt, sgt";
+              }
+            }
+            break;
+          } else if (opName == "arith.cmpf" || opName == "llvm.fcmp") {
+            foundComparison = true;
+            auto predAttr = op.getAttrOfType<mlir::IntegerAttr>("predicate");
+            if (predAttr) {
+              auto predName = mlir::arith::stringifyCmpFPredicate(
+                  static_cast<mlir::arith::CmpFPredicate>(predAttr.getInt()));
+              if (predName != "oeq" && predName != "ogt" && predName != "olt") {
+                return $_op.emitError(
+                  "unsupported comparison operator '")
+                  << predName
+                  << "' in atomic compare region, "
+                     "supported operators are: oeq, ogt, olt";
+              }
+            }
+            break;
+          } else if (opName == "fir.cmpc") {
+            foundComparison = true;
+            auto predAttr = op.getAttrOfType<mlir::IntegerAttr>("predicate");
+            if (predAttr) {
+              auto predName = mlir::arith::stringifyCmpFPredicate(
+                  static_cast<mlir::arith::CmpFPredicate>(predAttr.getInt()));
+              if (predName != "oeq") {
+                return $_op.emitError(
+                  "unsupported comparison operator '")
+                  << predName
+                  << "' in atomic compare region for complex type, "
+                     "only 'oeq' is supported";
+              }
+            }
+            break;
+          }
+        }
+
+        if (!foundComparison)
+          return $_op.emitError(
+            "atomic compare region must contain a comparison operation "
+            "(arith.cmpi, arith.cmpf, llvm.icmp, llvm.fcmp, or fir.cmpc)");
+
+        return mlir::success();
+      }]
+    >,
+  ];
+}
+
 #endif // OPENACC_MP_COMMON_INTERFACES_ATOMICINTERFACES
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index ff880755b63a6..0962b330e2f23 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -876,11 +876,12 @@ def SimdOp : OpenMP_Op<"simd", traits = [
   let hasRegionVerifier = 1;
 }
 
+
 def YieldOp
     : OpenMP_Op<"yield", [Pure, ReturnLike, Terminator,
-                          ParentOneOf<["AtomicUpdateOp", "DeclareReductionOp",
-                                       "LoopNestOp", "PrivateClauseOp",
-                                       "IteratorOp"]>]> {
+                          ParentOneOf<["AtomicUpdateOp", "AtomicCompareOp",
+                                       "DeclareReductionOp", "LoopNestOp",
+                                       "PrivateClauseOp", "IteratorOp"]>]> {
   let summary = "loop yield and termination operation";
   let description = [{
     "omp.yield" yields SSA values from the OpenMP dialect op region and
@@ -1951,6 +1952,51 @@ def AtomicCaptureOp : OpenMP_Op<"atomic.capture", traits = [
   let hasVerifier = 1;
 }
 
+//===----------------------------------------------------------------------===//
+// [5.1] 2.17.7 atomic Directive - compare clause
+//===----------------------------------------------------------------------===//
+
+def AtomicCompareOp : OpenMP_Op<"atomic.compare", traits = [
+    AtomicCompareOpInterface, RecursiveMemoryEffects,
+    SingleBlockImplicitTerminator<"YieldOp">
+  ], clauses = [
+    OpenMP_HintClause, OpenMP_MemoryOrderClause
+  ], singleRegion = 1> {
+  let summary = "performs an atomic compare";
+  let description = [{
+    This operation performs an atomic compare-and-swap.
+
+    The `atomic compare` construct implements atomic conditional update
+    semantics. The operand `x` is the address of the variable that is being
+    compared and potentially updated. The region describes the comparison
+    and update logic.
+
+    The region has the following structure:
+    ```
+      omp.atomic.compare {
+        if (x == d) x = e
+        omp.yield
+      }
+    ```
+  }] # clausesDescription;
+
+  let arguments = !con(
+      (ins Arg<OpenMP_PointerLikeType,
+               "Address of variable to be compared/updated", [MemRead, MemWrite]>:$x,
+           UnitAttr:$weak),
+      clausesArgs);
+
+  // Override region definition.
+  let regions = (region SizedRegion<1>:$region);
+
+  // Override clause-based assemblyFormat.
+  let assemblyFormat = clausesAssemblyFormat #
+    "$x `:` type($x) $region attr-dict";
+
+  let hasVerifier = 1;
+  let hasRegionVerifier = 1;
+}
+
 //===----------------------------------------------------------------------===//
 // [5.1] 2.21.2 threadprivate Directive
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Dialect/OpenACCMPCommon/Interfaces/CMakeLists.txt b/mlir/lib/Dialect/OpenACCMPCommon/Interfaces/CMakeLists.txt
index 6da04424231aa..2a82ff2150287 100644
--- a/mlir/lib/Dialect/OpenACCMPCommon/Interfaces/CMakeLists.txt
+++ b/mlir/lib/Dialect/OpenACCMPCommon/Interfaces/CMakeLists.txt
@@ -5,8 +5,10 @@ ADDITIONAL_HEADER_DIRS
 ${MLIR_MAIN_INCLUDE_DIR}/mlir/Dialect/OpenACCMPCommon/Interfaces
 
 DEPENDS
+MLIRArithOpsIncGen
 MLIRAtomicInterfacesIncGen
 
 LINK_LIBS PUBLIC
+MLIRArithDialect
 MLIRIR
 )
diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index ecf71480201f7..7cef23bdfef18 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -4611,6 +4611,32 @@ LogicalResult AtomicCaptureOp::verifyRegions() {
   return success();
 }
 
+//===----------------------------------------------------------------------===//
+// AtomicCompareOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult AtomicCompareOp::verify() {
+  if (verifyCommon().failed())
+    return mlir::failure();
+  return verifySynchronizationHint(*this, getHint());
+}
+
+LogicalResult AtomicCompareOp::verifyRegions() {
+  if (verifyRegionsCommon().failed())
+    return mlir::failure();
+
+  if (verifyOperator().failed())
+    return mlir::failure();
+
+  Block &block = getRegion().front();
+
+  Operation *terminator = block.getTerminator();
+  if (!terminator || !isa<YieldOp>(terminator))
+    return emitOpError("region must be terminated with omp.yield");
+
+  return success();
+}
+
 //===----------------------------------------------------------------------===//
 // CancelOp
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 8a7735ba4ae30..f0511bb4be7dd 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -450,6 +450,20 @@ static LogicalResult checkImplementationStatus(Operation &op) {
       .Case([&](omp::SimdOp op) { checkReduction(op, result); })
       .Case<omp::AtomicReadOp, omp::AtomicWriteOp, omp::AtomicUpdateOp,
             omp::AtomicCaptureOp>([&](auto op) { checkHint(op, result); })
+      .Case([&](omp::AtomicCompareOp op) {
+        checkHint(op, result);
+        Region &region = op.getRegion();
+        if (region.empty())
+          return;
+        mlir::Type argType = region.front().getArgument(0).getType();
+        auto structTy = dyn_cast<LLVM::LLVMStructType>(argType);
+        if (!structTy)
+          return;
+        DataLayout dl = DataLayout(op->getParentOfType<ModuleOp>());
+        unsigned totalBits = dl.getTypeSizeInBits(structTy);
+        if (totalBits > 128)
+          result = todo("compare for complex types wider than 128 bits");
+      })
       .Case<omp::TargetEnterDataOp, omp::TargetExitDataOp>(
           [&](auto op) { checkDepend(op, result); })
       .Case([&](omp::TargetUpdateOp op) { checkDepend(op, result); })
@@ -4851,6 +4865,348 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
   return success();
 }
 
+/// Helper to extract the OMPAtomicCompareOp from an integer comparison
+/// predicate. Returns std::nullopt for unsupported predicates.
+static std::optional<llvm::omp::OMPAtomicCompareOp>
+convertICmpPredicateToAtomicCompareOp(LLVM::ICmpPredicate predicate) {
+  switch (predicate) {
+  case LLVM::ICmpPredicate::eq:
+    return llvm::omp::OMPAtomicCompareOp::EQ;
+  case LLVM::ICmpPredicate::slt:
+  case LLVM::ICmpPredicate::ult:
+    return llvm::omp::OMPAtomicCompareOp::MIN;
+  case LLVM::ICmpPredicate::sgt:
+  case LLVM::ICmpPredicate::ugt:
+    return llvm::omp::OMPAtomicCompareOp::MAX;
+  default:
+    return std::nullopt;
+  }
+}
+
+/// Helper to extract the OMPAtomicCompareOp from a floating-point comparison
+/// predicate. Returns std::nullopt for unsupported predicates.
+static std::optional<llvm::omp::OMPAtomicCompareOp>
+convertFCmpPredicateToAtomicCompareOp(LLVM::FCmpPredicate predicate) {
+  switch (predicate) {
+  case LLVM::FCmpPredicate::oeq:
+  case LLVM::FCmpPredicate::ueq:
+    return llvm::omp::OMPAtomicCompareOp::EQ;
+  case LLVM::FCmpPredicate::olt:
+  case LLVM::FCmpPredicate::ult:
+    return llvm::omp::OMPAtomicCompareOp::MIN;
+  case LLVM::FCmpPredicate::ogt:
+  case LLVM::FCmpPredicate::ugt:
+    return llvm::omp::OMPAtomicCompareOp::MAX;
+  default:
+    return std::nullopt;
+  }
+}
+
+/// Converts an omp.atomic.compare operation to LLVM IR.
+///
+///      if (x == e) x = d
+/// The region contains a comparison + select pattern:
+///   ^bb0(%xval: T):
+///     %cmp = llvm.icmp/fcmp <pred> %xval, %e : T
+///     %sel = llvm.select %cmp, %d, %xval : i1, T
+///     omp.yield(%sel : T)
+///
+/// From MLIR extract:
+///   1) comparison operator
+///   2) expected value (e)
+///   3) desired value (d)
+/// These are passed to OpenMPIRBuilder::createAtomicCompare which generates
+/// the actual cmpxchg / atomicrmw instruction.
+///
+static LogicalResult
+convertOmpAtomicCompare(omp::AtomicCompareOp atomicCompareOp,
+                        llvm::IRBuilderBase &builder,
+                        LLVM::ModuleTranslation &moduleTranslation) {
+  llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
+  if (failed(checkImplementationStatus(*atomicCompareOp)))
+    return failure();
+
+  Region &region = atomicCompareOp.getRegion();
+  Block &block = region.front();
+
+  // Determine element type from the region block argument
+  llvm::Type *llvmXElementType =
+      moduleTranslation.convertType(block.getArgument(0).getType());
+  if (!llvmXElementType)
+    return atomicCompareOp.emitError(
+        "unable to determine element type for atomic compare");
+
+  llvm::Value *llvmX = moduleTranslation.lookupValue(atomicCompareOp.getX());
+
+  // IsSigned is determined from the comparison predicate in the region.
+  // Signed ICmp predicates (slt/sgt) set this to true; unsigned (ult/ugt)
+  // leave it false. For EQ and float comparisons, signedness is irrelevant.
+  bool isSigned = false;
+  llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {llvmX, llvmXElementType,
+                                                      isSigned,
+                                                      /*IsVolatile=*/false};
+
+  llvm::AtomicOrdering atomicOrdering =
+      convertAtomicOrdering(atomicCompareOp.getMemoryOrder());
+
+  auto isAtomicComparePatternOp = [](Operation &op) {
+    return llvm::isa<LLVM::ICmpOp, LLVM::FCmpOp, LLVM::SelectOp, LLVM::AndOp,
+                     LLVM::OrOp>(op);
+  };
+
+  // Pre-translate operations inside the region that compute e and d (e.g.,
+  // GEP, loads for dereferencing Fortran pointers) but are not part of the
+  // atomic compare-and-swap pattern (icmp/fcmp, select, and/or).
+  //
+  // 1) Validity: The OpenMP spec requires e and d to be evaluated before the
+  //    atomic operation, so emitting their computation here is correct.
+  // 2) Memory effects: These ops only depend on values defined outside the
+  //    region. They cannot observe the block argument (%xval), which is the
+  //    value loaded atomically by cmpxchg and does not exist yet.
+  // 3) Invariant enforcement: The `allOperandsMapped` check below skips any
+  //    op whose operands include the unmapped block argument, guaranteeing
+  //    only region-external-dependent ops are pre-translated.
+  for (Operation &op : block.without_terminator()) {
+    // Skip operations that form the atomic compare pattern — these are
+    // not emitted as individual instructions but are analyzed below to
+    // extract the comparison predicate, expected value (e), and desired
+    // value (d) for generating a single cmpxchg/atomicrmw.
+    if (isAtomicComparePatternOp(op))
+      continue;
+
+    // Avoid translating ops that depend on the unmapped block argument.
+    bool allOperandsMapped = llvm::all_of(op.getOperands(), [&](mlir::Value v) {
+      return moduleTranslation.lookupValue(v) != nullptr;
+    });
+    if (!allOperandsMapped)
+      continue;
+
+    if (failed(moduleTranslation.convertOperation(op, builder)))
+      return atomicCompareOp.emitError(
+          "failed to translate operation inside atomic compare region");
+  }
+
+  // Look up a value that may have been pre-translated or defined outside the
+  // region.
+  auto materializeValue = [&](mlir::Value val) -> llvm::Value * {
+    // Check if the value is already mapped (pre-translated or defined outside).
+    if (llvm::Value *existing = moduleTranslation.lookupValue(val))
+      return existing;
+    // Fallback for a single LoadOp whose address is mapped but whose result
+    // was not pre-translated.
+    if (auto loadOp = val.getDefiningOp<LLVM::LoadOp>()) {
+      if (loadOp->getParentRegion() == &region) {
+        llvm::Value *loadAddr = moduleTranslation.lookupValue(loadOp.getAddr());
+        if (!loadAddr)
+          return nullptr;
+        llvm::Type *loadType =
+            moduleTranslation.convertType(loadOp.getResult().getType());
+        return builder.CreateLoad(loadType, loadAddr);
+      }
+    }
+    return nullptr;
+  };
+
+  // Walk the region to extract comparison predicate, eVal, and dVal.
+  // if (x == eVal) x = dVal
+  llvm::omp::OMPAtomicCompareOp compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
+  llvm::Value *eVal = nullptr;
+  llvm::Value *dVal = nullptr;
+  bool isXBinopExpr = false;
+
+  auto traceToAggregate = [](mlir::Value v) -> mlir::Value {
+    if (auto extractOp = v.getDefiningOp<LLVM::ExtractValueOp>())
+      return extractOp.getContainer();
+    return nullptr;
+  };
+
+  // Check for a decomposed complex comparison pattern:
+  //   %re_x = llvm.extractvalue %xval[0]
+  //   %re_e = llvm.extractvalue %eStruct[0]
+  //   %cmp_re = llvm.fcmp "oeq" %re_x, %re_e
+  //   %im_x = llvm.extractvalue %xval[1]
+  //   %im_e = llvm.extractvalue %eStruct[1]
+  //   %cmp_im = llvm.fcmp "oeq" %im_x, %im_e
+  //   %cmp = llvm.and %cmp_re, %cmp_im   (for EQ)
+  // Detect this by looking for AndOp/OrOp whose operands are both FCmpOps
+  // operating on ExtractValueOps from the block argument.
+  bool isComplexPattern = false;
+  for (Operation &op : block.getOperations()) {
+    if (!isa<LLVM::AndOp, LLVM::OrOp>(op))
+      continue;
+
+    // Using : %cmp = llvm.and %cmp_re, %cmp_im
+    auto lhsFcmp = op.getOperand(0).getDefiningOp<LLVM::FCmpOp>();
+    auto rhsFcmp = op.getOperand(1).getDefiningOp<LLVM::FCmpOp>();
+    if (!lhsFcmp || !rhsFcmp)
+      continue;
+
+    // Using : %cmp_re = llvm.fcmp "oeq" %re_x, %re_e
+    // Check presence of x (block argument) and get e.
+    mlir::Value lhsAgg0 = traceToAggregate(lhsFcmp.getOperand(0));
+    mlir::Value lhsAgg1 = traceToAggregate(lhsFcmp.getOperand(1));
+    bool lhsXIsOp0 = (lhsAgg0 == block.getArgument(0));
+    bool lhsXIsOp1 = (lhsAgg1 == block.getArgument(0));
+    if (!lhsXIsOp0 && !lhsXIsOp1)
+      continue;
+    mlir::Value eAggregate = lhsXIsOp0 ? lhsAgg1 : lhsAgg0;
+    if (!eAggregate)
+      continue;
+
+    if (isa<LLVM::AndOp>(op))
+      compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
+    else
+      // OrOp corresponds to NE, which is not a valid atomic compare op.
+      return atomicCompareOp.emitError(
+          "unsupported comparison predicate (NE) for complex atomic compare");
+
+    isXBinopExpr = lhsXIsOp0;
+    eVal = materializeValue(eAggregate);
+    isComplexPattern = true;
+    break;
+  }
+
+  if (isComplexPattern) {
+    // dVal from SelectOp or YieldOp.
+    for (Operation &op : block.getOperations()) {
+      if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
+        dVal = materializeValue(selectOp.getTrueValue());
+        break;
+      }
+    }
+    if (!dVal) {
+      auto yieldOp = cast<omp::YieldOp>(block.getTerminator());
+      if (yieldOp.getResults().empty())
+        return atomicCompareOp.emitError(
+            "failed to extract desired value (d) from atomic compare region");
+      dVal = materializeValue(yieldOp.getResults()[0]);
+    }
+
+    const llvm::DataLayout &DL =
+        builder.GetInsertBlock()->getModule()->getDataLayout();
+    unsigned totalBits =
+        DL.getTypeStoreSizeInBits(llvmXElementType).getFixedValue();
+
+    llvm::IntegerType *intTy =
+        llvm::IntegerType::get(builder.getContext(), totalBits);
+
+    llvm::Align complexAlign = DL.getABITypeAlign(llvmXElementType);
+    llvm::Align intAlign = DL.getABITypeAlign(intTy);
+    llvm::Align maxAlign = std::max(complexAlign, intAlign);
+
+    llvm::AllocaInst *eAlloca =
+        builder.CreateAlloca(llvmXElementType, nullptr, "cmplx.e");
+    eAlloca->setAlignment(maxAlign);
+    llvm::AllocaInst *dAlloca =
+        builder.CreateAlloca(llvmXElementType, nullptr, "cmplx.d");
+    dAlloca->setAlignment(maxAlign);
+
+    builder.CreateAlignedStore(eVal, eAlloca, maxAlign);
+    llvm::Value *eInt =
+        builder.CreateAlignedLoad(intTy, eAlloca, maxAlign, "cmplx.e.int");
+    builder.CreateAlignedStore(dVal, dAlloca, maxAlign);
+    llvm::Value *dInt =
+        builder.CreateAlignedLoad(intTy, dAlloca, maxAlign, "cmplx.d.int");
+
+    llvm::AtomicOrdering failOrdering =
+        llvm::AtomicCmpXchgInst::getStrongestFailureOrdering(atomicOrdering);
+    builder.CreateAtomicCmpXchg(llvmX, eInt, dInt, maxAlign, atomicOrdering,
+                                failOrdering);
+
+    // Emit flush after atomic compare if needed (for release, acq_rel,
+    // seq_cst orderings).
+    if (atomicOrdering == llvm::AtomicOrdering::Release ||
+        atomicOrdering == llvm::AtomicOrdering::AcquireRelease ||
+        atomicOrdering == llvm::AtomicOrdering::SequentiallyConsistent) {
+      llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
+      ompBuilder->createFlush(ompLoc);
+    }
+    return success();
+  } else {
+
+    for (Operation &op : block.getOperations()) {
+      if (auto icmpOp = dyn_cast<LLVM::ICmpOp>(op)) {
+        auto maybeOp =
+            convertICmpPredicateToAtomicCompareOp(icmpOp.getPredicate());
+        if (!maybeOp)
+          return atomicCompareOp.emitError(
+              "unsupported comparison predicate in atomic compare");
+        compareOp = *maybeOp;
+
+        LLVM::ICmpPredicate pred = icmpOp.getPredicate();
+        isSigned = (pred == LLVM::ICmpPredicate::slt ||
+                    pred == LLVM::ICmpPredicate::sgt ||
+                    pred == LLVM::ICmpPredicate::sle ||
+                    pred == LLVM::ICmpPredicate::sge);
+
+        // Identify which operand is the block argument (x) and which is e.
+        isXBinopExpr = (icmpOp.getOperand(0) == block.getArgument(0));
+        mlir::Value eOperand =
+            isXBinopExpr ? icmpOp.getOperand(1) : icmpOp.getOperand(0);
+        eVal = materializeValue(eOperand);
+      } else if (auto fcmpOp = dyn_cast<LLVM::FCmpOp>(op)) {
+        auto maybeOp =
+            convertFCmpPredicateToAtomicCompareOp(fcmpOp.getPredicate());
+        if (!maybeOp)
+          return atomicCompareOp.emitError(
+              "unsupported comparison predicate in atomic compare");
+        compareOp = *maybeOp;
+
+        isXBinopExpr = (fcmpOp.getOperand(0) == block.getArgument(0));
+        mlir::Value eOperand =
+            isXBinopExpr ? fcmpOp.getOperand(1) : fcmpOp.getOperand(0);
+        eVal = materializeValue(eOperand);
+      } else if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
+        if (!dVal)
+          dVal = materializeValue(selectOp.getTrueValue());
+      }
+    }
+  }
+
+  // For non-complex patterns, also extract dVal from SelectOp.
+  if (!dVal) {
+    for (Operation &op : block.getOperations()) {
+      if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
+        dVal = materializeValue(selectOp.getTrueValue());
+        break;
+      }
+    }
+  }
+
+  if (!eVal)
+    return atomicCompareOp.emitError(
+        "failed to extract expected value (e) from atomic compare region");
+  if (!dVal) {
+    // Fall back to the yield operand.
+    auto yieldOp = cast<omp::YieldOp>(block.getTerminator());
+    if (yieldOp.getResults().empty())
+      return atomicCompareOp.emitError(
+          "failed to extract desired value (d) from atomic compare region");
+    dVal = materializeValue(yieldOp.getResults()[0]);
+  }
+
+  llvmAtomicX.IsSigned = isSigned;
+
+  llvm::OpenMPIRBuilder::AtomicOpValue vOpVal = {nullptr, nullptr, false,
+                                                 false};
+  llvm::OpenMPIRBuilder::AtomicOpValue rOpVal = {nullptr, nullptr, false,
+                                                 false};
+  llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
+
+  bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
+  llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
+      ompBuilder->createAtomicCompare(ompLoc, llvmAtomicX, vOpVal, rOpVal, eVal,
+                                      dVal, atomicOrdering, compareOp,
+                                      isXBinopExpr, false, false);
+  ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
+
+  if (failed(handleError(afterIP, *atomicCompareOp)))
+    return failure();
+
+  builder.restoreIP(*afterIP);
+  return success();
+}
+
 static llvm::omp::Directive convertCancellationConstructType(
     omp::ClauseCancellationConstructType directive) {
   switch (directive) {
@@ -8442,6 +8798,9 @@ LogicalResult OpenMPDialectLLVMIRTranslationInterface::convertOperation(
           .Case([&](omp::AtomicCaptureOp op) {
             return convertOmpAtomicCapture(op, builder, moduleTranslation);
           })
+          .Case([&](omp::AtomicCompareOp op) {
+            return convertOmpAtomicCompare(op, builder, moduleTranslation);
+          })
           .Case([&](omp::CancelOp op) {
             return convertOmpCancel(op, builder, moduleTranslation);
           })
diff --git a/mlir/test/Dialect/OpenMP/invalid.mlir b/mlir/test/Dialect/OpenMP/invalid.mlir
index f22b05c6c9a46..06ad3d60ea635 100644
--- a/mlir/test/Dialect/OpenMP/invalid.mlir
+++ b/mlir/test/Dialect/OpenMP/invalid.mlir
@@ -1420,6 +1420,523 @@ func.func @omp_atomic_capture(%x: memref<i32>, %v: memref<i32>, %expr: i32) {
 
 // -----
 
+func.func @omp_atomic_compare_no_block_arg(%x: memref<i32>, %e: i32, %d: i32) {
+  // expected-error @below {{the region must accept exactly one argument}}
+  omp.atomic.compare %x : memref<i32> {
+    omp.yield
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_empty_region(%x: memref<i32>, %e: i32, %d: i32) {
+  // expected-error @below {{region must contain a comparison operation}}
+  omp.atomic.compare %x : memref<i32> {
+  ^bb0(%xval: i32):
+    omp.yield(%xval : i32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_hint(%x: memref<i32>, %e: i32, %d: i32) {
+  // expected-error @below {{the hints omp_sync_hint_uncontended and omp_sync_hint_contended cannot be combined}}
+  omp.atomic.compare hint(contended, uncontended) %x : memref<i32> {
+  ^bb0(%xval: i32):
+    %cmp = llvm.icmp "eq" %xval, %e : i32
+    %sel = llvm.select %cmp, %d, %xval : i1, i32
+    omp.yield(%sel : i32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_hint2(%x: memref<i32>, %e: i32, %d: i32) {
+  // expected-error @below {{the hints omp_sync_hint_nonspeculative and omp_sync_hint_speculative cannot be combined}}
+  omp.atomic.compare hint(nonspeculative, speculative) %x : memref<i32> {
+  ^bb0(%xval: i32):
+    %cmp = llvm.icmp "eq" %xval, %e : i32
+    %sel = llvm.select %cmp, %d, %xval : i1, i32
+    omp.yield(%sel : i32)
+  }
+  return
+}
+
+// -----
+// float comparison operators mentionend in ArithBase.td not permitted for
+//     !omp atomic compare
+
+func.func @omp_atomic_compare_invalid_cmpf_predicate(%x: memref<f32>, %e: f32, %d: f32) {
+  // expected-error @below {{unsupported comparison operator 'one' in atomic compare region, supported operators are: oeq, ogt, olt}}
+  omp.atomic.compare %x : memref<f32> {
+  ^bb0(%xval: f32):
+    %cmp = arith.cmpf one, %xval, %e : f32
+    %sel = arith.select %cmp, %d, %xval : f32
+    omp.yield(%sel : f32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_fcmp_predicate(%x: memref<f64>, %e: f64, %d: f64) {
+  // expected-error @below {{unsupported comparison operator 'one' in atomic compare region, supported operators are: oeq, ogt, olt}}
+  omp.atomic.compare %x : memref<f64> {
+  ^bb0(%xval: f64):
+    %cmp = llvm.fcmp "one" %xval, %e : f64
+    %sel = llvm.select %cmp, %d, %xval : i1, f64
+    omp.yield(%sel : f64)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_cmpf_predicate(%x: memref<f32>, %e: f32, %d: f32) {
+  // expected-error @below {{unsupported comparison operator 'oge' in atomic compare region, supported operators are: oeq, ogt, olt}}
+  omp.atomic.compare %x : memref<f32> {
+  ^bb0(%xval: f32):
+    %cmp = arith.cmpf oge, %xval, %e : f32
+    %sel = arith.select %cmp, %d, %xval : f32
+    omp.yield(%sel : f32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_fcmp_predicate(%x: memref<f64>, %e: f64, %d: f64) {
+  // expected-error @below {{unsupported comparison operator 'oge' in atomic compare region, supported operators are: oeq, ogt, olt}}
+  omp.atomic.compare %x : memref<f64> {
+  ^bb0(%xval: f64):
+    %cmp = llvm.fcmp "oge" %xval, %e : f64
+    %sel = llvm.select %cmp, %d, %xval : i1, f64
+    omp.yield(%sel : f64)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_cmpf_predicate(%x: memref<f64>, %e: f64, %d: f64) {
+  // expected-error @below {{unsupported comparison operator 'ole' in atomic compare region, supported operators are: oeq, ogt, olt}}
+  omp.atomic.compare %x : memref<f64> {
+  ^bb0(%xval: f64):
+    %cmp = arith.cmpf ole, %xval, %e : f64
+    %sel = arith.select %cmp, %d, %xval : f64
+    omp.yield(%sel : f64)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_fcmp_predicate(%x: memref<f32>, %e: f32, %d: f32) {
+  // expected-error @below {{unsupported comparison operator 'ole' in atomic compare region, supported operators are: oeq, ogt, olt}}
+  omp.atomic.compare %x : memref<f32> {
+  ^bb0(%xval: f32):
+    %cmp = llvm.fcmp "ole" %xval, %e : f32
+    %sel = llvm.select %cmp, %d, %xval : i1, f32
+    omp.yield(%sel : f32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_cmpf_predicate(%x: memref<f64>, %e: f64, %d: f64) {
+  // expected-error @below {{unsupported comparison operator 'ord' in atomic compare region, supported operators are: oeq, ogt, olt}}
+  omp.atomic.compare %x : memref<f64> {
+  ^bb0(%xval: f64):
+    %cmp = arith.cmpf ord, %xval, %e : f64
+    %sel = arith.select %cmp, %d, %xval : f64
+    omp.yield(%sel : f64)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_fcmp_predicate(%x: memref<f32>, %e: f32, %d: f32) {
+  // expected-error @below {{unsupported comparison operator 'ord' in atomic compare region, supported operators are: oeq, ogt, olt}}
+  omp.atomic.compare %x : memref<f32> {
+  ^bb0(%xval: f32):
+    %cmp = llvm.fcmp "ord" %xval, %e : f32
+    %sel = llvm.select %cmp, %d, %xval : i1, f32
+    omp.yield(%sel : f32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_cmpf_predicate(%x: memref<f32>, %e: f32, %d: f32) {
+  // expected-error @below {{unsupported comparison operator 'ueq' in atomic compare region, supported operators are: oeq, ogt, olt}}
+  omp.atomic.compare %x : memref<f32> {
+  ^bb0(%xval: f32):
+    %cmp = arith.cmpf ueq, %xval, %e : f32
+    %sel = arith.select %cmp, %d, %xval : f32
+    omp.yield(%sel : f32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_fcmp_predicate(%x: memref<f64>, %e: f64, %d: f64) {
+  // expected-error @below {{unsupported comparison operator 'ueq' in atomic compare region, supported operators are: oeq, ogt, olt}}
+  omp.atomic.compare %x : memref<f64> {
+  ^bb0(%xval: f64):
+    %cmp = llvm.fcmp "ueq" %xval, %e : f64
+    %sel = llvm.select %cmp, %d, %xval : i1, f64
+    omp.yield(%sel : f64)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_cmpf_predicate(%x: memref<f32>, %e: f32, %d: f32) {
+  // expected-error @below {{unsupported comparison operator 'ugt' in atomic compare region, supported operators are: oeq, ogt, olt}}
+  omp.atomic.compare %x : memref<f32> {
+  ^bb0(%xval: f32):
+    %cmp = arith.cmpf ugt, %xval, %e : f32
+    %sel = arith.select %cmp, %d, %xval : f32
+    omp.yield(%sel : f32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_fcmp_predicate(%x: memref<f64>, %e: f64, %d: f64) {
+  // expected-error @below {{unsupported comparison operator 'ugt' in atomic compare region, supported operators are: oeq, ogt, olt}}
+  omp.atomic.compare %x : memref<f64> {
+  ^bb0(%xval: f64):
+    %cmp = llvm.fcmp "ugt" %xval, %e : f64
+    %sel = llvm.select %cmp, %d, %xval : i1, f64
+    omp.yield(%sel : f64)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_cmpf_predicate(%x: memref<f32>, %e: f32, %d: f32) {
+  // expected-error @below {{unsupported comparison operator 'uge' in atomic compare region, supported operators are: oeq, ogt, olt}}
+  omp.atomic.compare %x : memref<f32> {
+  ^bb0(%xval: f32):
+    %cmp = arith.cmpf uge, %xval, %e : f32
+    %sel = arith.select %cmp, %d, %xval : f32
+    omp.yield(%sel : f32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_fcmp_predicate(%x: memref<f32>, %e: f32, %d: f32) {
+  // expected-error @below {{unsupported comparison operator 'uge' in atomic compare region, supported operators are: oeq, ogt, olt}}
+  omp.atomic.compare %x : memref<f32> {
+  ^bb0(%xval: f32):
+    %cmp = llvm.fcmp "uge" %xval, %e : f32
+    %sel = llvm.select %cmp, %d, %xval : i1, f32
+    omp.yield(%sel : f32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_cmpf_predicate(%x: memref<f64>, %e: f64, %d: f64) {
+  // expected-error @below {{unsupported comparison operator 'ult' in atomic compare region, supported operators are: oeq, ogt, olt}}
+  omp.atomic.compare %x : memref<f64> {
+  ^bb0(%xval: f64):
+    %cmp = arith.cmpf ult, %xval, %e : f64
+    %sel = arith.select %cmp, %d, %xval : f64
+    omp.yield(%sel : f64)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_fcmp_predicate(%x: memref<f32>, %e: f32, %d: f32) {
+  // expected-error @below {{unsupported comparison operator 'ult' in atomic compare region, supported operators are: oeq, ogt, olt}}
+  omp.atomic.compare %x : memref<f32> {
+  ^bb0(%xval: f32):
+    %cmp = llvm.fcmp "ult" %xval, %e : f32
+    %sel = llvm.select %cmp, %d, %xval : i1, f32
+    omp.yield(%sel : f32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_cmpf_predicate(%x: memref<f32>, %e: f32, %d: f32) {
+  // expected-error @below {{unsupported comparison operator 'ule' in atomic compare region, supported operators are: oeq, ogt, olt}}
+  omp.atomic.compare %x : memref<f32> {
+  ^bb0(%xval: f32):
+    %cmp = arith.cmpf ule, %xval, %e : f32
+    %sel = arith.select %cmp, %d, %xval : f32
+    omp.yield(%sel : f32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_fcmp_predicate(%x: memref<f64>, %e: f64, %d: f64) {
+  // expected-error @below {{unsupported comparison operator 'ule' in atomic compare region, supported operators are: oeq, ogt, olt}}
+  omp.atomic.compare %x : memref<f64> {
+  ^bb0(%xval: f64):
+    %cmp = llvm.fcmp "ule" %xval, %e : f64
+    %sel = llvm.select %cmp, %d, %xval : i1, f64
+    omp.yield(%sel : f64)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_cmpf_predicate(%x: memref<f32>, %e: f32, %d: f32) {
+  // expected-error @below {{unsupported comparison operator 'une' in atomic compare region, supported operators are: oeq, ogt, olt}}
+  omp.atomic.compare %x : memref<f32> {
+  ^bb0(%xval: f32):
+    %cmp = arith.cmpf une, %xval, %e : f32
+    %sel = arith.select %cmp, %d, %xval : f32
+    omp.yield(%sel : f32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_fcmp_predicate(%x: memref<f32>, %e: f32, %d: f32) {
+  // expected-error @below {{unsupported comparison operator 'une' in atomic compare region, supported operators are: oeq, ogt, olt}}
+  omp.atomic.compare %x : memref<f32> {
+  ^bb0(%xval: f32):
+    %cmp = llvm.fcmp "une" %xval, %e : f32
+    %sel = llvm.select %cmp, %d, %xval : i1, f32
+    omp.yield(%sel : f32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_cmpf_predicate(%x: memref<f64>, %e: f64, %d: f64) {
+  // expected-error @below {{unsupported comparison operator 'uno' in atomic compare region, supported operators are: oeq, ogt, olt}}
+  omp.atomic.compare %x : memref<f64> {
+  ^bb0(%xval: f64):
+    %cmp = arith.cmpf uno, %xval, %e : f64
+    %sel = arith.select %cmp, %d, %xval : f64
+    omp.yield(%sel : f64)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_fcmp_predicate(%x: memref<f32>, %e: f32, %d: f32) {
+  // expected-error @below {{unsupported comparison operator 'uno' in atomic compare region, supported operators are: oeq, ogt, olt}}
+  omp.atomic.compare %x : memref<f32> {
+  ^bb0(%xval: f32):
+    %cmp = llvm.fcmp "uno" %xval, %e : f32
+    %sel = llvm.select %cmp, %d, %xval : i1, f32
+    omp.yield(%sel : f32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_cmpi_predicate(%x: memref<i32>, %e: i32, %d: i32) {
+  // expected-error @below {{unsupported comparison operator 'ne' in atomic compare region, supported operators are: eq, slt, sgt}}
+  omp.atomic.compare %x : memref<i32> {
+  ^bb0(%xval: i32):
+    %cmp = arith.cmpi ne, %xval, %e : i32
+    %sel = arith.select %cmp, %d, %xval : i32
+    omp.yield(%sel : i32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_icmp_predicate(%x: memref<i32>, %e: i32, %d: i32) {
+  // expected-error @below {{unsupported comparison operator 'ne' in atomic compare region, supported operators are: eq, slt, sgt}}
+  omp.atomic.compare %x : memref<i32> {
+  ^bb0(%xval: i32):
+    %cmp = llvm.icmp "ne" %xval, %e : i32
+    %sel = llvm.select %cmp, %d, %xval : i1, i32
+    omp.yield(%sel : i32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_cmpi_predicate(%x: memref<i32>, %e: i32, %d: i32) {
+  // expected-error @below {{unsupported comparison operator 'sle' in atomic compare region, supported operators are: eq, slt, sgt}}
+  omp.atomic.compare %x : memref<i32> {
+  ^bb0(%xval: i32):
+    %cmp = arith.cmpi sle, %xval, %e : i32
+    %sel = arith.select %cmp, %d, %xval : i32
+    omp.yield(%sel : i32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_icmp_predicate(%x: memref<i32>, %e: i32, %d: i32) {
+  // expected-error @below {{unsupported comparison operator 'sle' in atomic compare region, supported operators are: eq, slt, sgt}}
+  omp.atomic.compare %x : memref<i32> {
+  ^bb0(%xval: i32):
+    %cmp = llvm.icmp "sle" %xval, %e : i32
+    %sel = llvm.select %cmp, %d, %xval : i1, i32
+    omp.yield(%sel : i32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_cmpi_predicate(%x: memref<i32>, %e: i32, %d: i32) {
+  // expected-error @below {{unsupported comparison operator 'sge' in atomic compare region, supported operators are: eq, slt, sgt}}
+  omp.atomic.compare %x : memref<i32> {
+  ^bb0(%xval: i32):
+    %cmp = arith.cmpi sge, %xval, %e : i32
+    %sel = arith.select %cmp, %d, %xval : i32
+    omp.yield(%sel : i32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_icmp_predicate(%x: memref<i32>, %e: i32, %d: i32) {
+  // expected-error @below {{unsupported comparison operator 'sge' in atomic compare region, supported operators are: eq, slt, sgt}}
+  omp.atomic.compare %x : memref<i32> {
+  ^bb0(%xval: i32):
+    %cmp = llvm.icmp "sge" %xval, %e : i32
+    %sel = llvm.select %cmp, %d, %xval : i1, i32
+    omp.yield(%sel : i32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_cmpi_predicate(%x: memref<i32>, %e: i32, %d: i32) {
+  // expected-error @below {{unsupported comparison operator 'ult' in atomic compare region, supported operators are: eq, slt, sgt}}
+  omp.atomic.compare %x : memref<i32> {
+  ^bb0(%xval: i32):
+    %cmp = arith.cmpi ult, %xval, %e : i32
+    %sel = arith.select %cmp, %d, %xval : i32
+    omp.yield(%sel : i32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_icmp_predicate(%x: memref<i32>, %e: i32, %d: i32) {
+  // expected-error @below {{unsupported comparison operator 'ult' in atomic compare region, supported operators are: eq, slt, sgt}}
+  omp.atomic.compare %x : memref<i32> {
+  ^bb0(%xval: i32):
+    %cmp = llvm.icmp "ult" %xval, %e : i32
+    %sel = llvm.select %cmp, %d, %xval : i1, i32
+    omp.yield(%sel : i32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_cmpi_predicate(%x: memref<i32>, %e: i32, %d: i32) {
+  // expected-error @below {{unsupported comparison operator 'ule' in atomic compare region, supported operators are: eq, slt, sgt}}
+  omp.atomic.compare %x : memref<i32> {
+  ^bb0(%xval: i32):
+    %cmp = arith.cmpi ule, %xval, %e : i32
+    %sel = arith.select %cmp, %d, %xval : i32
+    omp.yield(%sel : i32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_icmp_predicate(%x: memref<i32>, %e: i32, %d: i32) {
+  // expected-error @below {{unsupported comparison operator 'ule' in atomic compare region, supported operators are: eq, slt, sgt}}
+  omp.atomic.compare %x : memref<i32> {
+  ^bb0(%xval: i32):
+    %cmp = llvm.icmp "ule" %xval, %e : i32
+    %sel = llvm.select %cmp, %d, %xval : i1, i32
+    omp.yield(%sel : i32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_cmpi_predicate(%x: memref<i32>, %e: i32, %d: i32) {
+  // expected-error @below {{unsupported comparison operator 'ugt' in atomic compare region, supported operators are: eq, slt, sgt}}
+  omp.atomic.compare %x : memref<i32> {
+  ^bb0(%xval: i32):
+    %cmp = arith.cmpi ugt, %xval, %e : i32
+    %sel = arith.select %cmp, %d, %xval : i32
+    omp.yield(%sel : i32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_icmp_predicate(%x: memref<i32>, %e: i32, %d: i32) {
+  // expected-error @below {{unsupported comparison operator 'ugt' in atomic compare region, supported operators are: eq, slt, sgt}}
+  omp.atomic.compare %x : memref<i32> {
+  ^bb0(%xval: i32):
+    %cmp = llvm.icmp "ugt" %xval, %e : i32
+    %sel = llvm.select %cmp, %d, %xval : i1, i32
+    omp.yield(%sel : i32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_cmpi_predicate(%x: memref<i32>, %e: i32, %d: i32) {
+  // expected-error @below {{unsupported comparison operator 'uge' in atomic compare region, supported operators are: eq, slt, sgt}}
+  omp.atomic.compare %x : memref<i32> {
+  ^bb0(%xval: i32):
+    %cmp = arith.cmpi uge, %xval, %e : i32
+    %sel = arith.select %cmp, %d, %xval : i32
+    omp.yield(%sel : i32)
+  }
+  return
+}
+
+// -----
+
+func.func @omp_atomic_compare_invalid_icmp_predicate(%x: memref<i32>, %e: i32, %d: i32) {
+  // expected-error @below {{unsupported comparison operator 'uge' in atomic compare region, supported operators are: eq, slt, sgt}}
+  omp.atomic.compare %x : memref<i32> {
+  ^bb0(%xval: i32):
+    %cmp = llvm.icmp "uge" %xval, %e : i32
+    %sel = llvm.select %cmp, %d, %xval : i1, i32
+    omp.yield(%sel : i32)
+  }
+  return
+}
+
+// -----
+
 func.func @omp_teams_parent() {
   omp.parallel {
     // expected-error @below {{expected to be nested inside of omp.target or not nested in any OpenMP dialect operations}}
diff --git a/mlir/test/Target/LLVMIR/openmp-llvm.mlir b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
index f002a64a593a0..38f10320bd3ae 100644
--- a/mlir/test/Target/LLVMIR/openmp-llvm.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
@@ -2559,6 +2559,215 @@ llvm.func @omp_atomic_capture_misc(
 
 // -----
 
+// CHECK-LABEL: @omp_atomic_compare
+// CHECK-SAME: (ptr %[[X:.*]], i32 %[[E:.*]], i32 %[[D:.*]], ptr %[[XF:.*]], float %[[EF:.*]], float %[[DF:.*]], ptr %[[XC:.*]], { float, float } %[[EC:.*]], { float, float } %[[DC:.*]], ptr %[[XP:.*]], ptr %[[EP:.*]], ptr %[[DP:.*]])
+llvm.func @omp_atomic_compare(
+  %x : !llvm.ptr, %e : i32, %d : i32,
+  %xf : !llvm.ptr, %ef : f32, %df : f32,
+  %xc : !llvm.ptr, %ec : !llvm.struct<(f32, f32)>, %dc : !llvm.struct<(f32, f32)>,
+  %xp : !llvm.ptr, %ep : !llvm.ptr, %dp : !llvm.ptr) {
+
+  // Integer equality  →  cmpxchg
+  // CHECK: cmpxchg ptr %[[X]], i32 %[[E]], i32 %[[D]] monotonic monotonic
+  omp.atomic.compare %x : !llvm.ptr {
+  ^bb0(%xval : i32):
+    %cmp0 = llvm.icmp "eq" %xval, %e : i32
+    %sel0 = llvm.select %cmp0, %d, %xval : i1, i32
+    omp.yield(%sel0 : i32)
+  }
+
+  // Float equality  →  NaN guard + ±0.0 guard + cmpxchg.
+  // IEEE 754: NaN != NaN but same-bit-pattern NaN would match in cmpxchg;
+  // -0.0 == +0.0 but different bit patterns would mismatch.
+  // CHECK: %[[EBC:.*]] = bitcast float %[[EF]] to i32
+  // CHECK: %[[DBC:.*]] = bitcast float %[[DF]] to i32
+  // CHECK: load atomic i32, ptr %[[XF]] monotonic
+  // NaN check: skip cmpxchg if either E or X is NaN.
+  // CHECK: %[[EISNAN:.*]] = fcmp uno float %[[EF]], %[[EF]]
+  // CHECK: %[[XISNAN:.*]] = fcmp uno float %{{.*}}, %{{.*}}
+  // CHECK: %[[EITHERNAN:.*]] = or i1 %[[EISNAN]], %[[XISNAN]]
+  // CHECK: br i1 %[[EITHERNAN]], label %[[NANBB:[^,]+]], label %[[NOTNANBB:[^,]+]]
+  // NaNBB: NaN == anything is always false; skip cmpxchg.
+  // CHECK: [[NANBB]]:
+  // CHECK-NEXT: br label %[[EXIT:[^ ]+]]
+  // NotNaNBB: check ±0.0.
+  // CHECK: [[NOTNANBB]]:
+  // CHECK: %[[XISZERO:.*]] = fcmp oeq float %{{.*}}, 0.000000e+00
+  // CHECK: %[[EISZERO:.*]] = fcmp oeq float %[[EF]], 0.000000e+00
+  // CHECK: %[[BOTH:.*]] = and i1 %[[XISZERO]], %[[EISZERO]]
+  // CHECK: br i1 %[[BOTH]], label %[[ZERO:[^,]+]], label %[[NORMAL:[^,]+]]
+  // ZeroBB: cmpxchg with x's bit-pattern; goto end.
+  // CHECK: [[ZERO]]:
+  // CHECK: cmpxchg ptr %[[XF]], i32 %{{.*}}, i32 %[[DBC]] monotonic monotonic
+  // CHECK-NEXT: extractvalue
+  // CHECK-NEXT: extractvalue
+  // CHECK-NEXT: br label %[[EXIT]]
+  // NormalBB: original cmpxchg; goto end.
+  // CHECK: [[NORMAL]]:
+  // CHECK: cmpxchg ptr %[[XF]], i32 %[[EBC]], i32 %[[DBC]] monotonic monotonic
+  // CHECK-NEXT: extractvalue
+  // CHECK-NEXT: extractvalue
+  // CHECK-NEXT: br label %[[EXIT]]
+  // ExitBB: phi merge (3-way: NaN, Zero, Normal).
+  // CHECK: [[EXIT]]:
+  // CHECK-NEXT: phi i32 [ %{{.*}}, %[[NANBB]] ], [ %{{.*}}, %[[ZERO]] ], [ %{{.*}}, %[[NORMAL]] ]
+  // CHECK-NEXT: phi i1 [ false, %[[NANBB]] ], [ %{{.*}}, %[[ZERO]] ], [ %{{.*}}, %[[NORMAL]] ]
+  omp.atomic.compare %xf : !llvm.ptr {
+  ^bb0(%xval : f32):
+    %cmp1 = llvm.fcmp "oeq" %xval, %ef : f32
+    %sel1 = llvm.select %cmp1, %df, %xval : i1, f32
+    omp.yield(%sel1 : f32)
+  }
+
+  // Complex equality  →  bitcasted integer cmpxchg with consistent alignment
+  // CHECK: %[[EALLOCA:.*]] = alloca { float, float }, align [[ALIGN:[0-9]+]]
+  // CHECK: %[[DALLOCA:.*]] = alloca { float, float }, align [[ALIGN]]
+  // CHECK: store { float, float } %[[EC]], ptr %[[EALLOCA]], align [[ALIGN]]
+  // CHECK: %[[EINT:.*]] = load i64, ptr %[[EALLOCA]], align [[ALIGN]]
+  // CHECK: store { float, float } %[[DC]], ptr %[[DALLOCA]], align [[ALIGN]]
+  // CHECK: %[[DINT:.*]] = load i64, ptr %[[DALLOCA]], align [[ALIGN]]
+  // CHECK: cmpxchg ptr %[[XC]], i64 %[[EINT]], i64 %[[DINT]] monotonic monotonic, align [[ALIGN]]
+  omp.atomic.compare %xc : !llvm.ptr {
+  ^bb0(%xval : !llvm.struct<(f32, f32)>):
+    %re_x = llvm.extractvalue %xval[0] : !llvm.struct<(f32, f32)>
+    %re_e = llvm.extractvalue %ec[0] : !llvm.struct<(f32, f32)>
+    %cmp_re = llvm.fcmp "oeq" %re_x, %re_e : f32
+    %im_x = llvm.extractvalue %xval[1] : !llvm.struct<(f32, f32)>
+    %im_e = llvm.extractvalue %ec[1] : !llvm.struct<(f32, f32)>
+    %cmp_im = llvm.fcmp "oeq" %im_x, %im_e : f32
+    %cmp = llvm.and %cmp_re, %cmp_im : i1
+    %sel = llvm.select %cmp, %dc, %xval : i1, !llvm.struct<(f32, f32)>
+    omp.yield(%sel : !llvm.struct<(f32, f32)>)
+  }
+
+  // Integer x < e  →  atomicrmw max (signed)
+  // CHECK: atomicrmw max ptr %[[X]], i32 %[[E]] monotonic
+  omp.atomic.compare %x : !llvm.ptr {
+  ^bb0(%xval : i32):
+    %cmp2 = llvm.icmp "slt" %xval, %e : i32
+    %sel2 = llvm.select %cmp2, %e, %xval : i1, i32
+    omp.yield(%sel2 : i32)
+  }
+
+  // Integer x > e  →  atomicrmw min (signed)
+  // CHECK: atomicrmw min ptr %[[X]], i32 %[[E]] monotonic
+  omp.atomic.compare %x : !llvm.ptr {
+  ^bb0(%xval : i32):
+    %cmp3 = llvm.icmp "sgt" %xval, %e : i32
+    %sel3 = llvm.select %cmp3, %e, %xval : i1, i32
+    omp.yield(%sel3 : i32)
+  }
+
+  // Float x < e  →  atomicrmw fmax (reversed)
+  // CHECK: atomicrmw fmax ptr %[[XF]], float %[[EF]] monotonic, align 4
+  omp.atomic.compare %xf : !llvm.ptr {
+  ^bb0(%xval : f32):
+    %cmp4 = llvm.fcmp "olt" %xval, %ef : f32
+    %sel4 = llvm.select %cmp4, %ef, %xval : i1, f32
+    omp.yield(%sel4 : f32)
+  }
+
+  // Float x > e  →  atomicrmw fmin (reversed)
+  // CHECK: atomicrmw fmin ptr %[[XF]], float %[[EF]] monotonic, align 4
+  omp.atomic.compare %xf : !llvm.ptr {
+  ^bb0(%xval : f32):
+    %cmp5 = llvm.fcmp "ogt" %xval, %ef : f32
+    %sel5 = llvm.select %cmp5, %ef, %xval : i1, f32
+    omp.yield(%sel5 : f32)
+  }
+
+  // Integer equality with seq_cst  →  cmpxchg + flush
+  // CHECK: cmpxchg ptr %[[X]], i32 %[[E]], i32 %[[D]] seq_cst seq_cst
+  // CHECK: call void @__kmpc_flush(ptr @{{.*}})
+  omp.atomic.compare memory_order(seq_cst) %x : !llvm.ptr {
+  ^bb0(%xval6 : i32):
+    %cmp6 = llvm.icmp "eq" %xval6, %e : i32
+    %sel6 = llvm.select %cmp6, %d, %xval6 : i1, i32
+    omp.yield(%sel6 : i32)
+  }
+
+  // Complex equality with seq_cst  →  cmpxchg + flush
+  // CHECK: cmpxchg ptr %[[XC]], i64 %{{.*}}, i64 %{{.*}} seq_cst seq_cst
+  // CHECK: call void @__kmpc_flush(ptr @{{.*}})
+  omp.atomic.compare memory_order(seq_cst) %xc : !llvm.ptr {
+  ^bb0(%xval7 : !llvm.struct<(f32, f32)>):
+    %re_x7 = llvm.extractvalue %xval7[0] : !llvm.struct<(f32, f32)>
+    %re_e7 = llvm.extractvalue %ec[0] : !llvm.struct<(f32, f32)>
+    %cmp_re7 = llvm.fcmp "oeq" %re_x7, %re_e7 : f32
+    %im_x7 = llvm.extractvalue %xval7[1] : !llvm.struct<(f32, f32)>
+    %im_e7 = llvm.extractvalue %ec[1] : !llvm.struct<(f32, f32)>
+    %cmp_im7 = llvm.fcmp "oeq" %im_x7, %im_e7 : f32
+    %cmp7 = llvm.and %cmp_re7, %cmp_im7 : i1
+    %sel7 = llvm.select %cmp7, %dc, %xval7 : i1, !llvm.struct<(f32, f32)>
+    omp.yield(%sel7 : !llvm.struct<(f32, f32)>)
+  }
+
+  // pointer-associated integer target:
+  // CHECK: %[[EPVAL:.*]] = load i32, ptr %[[EP]]
+  // CHECK: %[[DPVAL:.*]] = load i32, ptr %[[DP]]
+  // CHECK: cmpxchg ptr %[[XP]], i32 %[[EPVAL]], i32 %[[DPVAL]] monotonic monotonic
+  %eval = llvm.load %ep : !llvm.ptr -> i32
+  %dval = llvm.load %dp : !llvm.ptr -> i32
+  omp.atomic.compare %xp : !llvm.ptr {
+  ^bb0(%xval : i32):
+    %cmp = llvm.icmp "eq" %xval, %eval : i32
+    %sel = llvm.select %cmp, %dval, %xval : i1, i32
+    omp.yield(%sel : i32)
+  }
+
+  llvm.return
+}
+
+// -----
+
+// CHECK-LABEL: @omp_atomic_compare_float_neg_zero
+// CHECK-SAME: (ptr %[[XF:.*]], float %[[EF:.*]], float %[[DF:.*]])
+// Verify NaN guard + ±0.0 handling.
+llvm.func @omp_atomic_compare_float_neg_zero(%xf : !llvm.ptr, %ef : f32, %df : f32) {
+  // CHECK: %[[EBC:.*]] = bitcast float %[[EF]] to i32
+  // CHECK: %[[DBC:.*]] = bitcast float %[[DF]] to i32
+  // CHECK: load atomic i32, ptr %[[XF]] monotonic
+  // NaN check: skip cmpxchg if either E or X is NaN.
+  // CHECK: %[[EISNAN:.*]] = fcmp uno float %[[EF]], %[[EF]]
+  // CHECK: %[[XISNAN:.*]] = fcmp uno float %{{.*}}, %{{.*}}
+  // CHECK: %[[EITHERNAN:.*]] = or i1 %[[EISNAN]], %[[XISNAN]]
+  // CHECK: br i1 %[[EITHERNAN]], label %[[NANBB:[^,]+]], label %[[NOTNANBB:[^,]+]]
+  // NaNBB: NaN == anything is always false; skip cmpxchg.
+  // CHECK: [[NANBB]]:
+  // CHECK-NEXT: br label %[[EXIT:[^ ]+]]
+  // NotNaNBB: check ±0.0.
+  // CHECK: [[NOTNANBB]]:
+  // CHECK: %[[XISZERO:.*]] = fcmp oeq float %{{.*}}, 0.000000e+00
+  // CHECK: %[[EISZERO:.*]] = fcmp oeq float %[[EF]], 0.000000e+00
+  // CHECK: %[[BOTH:.*]] = and i1 %[[XISZERO]], %[[EISZERO]]
+  // CHECK: br i1 %[[BOTH]], label %[[ZERO:[^,]+]], label %[[NORMAL:[^,]+]]
+  // ZeroBB: cmpxchg with X's loaded bit-pattern; goto end directly.
+  // CHECK: [[ZERO]]:
+  // CHECK: cmpxchg ptr %[[XF]], i32 %{{.*}}, i32 %[[DBC]] monotonic monotonic
+  // CHECK-NEXT: extractvalue
+  // CHECK-NEXT: extractvalue
+  // CHECK-NEXT: br label %[[EXIT]]
+  // NormalBB: original bitwise cmpxchg; goto end.
+  // CHECK: [[NORMAL]]:
+  // CHECK: cmpxchg ptr %[[XF]], i32 %[[EBC]], i32 %[[DBC]] monotonic monotonic
+  // CHECK-NEXT: extractvalue
+  // CHECK-NEXT: extractvalue
+  // CHECK-NEXT: br label %[[EXIT]]
+  // ExitBB: phi merge (3-way: NaN, Zero, Normal).
+  // CHECK: [[EXIT]]:
+  // CHECK-NEXT: phi i32 [ %{{.*}}, %[[NANBB]] ], [ %{{.*}}, %[[ZERO]] ], [ %{{.*}}, %[[NORMAL]] ]
+  // CHECK-NEXT: phi i1 [ false, %[[NANBB]] ], [ %{{.*}}, %[[ZERO]] ], [ %{{.*}}, %[[NORMAL]] ]
+  omp.atomic.compare %xf : !llvm.ptr {
+  ^bb0(%xval : f32):
+    %cmp = llvm.fcmp "oeq" %xval, %ef : f32
+    %sel = llvm.select %cmp, %df, %xval : i1, f32
+    omp.yield(%sel : f32)
+  }
+  llvm.return
+}
+
+// -----
+
 // CHECK-LABEL: @omp_sections_empty
 llvm.func @omp_sections_empty() -> () {
   omp.sections {



More information about the Mlir-commits mailing list