[flang-commits] [flang] [llvm] [mlir] [flang][OpenMP] Support for "atomic compare capture" (PR #202315)

via flang-commits flang-commits at lists.llvm.org
Tue Jul 21 00:06:47 PDT 2026


https://github.com/SunilKuravinakop updated https://github.com/llvm/llvm-project/pull/202315

>From 216cbd2e710ce3c725bb51c5606bea4ba84a33a3 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Sat, 6 Jun 2026 14:38:54 -0500
Subject: [PATCH 01/11] Support for "atomic compare capture".

---
 flang/lib/Lower/OpenMP/Atomic.cpp             |  88 ++++---
 .../Interfaces/AtomicInterfaces.td            |   9 +-
 mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td |  10 +
 mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp  |   6 +
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      | 216 ++++++++++++++----
 5 files changed, 250 insertions(+), 79 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/Atomic.cpp b/flang/lib/Lower/OpenMP/Atomic.cpp
index 9d711b92bb520..5e02d3fb10dbb 100644
--- a/flang/lib/Lower/OpenMP/Atomic.cpp
+++ b/flang/lib/Lower/OpenMP/Atomic.cpp
@@ -558,16 +558,45 @@ void Fortran::lower::omp::lowerAtomic(
   int action1 = analysis.op1.what & analysis.Action;
   memOrder = makeValidForAction(memOrder, action0, action1, version);
 
+  // --- Shared capture scaffolding ---
+  mlir::Operation *captureOp = nullptr;
+  fir::FirOpBuilder::InsertPoint preAt = builder.saveInsertionPoint();
+  fir::FirOpBuilder::InsertPoint atomicAt, postAt;
+
+  if (construct.IsCapture()) {
+    assert(action0 != analysis.None && action1 != analysis.None &&
+           "Expexcing two actions");
+    (void)action0;
+    (void)action1;
+    captureOp = mlir::omp::AtomicCaptureOp::create(
+        builder, loc, hint, makeMemOrderAttr(converter, memOrder));
+    // Set the non-atomic insertion point to before the atomic.capture.
+    preAt = getInsertionPointBefore(captureOp);
+
+    mlir::Block *block = builder.createBlock(&captureOp->getRegion(0));
+    builder.setInsertionPointToEnd(block);
+    // Set the atomic insertion point to before the terminator inside
+    // atomic.capture.
+    mlir::Operation *term = mlir::omp::TerminatorOp::create(builder, loc);
+    atomicAt = getInsertionPointBefore(term);
+    postAt = getInsertionPointAfter(captureOp);
+    hint = nullptr;
+    memOrder = std::nullopt;
+  }
+
   if (auto *cond = get(analysis.cond)) {
     // atomic compare: if (x == e) x = d
     // e : expecteVal
     // d : desiredVal
 
-    // Check for compound clauses (fail, capture, weak) that are not yet
+    // Restore insertion point so pre-processing code (e.g. computing
+    // expectedVal) is emitted before the capture op, not after the terminator.
+    builder.restoreInsertionPoint(preAt);
+
+    // Check for compound clauses (fail, 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");
@@ -616,6 +645,17 @@ void Fortran::lower::omp::lowerAtomic(
       expectedVal = builder.createConvert(loc, elemTypeOfX, expectedVal);
     }
 
+    // If this is a compare+capture, generate the read op first.
+    if (construct.IsCapture()) {
+      assert(get(analysis.op0.assign) && (analysis.op0.what & analysis.Read) &&
+             "Expected a read operation for compare capture");
+      mlir::Operation *readOp = genAtomicRead(
+          converter, semaCtx, loc, stmtCtx, atomAddr, atom,
+          *get(analysis.op0.assign), hint, memOrder, preAt, atomicAt, postAt);
+      assert(readOp && "Should have created an atomic read operation");
+      builder.setInsertionPointAfter(readOp);
+    }
+
     mlir::UnitAttr weakAttr = nullptr;
     mlir::Operation *atomicOp = mlir::omp::AtomicCompareOp::create(
         builder, loc, atomAddr, weakAttr, hint,
@@ -679,34 +719,9 @@ void Fortran::lower::omp::lowerAtomic(
     // 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();
-    fir::FirOpBuilder::InsertPoint atomicAt, postAt;
-
-    if (construct.IsCapture()) {
-      // Capturing operation.
-      assert(action0 != analysis.None && action1 != analysis.None &&
-             "Expexcing two actions");
-      (void)action0;
-      (void)action1;
-      captureOp = mlir::omp::AtomicCaptureOp::create(
-          builder, loc, hint, makeMemOrderAttr(converter, memOrder));
-      // Set the non-atomic insertion point to before the atomic.capture.
-      preAt = getInsertionPointBefore(captureOp);
-
-      mlir::Block *block = builder.createBlock(&captureOp->getRegion(0));
-      builder.setInsertionPointToEnd(block);
-      // Set the atomic insertion point to before the terminator inside
-      // atomic.capture.
-      mlir::Operation *term = mlir::omp::TerminatorOp::create(builder, loc);
-      atomicAt = getInsertionPointBefore(term);
-      postAt = getInsertionPointAfter(captureOp);
-      hint = nullptr;
-      memOrder = std::nullopt;
-    } else {
+    if (!construct.IsCapture()) {
       // Non-capturing operation.
       assert(action0 != analysis.None && action1 == analysis.None &&
              "Expexcing single action");
@@ -729,16 +744,13 @@ void Fortran::lower::omp::lowerAtomic(
           *get(analysis.op1.assign), hint, memOrder, preAt, atomicAt, postAt);
     }
 
-    if (construct.IsCapture()) {
-      // If this is a capture operation, the first/second ops will be inside
-      // of it. Set the insertion point to past the capture op itself.
-      builder.restoreInsertionPoint(postAt);
-    } else {
-      if (secondOp) {
-        builder.setInsertionPointAfter(secondOp);
-      } else {
-        builder.setInsertionPointAfter(firstOp);
-      }
+    if (!construct.IsCapture()) {
+      builder.setInsertionPointAfter(secondOp ? secondOp : firstOp);
     }
   }
+
+  // Shared capture cleanup.
+  if (construct.IsCapture()) {
+    builder.restoreInsertionPoint(postAt);
+  }
 }
diff --git a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
index abb21705b3c1c..8c9015f05bb72 100644
--- a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
+++ b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
@@ -289,10 +289,12 @@ def AtomicCaptureOpInterface : OpInterface<"AtomicCaptureOpInterface"> {
         auto secondReadStmt = dyn_cast<AtomicReadOpInterface>(secondOp);
         auto secondUpdateStmt = dyn_cast<AtomicUpdateOpInterface>(secondOp);
         auto secondWriteStmt = dyn_cast<AtomicWriteOpInterface>(secondOp);
+        auto secondCompareStmt = dyn_cast<AtomicCompareOpInterface>(secondOp);
 
         if (!((firstUpdateStmt && secondReadStmt) ||
               (firstReadStmt && secondUpdateStmt) ||
-              (firstReadStmt && secondWriteStmt)))
+              (firstReadStmt && secondWriteStmt) ||
+              (firstReadStmt && secondCompareStmt)))
           return ops.front().emitError()
                 << "invalid sequence of operations in the capture region";
         if (firstUpdateStmt && secondReadStmt &&
@@ -310,6 +312,11 @@ def AtomicCaptureOpInterface : OpInterface<"AtomicCaptureOpInterface"> {
           return firstReadStmt.emitError()
                 << "captured variable in atomic.read must be updated in "
                     "second operation";
+        if (firstReadStmt && secondCompareStmt &&
+            firstReadStmt.getX() != secondCompareStmt.getX())
+          return firstReadStmt.emitError()
+                << "captured variable in atomic.read must be updated in "
+                    "second operation";
 
         return mlir::success();
       }]
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index 0962b330e2f23..1241abc10298f 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -1928,6 +1928,12 @@ def AtomicCaptureOp : OpenMP_Op<"atomic.capture", traits = [
         omp.atomic.write ...
         omp.terminator
       }
+
+      omp.atomic.capture {
+        omp.atomic.read ...
+        omp.atomic.compare ...
+        omp.terminator
+      }
     ```
   }] # clausesDescription;
 
@@ -1946,6 +1952,10 @@ def AtomicCaptureOp : OpenMP_Op<"atomic.capture", traits = [
     /// Returns the `atomic.update` operation inside the region, if any.
     /// Otherwise, it returns nullptr.
     AtomicUpdateOp getAtomicUpdateOp();
+
+    /// Returns the `atomic.compare` operation inside the region, if any.
+    /// Otherwise, it returns nullptr.
+    AtomicCompareOp getAtomicCompareOp();
   }] # clausesExtraClassDeclaration;
 
   let hasRegionVerifier = 1;
diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index db5fd8f2e3230..0eafd0a267b97 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -4615,6 +4615,12 @@ AtomicUpdateOp AtomicCaptureOp::getAtomicUpdateOp() {
   return dyn_cast<AtomicUpdateOp>(getSecondOp());
 }
 
+AtomicCompareOp AtomicCaptureOp::getAtomicCompareOp() {
+  if (auto op = dyn_cast<AtomicCompareOp>(getFirstOp()))
+    return op;
+  return dyn_cast<AtomicCompareOp>(getSecondOp());
+}
+
 LogicalResult AtomicCaptureOp::verify() {
   return verifySynchronizationHint(*this, getHint());
 }
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 07fa92bdabe50..4928b288adb65 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -4795,6 +4795,43 @@ convertOmpAtomicUpdate(omp::AtomicUpdateOp &opInst,
   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;
+  }
+}
+
 static LogicalResult
 convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
                         llvm::IRBuilderBase &builder,
@@ -4803,13 +4840,149 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
   if (failed(checkImplementationStatus(*atomicCaptureOp)))
     return failure();
 
+  omp::AtomicUpdateOp atomicUpdateOp = atomicCaptureOp.getAtomicUpdateOp();
+  omp::AtomicWriteOp atomicWriteOp = atomicCaptureOp.getAtomicWriteOp();
+  omp::AtomicCompareOp atomicCompareOp = atomicCaptureOp.getAtomicCompareOp();
+
+  // If the capture contains an atomic.compare, delegate to
+  // createAtomicCompare with the capture variable (V) set.
+  if (atomicCompareOp) {
+    omp::AtomicReadOp atomicReadOp = atomicCaptureOp.getAtomicReadOp();
+    assert(atomicReadOp && "expected atomic.read in capture+compare");
+
+    Region &region = atomicCompareOp.getRegion();
+    Block &block = region.front();
+
+    llvm::Type *llvmXElementType =
+        moduleTranslation.convertType(block.getArgument(0).getType());
+    llvm::Value *llvmX = moduleTranslation.lookupValue(atomicCompareOp.getX());
+    llvm::Value *llvmV = moduleTranslation.lookupValue(atomicReadOp.getV());
+
+    bool isSigned = false;
+    llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {
+        llvmX, llvmXElementType, isSigned, /*IsVolatile=*/false};
+    llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicV = {
+        llvmV, llvmXElementType, /*isSigned=*/false, /*IsVolatile=*/false};
+    llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicR = {nullptr, nullptr, false,
+                                                        false};
+
+    llvm::AtomicOrdering atomicOrdering =
+        convertAtomicOrdering(atomicCaptureOp.getMemoryOrder());
+
+    // Pre-translate non-pattern operations inside the compare region.
+    auto isAtomicComparePatternOp = [](Operation &op) {
+      return llvm::isa<LLVM::ICmpOp, LLVM::FCmpOp, LLVM::SelectOp, LLVM::AndOp,
+                       LLVM::OrOp>(op);
+    };
+    for (Operation &op : block.without_terminator()) {
+      if (isAtomicComparePatternOp(op))
+        continue;
+      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");
+    }
+
+    auto materializeValue = [&](mlir::Value val) -> llvm::Value * {
+      if (llvm::Value *existing = moduleTranslation.lookupValue(val))
+        return existing;
+      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;
+    };
+
+    // Extract comparison predicate, eVal, and dVal from the region.
+    llvm::omp::OMPAtomicCompareOp compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
+    llvm::Value *eVal = nullptr;
+    llvm::Value *dVal = nullptr;
+    bool isXBinopExpr = false;
+
+    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);
+
+        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());
+      }
+    }
+
+    if (!eVal)
+      return atomicCompareOp.emitError(
+          "failed to extract expected value (e) from atomic compare region");
+    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]);
+    }
+
+    llvmAtomicX.IsSigned = isSigned;
+
+    llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
+    bool isPostfixUpdate = true;
+
+    bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
+    llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
+        ompBuilder->createAtomicCompare(ompLoc, llvmAtomicX, llvmAtomicV,
+                                        llvmAtomicR, eVal, dVal, atomicOrdering,
+                                        compareOp, isXBinopExpr,
+                                        isPostfixUpdate, /*IsFailOnly=*/false);
+    ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
+
+    if (failed(handleError(afterIP, *atomicCaptureOp)))
+      return failure();
+
+    builder.restoreIP(*afterIP);
+    return success();
+  }
+
   mlir::Value mlirExpr;
   bool isXBinopExpr = false, isPostfixUpdate = false;
   llvm::AtomicRMWInst::BinOp binop = llvm::AtomicRMWInst::BinOp::BAD_BINOP;
 
-  omp::AtomicUpdateOp atomicUpdateOp = atomicCaptureOp.getAtomicUpdateOp();
-  omp::AtomicWriteOp atomicWriteOp = atomicCaptureOp.getAtomicWriteOp();
-
   assert((atomicUpdateOp || atomicWriteOp) &&
          "internal op must be an atomic.update or atomic.write op");
 
@@ -4896,43 +5069,6 @@ 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

>From 7d3293944598e8090a64cdec2ac3c791a64b73f1 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Mon, 8 Jun 2026 05:12:22 -0500
Subject: [PATCH 02/11] Adding tests for "atomic compare capture".

---
 .../Integration/OpenMP/atomic-compare.f90     | 32 +++++++++++
 flang/test/Lower/OpenMP/atomic-compare.f90    | 56 +++++++++++++++++++
 flang/test/Parser/OpenMP/atomic-unparse.f90   | 29 ++++++++++
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      |  9 +--
 mlir/test/Dialect/OpenMP/ops.mlir             | 48 ++++++++++++++++
 mlir/test/Target/LLVMIR/openmp-llvm.mlir      | 41 ++++++++++++++
 6 files changed, 211 insertions(+), 4 deletions(-)

diff --git a/flang/test/Integration/OpenMP/atomic-compare.f90 b/flang/test/Integration/OpenMP/atomic-compare.f90
index 249fb0dd8fa64..650f64b80af12 100644
--- a/flang/test/Integration/OpenMP/atomic-compare.f90
+++ b/flang/test/Integration/OpenMP/atomic-compare.f90
@@ -260,3 +260,35 @@ subroutine atomic_compare_weak(x, e, d)
   if (x == e) x = d
 end 
 
+! Integer equality compare+capture: cmpxchg + store old value
+!CHECK-LABEL: define void @atomic_compare_capture_int_eq_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]], ptr noalias %[[V:.*]])
+!CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]]
+!CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]]
+!CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
+!CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+!CHECK: store i32 %[[OLD]], ptr %[[V]]
+subroutine atomic_compare_capture_int_eq(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture
+    v = x
+    if (x == e) x = d
+  !$omp end atomic
+end
+
+! Compare+capture with clause order reversed: capture compare
+!CHECK-LABEL: define void @atomic_capture_compare_int_eq_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]], ptr noalias %[[V:.*]])
+!CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]]
+!CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]]
+!CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
+!CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+!CHECK: store i32 %[[OLD]], ptr %[[V]]
+subroutine atomic_capture_compare_int_eq(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic capture compare
+    v = x
+    if (x == e) x = d
+  !$omp end atomic
+end
+
diff --git a/flang/test/Lower/OpenMP/atomic-compare.f90 b/flang/test/Lower/OpenMP/atomic-compare.f90
index ac70edbed4e60..752a221aa538d 100644
--- a/flang/test/Lower/OpenMP/atomic-compare.f90
+++ b/flang/test/Lower/OpenMP/atomic-compare.f90
@@ -161,3 +161,59 @@ subroutine atomic_compare_int_eq_weak(x, e, d)
   !$omp atomic compare weak
   if (x .eq. e) x = d
 end
+
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_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-SAME:    %[[V:.*]]: !fir.ref<i32> {fir.bindc_name = "v"})
+! CHECK:         %[[D_DECL:.*]]:2 = hlfir.declare %[[D]] {{.*}}
+! CHECK:         %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK:         %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
+! CHECK:         %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK:         %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.read %[[V_DECL]]#0 = %[[X_DECL]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:           omp.atomic.compare %[[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:           }
+! CHECK:         }
+subroutine atomic_compare_capture_int_eq(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture
+    v = x
+    if (x .eq. e) x = d
+  !$omp end atomic
+end
+
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_int_gt(
+! 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-SAME:    %[[V:.*]]: !fir.ref<i32> {fir.bindc_name = "v"})
+! CHECK:         %[[D_DECL:.*]]:2 = hlfir.declare %[[D]] {{.*}}
+! CHECK:         %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK:         %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
+! CHECK:         %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK:         %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.read %[[V_DECL]]#0 = %[[X_DECL]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:           omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<i32> {
+! CHECK:           ^bb0(%[[XVAL:.*]]: i32):
+! CHECK:             %[[CMP:.*]] = arith.cmpi sgt, %[[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:           } {{.*}}weak{{.*}}
+! CHECK:         }
+subroutine atomic_compare_capture_int_gt(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture weak
+    v = x
+    if (x > e) x = d
+  !$omp end atomic
+end
diff --git a/flang/test/Parser/OpenMP/atomic-unparse.f90 b/flang/test/Parser/OpenMP/atomic-unparse.f90
index 4f3cf0eac0338..dc0cc1a62f6c2 100644
--- a/flang/test/Parser/OpenMP/atomic-unparse.f90
+++ b/flang/test/Parser/OpenMP/atomic-unparse.f90
@@ -192,6 +192,26 @@ program main
       i = j
    end if
 
+!COMPARE CAPTURE
+!$omp atomic compare capture
+   k = i
+   if (i .eq. j) then
+      i = k
+   end if
+!$omp end atomic
+!$omp atomic capture compare
+   k = i
+   if (i .eq. j) then
+      i = k
+   end if
+!$omp end atomic
+!$omp atomic capture compare weak
+   k = i
+   if (i < j) then
+      i = k
+   end if
+!$omp end atomic
+
 !ATOMIC
 !$omp atomic
    i = j
@@ -296,6 +316,15 @@ end program main
 !CHECK: !$OMP ATOMIC WEAK COMPARE
 !CHECK: !$OMP ATOMIC COMPARE SEQ_CST WEAK
 
+!COMPARE CAPTURE
+
+!CHECK: !$OMP ATOMIC COMPARE CAPTURE
+!CHECK: !$OMP END ATOMIC
+!CHECK: !$OMP ATOMIC CAPTURE COMPARE
+!CHECK: !$OMP END ATOMIC
+!CHECK: !$OMP ATOMIC CAPTURE COMPARE WEAK
+!CHECK: !$OMP END ATOMIC
+
 !ATOMIC
 !CHECK: !$OMP ATOMIC
 !CHECK: !$OMP ATOMIC SEQ_CST
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 17f99a8c86d52..c5a07a7dc6cb2 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -4963,13 +4963,14 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
 
     llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
     bool isPostfixUpdate = true;
+    bool isWeak = atomicCompareOp.getWeak();
 
     bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
     llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
-        ompBuilder->createAtomicCompare(ompLoc, llvmAtomicX, llvmAtomicV,
-                                        llvmAtomicR, eVal, dVal, atomicOrdering,
-                                        compareOp, isXBinopExpr,
-                                        isPostfixUpdate, /*IsFailOnly=*/false);
+        ompBuilder->createAtomicCompare(
+            ompLoc, llvmAtomicX, llvmAtomicV, llvmAtomicR, eVal, dVal,
+            atomicOrdering, compareOp, isXBinopExpr, isPostfixUpdate,
+            /*IsFailOnly=*/false, isWeak);
     ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
 
     if (failed(handleError(afterIP, *atomicCaptureOp)))
diff --git a/mlir/test/Dialect/OpenMP/ops.mlir b/mlir/test/Dialect/OpenMP/ops.mlir
index acf6879906e74..289ec60551cb7 100644
--- a/mlir/test/Dialect/OpenMP/ops.mlir
+++ b/mlir/test/Dialect/OpenMP/ops.mlir
@@ -2047,6 +2047,54 @@ func.func @omp_atomic_compare(%x: memref<i32>, %e: i32, %d: i32) {
   return
 }
 
+// CHECK-LABEL: omp_atomic_compare_capture
+// CHECK-SAME: (%[[V:.*]]: memref<i32>, %[[X:.*]]: memref<i32>, %[[E:.*]]: i32, %[[D:.*]]: i32)
+func.func @omp_atomic_compare_capture(%v: memref<i32>, %x: memref<i32>, %e: i32, %d: i32) {
+  // CHECK: omp.atomic.capture {
+  // CHECK-NEXT: omp.atomic.read %[[V]] = %[[X]] : memref<i32>, memref<i32>, i32
+  // CHECK-NEXT: omp.atomic.compare %[[X]] : memref<i32> {
+  // CHECK-NEXT: ^bb0(%[[XVAL:.*]]: i32):
+  // CHECK-NEXT:   %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %[[E]] : i32
+  // CHECK-NEXT:   %[[SEL:.*]] = arith.select %[[CMP]], %[[D]], %[[XVAL]] : i32
+  // CHECK-NEXT:   omp.yield(%[[SEL]] : i32)
+  // CHECK-NEXT: }
+  // CHECK-NEXT: }
+  omp.atomic.capture {
+    omp.atomic.read %v = %x : memref<i32>, memref<i32>, i32
+    omp.atomic.compare %x : memref<i32> {
+    ^bb0(%xval: i32):
+      %cmp = arith.cmpi eq, %xval, %e : i32
+      %sel = arith.select %cmp, %d, %xval : i32
+      omp.yield(%sel : i32)
+    }
+  }
+  return
+}
+
+// CHECK-LABEL: omp_atomic_compare_capture_weak
+// CHECK-SAME: (%[[V:.*]]: memref<i32>, %[[X:.*]]: memref<i32>, %[[E:.*]]: i32, %[[D:.*]]: i32)
+func.func @omp_atomic_compare_capture_weak(%v: memref<i32>, %x: memref<i32>, %e: i32, %d: i32) {
+  // CHECK: omp.atomic.capture {
+  // CHECK-NEXT: omp.atomic.read %[[V]] = %[[X]] : memref<i32>, memref<i32>, i32
+  // CHECK-NEXT: omp.atomic.compare %[[X]] : memref<i32> {
+  // CHECK-NEXT: ^bb0(%[[XVAL:.*]]: i32):
+  // CHECK-NEXT:   %[[CMP:.*]] = arith.cmpi sgt, %[[XVAL]], %[[E]] : i32
+  // CHECK-NEXT:   %[[SEL:.*]] = arith.select %[[CMP]], %[[D]], %[[XVAL]] : i32
+  // CHECK-NEXT:   omp.yield(%[[SEL]] : i32)
+  // CHECK-NEXT: } {weak}
+  // CHECK-NEXT: }
+  omp.atomic.capture {
+    omp.atomic.read %v = %x : memref<i32>, memref<i32>, i32
+    omp.atomic.compare %x : memref<i32> {
+    ^bb0(%xval: i32):
+      %cmp = arith.cmpi sgt, %xval, %e : i32
+      %sel = arith.select %cmp, %d, %xval : i32
+      omp.yield(%sel : i32)
+    } {weak}
+  }
+  return
+}
+
 // CHECK-LABEL: omp_sectionsop
 func.func @omp_sectionsop(%data_var1 : memref<i32>, %data_var2 : memref<i32>,
                      %data_var3 : memref<i32>, %redn_var : !llvm.ptr) {
diff --git a/mlir/test/Target/LLVMIR/openmp-llvm.mlir b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
index 1890eaa6a3f0b..6ce57b304bfd9 100644
--- a/mlir/test/Target/LLVMIR/openmp-llvm.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
@@ -2795,6 +2795,47 @@ llvm.func @omp_atomic_compare_float_neg_zero(%xf : !llvm.ptr, %ef : f32, %df : f
 
 // -----
 
+// CHECK-LABEL: @omp_atomic_compare_capture_int_eq
+// CHECK-SAME: (ptr %[[X:.*]], ptr %[[V:.*]], i32 %[[E:.*]], i32 %[[D:.*]])
+llvm.func @omp_atomic_compare_capture_int_eq(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32, %d : i32) {
+  // Integer equality compare+capture → cmpxchg + extractvalue + store
+  // CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[E]], i32 %[[D]] monotonic monotonic
+  // CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+  // CHECK: store i32 %[[OLD]], ptr %[[V]]
+  omp.atomic.capture {
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+    omp.atomic.compare %x : !llvm.ptr {
+    ^bb0(%xval : i32):
+      %cmp = llvm.icmp "eq" %xval, %e : i32
+      %sel = llvm.select %cmp, %d, %xval : i1, i32
+      omp.yield(%sel : i32)
+    }
+  }
+  llvm.return
+}
+
+// -----
+
+// CHECK-LABEL: @omp_atomic_compare_capture_weak_int_eq
+// CHECK-SAME: (ptr %[[X:.*]], ptr %[[V:.*]], i32 %[[E:.*]], i32 %[[D:.*]])
+llvm.func @omp_atomic_compare_capture_weak_int_eq(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32, %d : i32) {
+  // Integer equality compare+capture → cmpxchg + extractvalue + store
+  // CHECK: %[[RES:.*]] = cmpxchg weak ptr %[[X]], i32 %[[E]], i32 %[[D]] monotonic monotonic
+  // CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+  // CHECK: store i32 %[[OLD]], ptr %[[V]]
+  omp.atomic.capture {
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+    omp.atomic.compare %x : !llvm.ptr {
+    ^bb0(%xval : i32):
+      %cmp = llvm.icmp "eq" %xval, %e : i32
+      %sel = llvm.select %cmp, %d, %xval : i1, i32
+      omp.yield(%sel : i32)
+    } {weak}
+  }
+  llvm.return
+}
+// -----
+
 // CHECK-LABEL: @omp_sections_empty
 llvm.func @omp_sections_empty() -> () {
   omp.sections {

>From f84987dea68a1dedc6b61bf0c9f5a2e24a0a1b40 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Tue, 9 Jun 2026 04:42:48 -0500
Subject: [PATCH 03/11] Minor changes incorporating feedback.

---
 flang/lib/Lower/OpenMP/Atomic.cpp                             | 4 ++--
 .../Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td    | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/Atomic.cpp b/flang/lib/Lower/OpenMP/Atomic.cpp
index c0f97aada637d..aa497b838e5b6 100644
--- a/flang/lib/Lower/OpenMP/Atomic.cpp
+++ b/flang/lib/Lower/OpenMP/Atomic.cpp
@@ -567,7 +567,7 @@ void Fortran::lower::omp::lowerAtomic(
 
   if (construct.IsCapture()) {
     assert(action0 != analysis.None && action1 != analysis.None &&
-           "Expexcing two actions");
+           "Expecting two actions");
     (void)action0;
     (void)action1;
     captureOp = mlir::omp::AtomicCaptureOp::create(
@@ -730,7 +730,7 @@ void Fortran::lower::omp::lowerAtomic(
     if (!construct.IsCapture()) {
       // Non-capturing operation.
       assert(action0 != analysis.None && action1 == analysis.None &&
-             "Expexcing single action");
+             "Expecting single action");
       assert(!(analysis.op0.what & analysis.Condition));
       postAt = atomicAt = preAt;
     }
diff --git a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
index 8c9015f05bb72..dc5b04126b757 100644
--- a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
+++ b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
@@ -315,7 +315,7 @@ def AtomicCaptureOpInterface : OpInterface<"AtomicCaptureOpInterface"> {
         if (firstReadStmt && secondCompareStmt &&
             firstReadStmt.getX() != secondCompareStmt.getX())
           return firstReadStmt.emitError()
-                << "captured variable in atomic.read must be updated in "
+                << "captured variable in atomic.read must be compared/updated in "
                     "second operation";
 
         return mlir::success();

>From ca24a147920d24c188127dbbe37ca62bcaeab792 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Tue, 23 Jun 2026 14:18:17 -0500
Subject: [PATCH 04/11] Handling test cases mentionend in OpenMP spec.

---
 flang/lib/Lower/OpenMP/Atomic.cpp             | 61 ++++++++++++++++---
 llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp     |  7 ++-
 .../Interfaces/AtomicInterfaces.td            | 11 +++-
 mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td |  2 +
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      | 12 ++--
 5 files changed, 74 insertions(+), 19 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/Atomic.cpp b/flang/lib/Lower/OpenMP/Atomic.cpp
index aa497b838e5b6..209871abd52b6 100644
--- a/flang/lib/Lower/OpenMP/Atomic.cpp
+++ b/flang/lib/Lower/OpenMP/Atomic.cpp
@@ -571,7 +571,8 @@ void Fortran::lower::omp::lowerAtomic(
     (void)action0;
     (void)action1;
     captureOp = mlir::omp::AtomicCaptureOp::create(
-        builder, loc, hint, makeMemOrderAttr(converter, memOrder));
+        builder, loc, hint, makeMemOrderAttr(converter, memOrder),
+        /*fail_only=*/nullptr);
     // Set the non-atomic insertion point to before the atomic.capture.
     preAt = getInsertionPointBefore(captureOp);
 
@@ -646,15 +647,45 @@ void Fortran::lower::omp::lowerAtomic(
       expectedVal = builder.createConvert(loc, elemTypeOfX, expectedVal);
     }
 
-    // If this is a compare+capture, generate the read op first.
+    // If this is a compare+capture, determine the ordering of ops.
+    // Pattern 1 (prefix): v = x; if (x == e) x = d  → read first
+    // Pattern 2 (postfix): if (x == e) x = d; v = x → compare first
+    // Pattern 3 (fail-only): if (x == e) x = d; else v = x → read on failure
+    bool isPostfixCapture = false;
+    bool isFailOnly = false;
+    const evaluate::Assignment *readAssign = nullptr;
     if (construct.IsCapture()) {
-      assert(get(analysis.op0.assign) && (analysis.op0.what & analysis.Read) &&
-             "Expected a read operation for compare capture");
-      mlir::Operation *readOp = genAtomicRead(
-          converter, semaCtx, loc, stmtCtx, atomAddr, atom,
-          *get(analysis.op0.assign), hint, memOrder, preAt, atomicAt, postAt);
-      assert(readOp && "Should have created an atomic read operation");
-      builder.setInsertionPointAfter(readOp);
+      // Determine which op is the read and check for fail-only (IfFalse).
+      int readWhat = 0;
+      if (analysis.op0.what & analysis.Read) {
+        readAssign = get(analysis.op0.assign);
+        readWhat = analysis.op0.what;
+      } else if (analysis.op1.what & analysis.Read) {
+        readAssign = get(analysis.op1.assign);
+        readWhat = analysis.op1.what;
+        isPostfixCapture = true;
+      }
+      assert(readAssign && "Expected a read assignment for compare capture");
+
+      // Check if the read is conditioned on comparison failure (else branch).
+      if (readWhat & analysis.IfFalse)
+        isFailOnly = true;
+
+      if (!isPostfixCapture && !isFailOnly) {
+        // Pattern 1 (prefix): read is first, generate it before compare.
+        mlir::Operation *readOp =
+            genAtomicRead(converter, semaCtx, loc, stmtCtx, atomAddr, atom,
+                          *readAssign, hint, memOrder, preAt, atomicAt, postAt);
+        assert(readOp && "Should have created an atomic read operation");
+        builder.setInsertionPointAfter(readOp);
+      } else {
+        // Pattern 2 (postfix) or 3 (fail-only): compare first, read after.
+        builder.restoreInsertionPoint(atomicAt);
+      }
+
+      // Set the fail_only attribute on the capture op.
+      if (isFailOnly && captureOp)
+        mlir::cast<mlir::omp::AtomicCaptureOp>(captureOp).setFailOnly(true);
     }
 
     mlir::UnitAttr weakAttr = nullptr;
@@ -725,6 +756,18 @@ void Fortran::lower::omp::lowerAtomic(
     // Generate omp.yield
     mlir::omp::YieldOp::create(builder, loc, newVal);
     builder.setInsertionPointAfter(atomicOp);
+
+    // Pattern 2 (postfix) or 3 (fail-only): compare first, read second.
+    // Generate read after compare for postfix or fail-only patterns.
+    if (construct.IsCapture() && (isPostfixCapture || isFailOnly)) {
+      fir::FirOpBuilder::InsertPoint afterCompareAt =
+          builder.saveInsertionPoint();
+      mlir::Operation *readOp = genAtomicRead(
+          converter, semaCtx, loc, stmtCtx, atomAddr, atom, *readAssign, hint,
+          memOrder, preAt, afterCompareAt, postAt);
+      assert(readOp && "Should have created an atomic read operation");
+      builder.setInsertionPointAfter(readOp);
+    }
     // END omp atomic compare
   } else {
     if (!construct.IsCapture()) {
diff --git a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
index 92eb7de0d882f..b2ccc05ad6770 100644
--- a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
+++ b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
@@ -11321,7 +11321,9 @@ OpenMPIRBuilder::InsertPointTy OpenMPIRBuilder::createAtomicCompare(
         assert(OldValue->getType() == V.ElemTy &&
                "OldValue and V must be of same type");
         if (IsPostfixUpdate) {
-          Builder.CreateStore(OldValue, V.Var, V.IsVolatile);
+          SuccessOrFail = Builder.CreateExtractValue(Result, /*Idxs=*/1);
+          Value *NewValue = Builder.CreateSelect(SuccessOrFail, D, OldValue);
+          Builder.CreateStore(NewValue, V.Var, V.IsVolatile);
         } else {
           SuccessOrFail = Builder.CreateExtractValue(Result, /*Idxs=*/1);
           if (IsFailOnly) {
@@ -11377,7 +11379,8 @@ OpenMPIRBuilder::InsertPointTy OpenMPIRBuilder::createAtomicCompare(
         assert(OldValue->getType() == V.ElemTy &&
                "OldValue and V must be of same type");
         if (IsPostfixUpdate) {
-          Builder.CreateStore(OldValue, V.Var, V.IsVolatile);
+          Value *NewValue = Builder.CreateSelect(SuccessOrFail, D, OldValue);
+          Builder.CreateStore(NewValue, V.Var, V.IsVolatile);
         } else {
           if (IsFailOnly) {
             BasicBlock *CurBB = Builder.GetInsertBlock();
diff --git a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
index dc5b04126b757..5b1527bcb84ec 100644
--- a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
+++ b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
@@ -286,6 +286,7 @@ def AtomicCaptureOpInterface : OpInterface<"AtomicCaptureOpInterface"> {
         auto &secondOp = *ops.getNextNode(firstOp);
         auto firstReadStmt = dyn_cast<AtomicReadOpInterface>(firstOp);
         auto firstUpdateStmt = dyn_cast<AtomicUpdateOpInterface>(firstOp);
+        auto firstCompareStmt = dyn_cast<AtomicCompareOpInterface>(firstOp);
         auto secondReadStmt = dyn_cast<AtomicReadOpInterface>(secondOp);
         auto secondUpdateStmt = dyn_cast<AtomicUpdateOpInterface>(secondOp);
         auto secondWriteStmt = dyn_cast<AtomicWriteOpInterface>(secondOp);
@@ -294,7 +295,8 @@ def AtomicCaptureOpInterface : OpInterface<"AtomicCaptureOpInterface"> {
         if (!((firstUpdateStmt && secondReadStmt) ||
               (firstReadStmt && secondUpdateStmt) ||
               (firstReadStmt && secondWriteStmt) ||
-              (firstReadStmt && secondCompareStmt)))
+              (firstReadStmt && secondCompareStmt) ||
+              (firstCompareStmt && secondReadStmt)))
           return ops.front().emitError()
                 << "invalid sequence of operations in the capture region";
         if (firstUpdateStmt && secondReadStmt &&
@@ -315,8 +317,13 @@ def AtomicCaptureOpInterface : OpInterface<"AtomicCaptureOpInterface"> {
         if (firstReadStmt && secondCompareStmt &&
             firstReadStmt.getX() != secondCompareStmt.getX())
           return firstReadStmt.emitError()
-                << "captured variable in atomic.read must be compared/updated in "
+                << "captured variable in atomic.read must be updated in "
                     "second operation";
+        if (firstCompareStmt && secondReadStmt &&
+            secondReadStmt.getX() != firstCompareStmt.getX())
+          return secondReadStmt.emitError()
+                << "captured variable in atomic.read must be updated in "
+                    "first operation";
 
         return mlir::success();
       }]
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index ebee887f2afd1..69c22641df6a2 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -1938,6 +1938,8 @@ def AtomicCaptureOp : OpenMP_Op<"atomic.capture", traits = [
     ```
   }] # clausesDescription;
 
+  let arguments = !con(clausesArgs, (ins UnitAttr:$fail_only));
+
   // Override region definition.
   let regions = (region SizedRegion<1>:$region);
 
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index dff1ae30e20aa..76616c7fba27a 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -4974,15 +4974,15 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
     llvmAtomicX.IsSigned = isSigned;
 
     llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
-    bool isPostfixUpdate = true;
-    bool isWeak = atomicCompareOp.getWeak();
+    bool isFailOnly = atomicCaptureOp.getFailOnly();
+    bool isPostfixUpdate = !isFailOnly;
 
     bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
     llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
-        ompBuilder->createAtomicCompare(
-            ompLoc, llvmAtomicX, llvmAtomicV, llvmAtomicR, eVal, dVal,
-            atomicOrdering, compareOp, isXBinopExpr, isPostfixUpdate,
-            /*IsFailOnly=*/false, isWeak);
+        ompBuilder->createAtomicCompare(ompLoc, llvmAtomicX, llvmAtomicV,
+                                        llvmAtomicR, eVal, dVal, atomicOrdering,
+                                        compareOp, isXBinopExpr,
+                                        isPostfixUpdate, isFailOnly);
     ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
 
     if (failed(handleError(afterIP, *atomicCaptureOp)))

>From afaf2025463e9e8cf15974747fa9e2d8b8e5fa95 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Tue, 23 Jun 2026 18:02:52 -0500
Subject: [PATCH 05/11] Adding tests into flang and mlir for the grammar
 mentionend in the spec.

---
 .../Integration/OpenMP/atomic-compare.f90     | 56 ++++++++++++++++--
 flang/test/Lower/OpenMP/atomic-compare.f90    | 59 +++++++++++++++++++
 flang/test/Parser/OpenMP/atomic-unparse.f90   | 17 ++++++
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      | 17 +++++-
 mlir/test/Dialect/OpenMP/ops.mlir             | 48 +++++++++++++++
 mlir/test/Target/LLVMIR/openmp-llvm.mlir      | 58 ++++++++++++++++--
 6 files changed, 245 insertions(+), 10 deletions(-)

diff --git a/flang/test/Integration/OpenMP/atomic-compare.f90 b/flang/test/Integration/OpenMP/atomic-compare.f90
index 650f64b80af12..da86f3ff6ef16 100644
--- a/flang/test/Integration/OpenMP/atomic-compare.f90
+++ b/flang/test/Integration/OpenMP/atomic-compare.f90
@@ -260,14 +260,17 @@ subroutine atomic_compare_weak(x, e, d)
   if (x == e) x = d
 end 
 
-! Integer equality compare+capture: cmpxchg + store old value
+! Integer equality compare+capture (prefix): v=x; if(x==e) x=d
+! v captures old value of x
 !CHECK-LABEL: define void @atomic_compare_capture_int_eq_(
 !CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]], ptr noalias %[[V:.*]])
 !CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]]
 !CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]]
 !CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
 !CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
-!CHECK: store i32 %[[OLD]], ptr %[[V]]
+!CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+!CHECK: %[[CAPTURED:.*]] = select i1 %[[SUCCESS]], i32 %[[EVAL]], i32 %[[OLD]]
+!CHECK: store i32 %[[CAPTURED]], ptr %[[V]]
 subroutine atomic_compare_capture_int_eq(x, e, d, v)
   integer :: x, e, d, v
   !$omp atomic compare capture
@@ -276,14 +279,16 @@ subroutine atomic_compare_capture_int_eq(x, e, d, v)
   !$omp end atomic
 end
 
-! Compare+capture with clause order reversed: capture compare
+! Compare+capture with clause order reversed: capture compare (still prefix read)
 !CHECK-LABEL: define void @atomic_capture_compare_int_eq_(
 !CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]], ptr noalias %[[V:.*]])
 !CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]]
 !CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]]
 !CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
 !CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
-!CHECK: store i32 %[[OLD]], ptr %[[V]]
+!CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+!CHECK: %[[CAPTURED:.*]] = select i1 %[[SUCCESS]], i32 %[[EVAL]], i32 %[[OLD]]
+!CHECK: store i32 %[[CAPTURED]], ptr %[[V]]
 subroutine atomic_capture_compare_int_eq(x, e, d, v)
   integer :: x, e, d, v
   !$omp atomic capture compare
@@ -292,3 +297,46 @@ subroutine atomic_capture_compare_int_eq(x, e, d, v)
   !$omp end atomic
 end
 
+! Postfix compare+capture: if (x == e) x = d; v = x
+! v captures new value (d if swapped, old x if not)
+!CHECK-LABEL: define void @atomic_compare_capture_postfix_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]], ptr noalias %[[V:.*]])
+!CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]]
+!CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]]
+!CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
+!CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+!CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+!CHECK: %[[NEWVAL:.*]] = select i1 %[[SUCCESS]], i32 %[[DVAL]], i32 %[[OLD]]
+!CHECK: store i32 %[[NEWVAL]], ptr %[[V]]
+subroutine atomic_compare_capture_postfix(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture
+    if (x == e) x = d
+    v = x
+  !$omp end atomic
+end
+
+! Fail-only compare+capture: if (x == e) x = d; else v = x
+! v is only written when the comparison fails
+!CHECK-LABEL: define void @atomic_compare_capture_fail_only_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]], ptr noalias %[[V:.*]])
+!CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]]
+!CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]]
+!CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
+!CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+!CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+!CHECK: br i1 %[[SUCCESS]], label %[[EXIT:.*]], label %[[CONT:.*]]
+!CHECK: {{.*}}:
+!CHECK: store i32 %[[OLD]], ptr %[[V]]
+!CHECK: br label %[[EXIT2:.*]]
+subroutine atomic_compare_capture_fail_only(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture
+    if (x == e) then
+      x = d
+    else
+      v = x
+    end if
+  !$omp end atomic
+end
+
diff --git a/flang/test/Lower/OpenMP/atomic-compare.f90 b/flang/test/Lower/OpenMP/atomic-compare.f90
index 752a221aa538d..c770605954539 100644
--- a/flang/test/Lower/OpenMP/atomic-compare.f90
+++ b/flang/test/Lower/OpenMP/atomic-compare.f90
@@ -217,3 +217,62 @@ subroutine atomic_compare_capture_int_gt(x, e, d, v)
     if (x > e) x = d
   !$omp end atomic
 end
+
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_postfix(
+! 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-SAME:    %[[V:.*]]: !fir.ref<i32> {fir.bindc_name = "v"})
+! CHECK:         %[[D_DECL:.*]]:2 = hlfir.declare %[[D]] {{.*}}
+! CHECK:         %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK:         %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
+! CHECK:         %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK:         %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %[[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:           }
+! CHECK:           omp.atomic.read %[[V_DECL]]#0 = %[[X_DECL]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:         }
+subroutine atomic_compare_capture_postfix(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture
+    if (x .eq. e) x = d
+    v = x
+  !$omp end atomic
+end
+
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_fail_only(
+! 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-SAME:    %[[V:.*]]: !fir.ref<i32> {fir.bindc_name = "v"})
+! CHECK:         %[[D_DECL:.*]]:2 = hlfir.declare %[[D]] {{.*}}
+! CHECK:         %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK:         %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
+! CHECK:         %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK:         %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %[[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:           }
+! CHECK:           omp.atomic.read %[[V_DECL]]#0 = %[[X_DECL]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:         } {fail_only}
+subroutine atomic_compare_capture_fail_only(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture
+    if (x .eq. e) then
+      x = d
+    else
+      v = x
+    end if
+  !$omp end atomic
+end
diff --git a/flang/test/Parser/OpenMP/atomic-unparse.f90 b/flang/test/Parser/OpenMP/atomic-unparse.f90
index dc0cc1a62f6c2..521c1bb2811e2 100644
--- a/flang/test/Parser/OpenMP/atomic-unparse.f90
+++ b/flang/test/Parser/OpenMP/atomic-unparse.f90
@@ -211,6 +211,19 @@ program main
       i = k
    end if
 !$omp end atomic
+!$omp atomic compare capture
+   if (i .eq. j) then
+      i = k
+   end if
+   k = i
+!$omp end atomic
+!$omp atomic compare capture
+   if (i .eq. j) then
+      i = k
+   else
+      k = i
+   end if
+!$omp end atomic
 
 !ATOMIC
 !$omp atomic
@@ -324,6 +337,10 @@ end program main
 !CHECK: !$OMP END ATOMIC
 !CHECK: !$OMP ATOMIC CAPTURE COMPARE WEAK
 !CHECK: !$OMP END ATOMIC
+!CHECK: !$OMP ATOMIC COMPARE CAPTURE
+!CHECK: !$OMP END ATOMIC
+!CHECK: !$OMP ATOMIC COMPARE CAPTURE
+!CHECK: !$OMP END ATOMIC
 
 !ATOMIC
 !CHECK: !$OMP ATOMIC
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 76616c7fba27a..b6fc6d6dc3038 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -4974,15 +4974,28 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
     llvmAtomicX.IsSigned = isSigned;
 
     llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
+    // In OMPIRBuilder::createAtomicCompare:
+    //   IsPostfixUpdate=true  → select(success, D, OldValue) → new value
+    //   IsPostfixUpdate=false → select(success, E, OldValue) → old value
+    // Prefix read (v=x; if(x==e) x=d): v captures old → IsPostfixUpdate=false
+    // Postfix read (if(x==e) x=d; v=x): v captures new → IsPostfixUpdate=true
+    // Fail-only: conditional store only on failure → IsPostfixUpdate=false
+    bool isPostfixUpdate =
+        !isa<omp::AtomicReadOp>(atomicCaptureOp.getFirstOp());
     bool isFailOnly = atomicCaptureOp.getFailOnly();
-    bool isPostfixUpdate = !isFailOnly;
 
+    // For fail-only, the OMPIRBuilder's conditional store logic is under
+    // the !IsPostfixUpdate path, so force it to false.
+    if (isFailOnly)
+      isPostfixUpdate = false;
+
+    bool isWeak = atomicCompareOp.getWeak();
     bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
     llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
         ompBuilder->createAtomicCompare(ompLoc, llvmAtomicX, llvmAtomicV,
                                         llvmAtomicR, eVal, dVal, atomicOrdering,
                                         compareOp, isXBinopExpr,
-                                        isPostfixUpdate, isFailOnly);
+                                        isPostfixUpdate, isFailOnly, isWeak);
     ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
 
     if (failed(handleError(afterIP, *atomicCaptureOp)))
diff --git a/mlir/test/Dialect/OpenMP/ops.mlir b/mlir/test/Dialect/OpenMP/ops.mlir
index 87f9a6f8e4119..6b13be481e0e0 100644
--- a/mlir/test/Dialect/OpenMP/ops.mlir
+++ b/mlir/test/Dialect/OpenMP/ops.mlir
@@ -2113,6 +2113,54 @@ func.func @omp_atomic_compare_capture_weak(%v: memref<i32>, %x: memref<i32>, %e:
   return
 }
 
+// CHECK-LABEL: omp_atomic_compare_capture_postfix
+// CHECK-SAME: (%[[V:.*]]: memref<i32>, %[[X:.*]]: memref<i32>, %[[E:.*]]: i32, %[[D:.*]]: i32)
+func.func @omp_atomic_compare_capture_postfix(%v: memref<i32>, %x: memref<i32>, %e: i32, %d: i32) {
+  // CHECK: omp.atomic.capture {
+  // CHECK-NEXT: omp.atomic.compare %[[X]] : memref<i32> {
+  // CHECK-NEXT: ^bb0(%[[XVAL:.*]]: i32):
+  // CHECK-NEXT:   %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %[[E]] : i32
+  // CHECK-NEXT:   %[[SEL:.*]] = arith.select %[[CMP]], %[[D]], %[[XVAL]] : i32
+  // CHECK-NEXT:   omp.yield(%[[SEL]] : i32)
+  // CHECK-NEXT: }
+  // CHECK-NEXT: omp.atomic.read %[[V]] = %[[X]] : memref<i32>, memref<i32>, i32
+  // CHECK-NEXT: }
+  omp.atomic.capture {
+    omp.atomic.compare %x : memref<i32> {
+    ^bb0(%xval: i32):
+      %cmp = arith.cmpi eq, %xval, %e : i32
+      %sel = arith.select %cmp, %d, %xval : i32
+      omp.yield(%sel : i32)
+    }
+    omp.atomic.read %v = %x : memref<i32>, memref<i32>, i32
+  }
+  return
+}
+
+// CHECK-LABEL: omp_atomic_compare_capture_fail_only
+// CHECK-SAME: (%[[V:.*]]: memref<i32>, %[[X:.*]]: memref<i32>, %[[E:.*]]: i32, %[[D:.*]]: i32)
+func.func @omp_atomic_compare_capture_fail_only(%v: memref<i32>, %x: memref<i32>, %e: i32, %d: i32) {
+  // CHECK: omp.atomic.capture {
+  // CHECK-NEXT: omp.atomic.compare %[[X]] : memref<i32> {
+  // CHECK-NEXT: ^bb0(%[[XVAL:.*]]: i32):
+  // CHECK-NEXT:   %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %[[E]] : i32
+  // CHECK-NEXT:   %[[SEL:.*]] = arith.select %[[CMP]], %[[D]], %[[XVAL]] : i32
+  // CHECK-NEXT:   omp.yield(%[[SEL]] : i32)
+  // CHECK-NEXT: }
+  // CHECK-NEXT: omp.atomic.read %[[V]] = %[[X]] : memref<i32>, memref<i32>, i32
+  // CHECK-NEXT: } {fail_only}
+  omp.atomic.capture {
+    omp.atomic.compare %x : memref<i32> {
+    ^bb0(%xval: i32):
+      %cmp = arith.cmpi eq, %xval, %e : i32
+      %sel = arith.select %cmp, %d, %xval : i32
+      omp.yield(%sel : i32)
+    }
+    omp.atomic.read %v = %x : memref<i32>, memref<i32>, i32
+  } {fail_only}
+  return
+}
+
 // CHECK-LABEL: omp_sectionsop
 func.func @omp_sectionsop(%data_var1 : memref<i32>, %data_var2 : memref<i32>,
                      %data_var3 : memref<i32>, %redn_var : !llvm.ptr) {
diff --git a/mlir/test/Target/LLVMIR/openmp-llvm.mlir b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
index 6ce57b304bfd9..978450d598b9b 100644
--- a/mlir/test/Target/LLVMIR/openmp-llvm.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
@@ -2798,10 +2798,12 @@ llvm.func @omp_atomic_compare_float_neg_zero(%xf : !llvm.ptr, %ef : f32, %df : f
 // CHECK-LABEL: @omp_atomic_compare_capture_int_eq
 // CHECK-SAME: (ptr %[[X:.*]], ptr %[[V:.*]], i32 %[[E:.*]], i32 %[[D:.*]])
 llvm.func @omp_atomic_compare_capture_int_eq(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32, %d : i32) {
-  // Integer equality compare+capture → cmpxchg + extractvalue + store
+  // Integer equality prefix compare+capture → cmpxchg + select(success, E, old) + store
   // CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[E]], i32 %[[D]] monotonic monotonic
   // CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
-  // CHECK: store i32 %[[OLD]], ptr %[[V]]
+  // CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+  // CHECK: %[[CAPTURED:.*]] = select i1 %[[SUCCESS]], i32 %[[E]], i32 %[[OLD]]
+  // CHECK: store i32 %[[CAPTURED]], ptr %[[V]]
   omp.atomic.capture {
     omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
     omp.atomic.compare %x : !llvm.ptr {
@@ -2819,10 +2821,12 @@ llvm.func @omp_atomic_compare_capture_int_eq(%x : !llvm.ptr, %v : !llvm.ptr, %e
 // CHECK-LABEL: @omp_atomic_compare_capture_weak_int_eq
 // CHECK-SAME: (ptr %[[X:.*]], ptr %[[V:.*]], i32 %[[E:.*]], i32 %[[D:.*]])
 llvm.func @omp_atomic_compare_capture_weak_int_eq(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32, %d : i32) {
-  // Integer equality compare+capture → cmpxchg + extractvalue + store
+  // Integer equality weak prefix compare+capture → cmpxchg + select(success, E, old) + store
   // CHECK: %[[RES:.*]] = cmpxchg weak ptr %[[X]], i32 %[[E]], i32 %[[D]] monotonic monotonic
   // CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
-  // CHECK: store i32 %[[OLD]], ptr %[[V]]
+  // CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+  // CHECK: %[[CAPTURED:.*]] = select i1 %[[SUCCESS]], i32 %[[E]], i32 %[[OLD]]
+  // CHECK: store i32 %[[CAPTURED]], ptr %[[V]]
   omp.atomic.capture {
     omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
     omp.atomic.compare %x : !llvm.ptr {
@@ -2836,6 +2840,52 @@ llvm.func @omp_atomic_compare_capture_weak_int_eq(%x : !llvm.ptr, %v : !llvm.ptr
 }
 // -----
 
+// CHECK-LABEL: @omp_atomic_compare_capture_postfix
+// CHECK-SAME: (ptr %[[X:.*]], ptr %[[V:.*]], i32 %[[E:.*]], i32 %[[D:.*]])
+llvm.func @omp_atomic_compare_capture_postfix(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32, %d : i32) {
+  // Postfix compare+capture: v captures new value (d if swapped, old x if not)
+  // CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[E]], i32 %[[D]] monotonic monotonic
+  // CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+  // CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+  // CHECK: %[[NEWVAL:.*]] = select i1 %[[SUCCESS]], i32 %[[D]], i32 %[[OLD]]
+  // CHECK: store i32 %[[NEWVAL]], ptr %[[V]]
+  omp.atomic.capture {
+    omp.atomic.compare %x : !llvm.ptr {
+    ^bb0(%xval : i32):
+      %cmp = llvm.icmp "eq" %xval, %e : i32
+      %sel = llvm.select %cmp, %d, %xval : i1, i32
+      omp.yield(%sel : i32)
+    }
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+  }
+  llvm.return
+}
+// -----
+
+// CHECK-LABEL: @omp_atomic_compare_capture_fail_only
+// CHECK-SAME: (ptr %[[X:.*]], ptr %[[V:.*]], i32 %[[E:.*]], i32 %[[D:.*]])
+llvm.func @omp_atomic_compare_capture_fail_only(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32, %d : i32) {
+  // Fail-only compare+capture: v is only written when comparison fails
+  // CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[E]], i32 %[[D]] monotonic monotonic
+  // CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+  // CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+  // CHECK: br i1 %[[SUCCESS]], label %[[EXIT:.*]], label %[[CONT:.*]]
+  // CHECK: [[CONT]]:
+  // CHECK: store i32 %[[OLD]], ptr %[[V]]
+  // CHECK: br label %[[EXIT2:.*]]
+  omp.atomic.capture {
+    omp.atomic.compare %x : !llvm.ptr {
+    ^bb0(%xval : i32):
+      %cmp = llvm.icmp "eq" %xval, %e : i32
+      %sel = llvm.select %cmp, %d, %xval : i1, i32
+      omp.yield(%sel : i32)
+    }
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+  } {fail_only}
+  llvm.return
+}
+// -----
+
 // CHECK-LABEL: @omp_sections_empty
 llvm.func @omp_sections_empty() -> () {
   omp.sections {

>From e8462a267b6d010ebc1e3ed881c1f38a9faca6be Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Wed, 24 Jun 2026 14:44:43 -0500
Subject: [PATCH 06/11] Simplifying the IR generator for one of the cases. This
 also fixes the failure in clang for "atomic compare capture".

---
 .../Integration/OpenMP/atomic-compare.f90     |  8 +---
 llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp     |  7 +--
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      | 43 +++++++++++++------
 mlir/test/Target/LLVMIR/openmp-llvm.mlir      | 12 ++----
 4 files changed, 38 insertions(+), 32 deletions(-)

diff --git a/flang/test/Integration/OpenMP/atomic-compare.f90 b/flang/test/Integration/OpenMP/atomic-compare.f90
index da86f3ff6ef16..4ae1b9a1f1e52 100644
--- a/flang/test/Integration/OpenMP/atomic-compare.f90
+++ b/flang/test/Integration/OpenMP/atomic-compare.f90
@@ -268,9 +268,7 @@ subroutine atomic_compare_weak(x, e, d)
 !CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]]
 !CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
 !CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
-!CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
-!CHECK: %[[CAPTURED:.*]] = select i1 %[[SUCCESS]], i32 %[[EVAL]], i32 %[[OLD]]
-!CHECK: store i32 %[[CAPTURED]], ptr %[[V]]
+!CHECK: store i32 %[[OLD]], ptr %[[V]]
 subroutine atomic_compare_capture_int_eq(x, e, d, v)
   integer :: x, e, d, v
   !$omp atomic compare capture
@@ -286,9 +284,7 @@ subroutine atomic_compare_capture_int_eq(x, e, d, v)
 !CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]]
 !CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
 !CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
-!CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
-!CHECK: %[[CAPTURED:.*]] = select i1 %[[SUCCESS]], i32 %[[EVAL]], i32 %[[OLD]]
-!CHECK: store i32 %[[CAPTURED]], ptr %[[V]]
+!CHECK: store i32 %[[OLD]], ptr %[[V]]
 subroutine atomic_capture_compare_int_eq(x, e, d, v)
   integer :: x, e, d, v
   !$omp atomic capture compare
diff --git a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
index 51f210e189e22..f8c1999fe1b89 100644
--- a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
+++ b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
@@ -11400,9 +11400,7 @@ OpenMPIRBuilder::InsertPointTy OpenMPIRBuilder::createAtomicCompare(
         assert(OldValue->getType() == V.ElemTy &&
                "OldValue and V must be of same type");
         if (IsPostfixUpdate) {
-          SuccessOrFail = Builder.CreateExtractValue(Result, /*Idxs=*/1);
-          Value *NewValue = Builder.CreateSelect(SuccessOrFail, D, OldValue);
-          Builder.CreateStore(NewValue, V.Var, V.IsVolatile);
+          Builder.CreateStore(OldValue, V.Var, V.IsVolatile);
         } else {
           SuccessOrFail = Builder.CreateExtractValue(Result, /*Idxs=*/1);
           if (IsFailOnly) {
@@ -11458,8 +11456,7 @@ OpenMPIRBuilder::InsertPointTy OpenMPIRBuilder::createAtomicCompare(
         assert(OldValue->getType() == V.ElemTy &&
                "OldValue and V must be of same type");
         if (IsPostfixUpdate) {
-          Value *NewValue = Builder.CreateSelect(SuccessOrFail, D, OldValue);
-          Builder.CreateStore(NewValue, V.Var, V.IsVolatile);
+          Builder.CreateStore(OldValue, V.Var, V.IsVolatile);
         } else {
           if (IsFailOnly) {
             BasicBlock *CurBB = Builder.GetInsertBlock();
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 85389caa9ac42..e1fb08d780753 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -5499,25 +5499,23 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
     llvmAtomicX.IsSigned = isSigned;
 
     llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
-    // In OMPIRBuilder::createAtomicCompare:
-    //   IsPostfixUpdate=true  → select(success, D, OldValue) → new value
-    //   IsPostfixUpdate=false → select(success, E, OldValue) → old value
-    // Prefix read (v=x; if(x==e) x=d): v captures old → IsPostfixUpdate=false
-    // Postfix read (if(x==e) x=d; v=x): v captures new → IsPostfixUpdate=true
-    // Fail-only: conditional store only on failure → IsPostfixUpdate=false
-    bool isPostfixUpdate =
-        !isa<omp::AtomicReadOp>(atomicCaptureOp.getFirstOp());
+    bool isReadFirst = isa<omp::AtomicReadOp>(atomicCaptureOp.getFirstOp());
+    bool isPostfixCapture = !isReadFirst;
     bool isFailOnly = atomicCaptureOp.getFailOnly();
 
-    // For fail-only, the OMPIRBuilder's conditional store logic is under
-    // the !IsPostfixUpdate path, so force it to false.
-    if (isFailOnly)
-      isPostfixUpdate = false;
+    llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicVForCall = llvmAtomicV;
+    // Postfix: bypass V in OMPIRBuilder, handled manually below.
+    if (isPostfixCapture && !isFailOnly)
+      llvmAtomicVForCall = {nullptr, nullptr, false, false};
+
+    // Prefix: IsPostfixUpdate=true → direct store of old value.
+    // Fail-only: IsPostfixUpdate=false → conditional store on failure.
+    bool isPostfixUpdate = !isFailOnly;
 
     bool isWeak = atomicCompareOp.getWeak();
     bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
     llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
-        ompBuilder->createAtomicCompare(ompLoc, llvmAtomicX, llvmAtomicV,
+        ompBuilder->createAtomicCompare(ompLoc, llvmAtomicX, llvmAtomicVForCall,
                                         llvmAtomicR, eVal, dVal, atomicOrdering,
                                         compareOp, isXBinopExpr,
                                         isPostfixUpdate, isFailOnly, isWeak);
@@ -5527,6 +5525,25 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
       return failure();
 
     builder.restoreIP(*afterIP);
+
+    // Postfix: v = select(success, D, old) — captures new value of x.
+    if (isPostfixCapture && !isFailOnly) {
+      llvm::BasicBlock *curBB = builder.GetInsertBlock();
+      llvm::Instruction *cmpxchgInst = nullptr;
+      for (auto &inst : llvm::reverse(*curBB)) {
+        if (isa<llvm::AtomicCmpXchgInst>(&inst)) {
+          cmpxchgInst = &inst;
+          break;
+        }
+      }
+      assert(cmpxchgInst && "expected cmpxchg instruction");
+      llvm::Value *oldVal = builder.CreateExtractValue(cmpxchgInst, /*Idxs=*/0);
+      llvm::Value *successVal =
+          builder.CreateExtractValue(cmpxchgInst, /*Idxs=*/1);
+      llvm::Value *newVal = builder.CreateSelect(successVal, dVal, oldVal);
+      builder.CreateStore(newVal, llvmAtomicV.Var, llvmAtomicV.IsVolatile);
+    }
+
     return success();
   }
 
diff --git a/mlir/test/Target/LLVMIR/openmp-llvm.mlir b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
index 978450d598b9b..377bd88894854 100644
--- a/mlir/test/Target/LLVMIR/openmp-llvm.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
@@ -2798,12 +2798,10 @@ llvm.func @omp_atomic_compare_float_neg_zero(%xf : !llvm.ptr, %ef : f32, %df : f
 // CHECK-LABEL: @omp_atomic_compare_capture_int_eq
 // CHECK-SAME: (ptr %[[X:.*]], ptr %[[V:.*]], i32 %[[E:.*]], i32 %[[D:.*]])
 llvm.func @omp_atomic_compare_capture_int_eq(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32, %d : i32) {
-  // Integer equality prefix compare+capture → cmpxchg + select(success, E, old) + store
+  // Integer equality prefix compare+capture → cmpxchg + store old value
   // CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[E]], i32 %[[D]] monotonic monotonic
   // CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
-  // CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
-  // CHECK: %[[CAPTURED:.*]] = select i1 %[[SUCCESS]], i32 %[[E]], i32 %[[OLD]]
-  // CHECK: store i32 %[[CAPTURED]], ptr %[[V]]
+  // CHECK: store i32 %[[OLD]], ptr %[[V]]
   omp.atomic.capture {
     omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
     omp.atomic.compare %x : !llvm.ptr {
@@ -2821,12 +2819,10 @@ llvm.func @omp_atomic_compare_capture_int_eq(%x : !llvm.ptr, %v : !llvm.ptr, %e
 // CHECK-LABEL: @omp_atomic_compare_capture_weak_int_eq
 // CHECK-SAME: (ptr %[[X:.*]], ptr %[[V:.*]], i32 %[[E:.*]], i32 %[[D:.*]])
 llvm.func @omp_atomic_compare_capture_weak_int_eq(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32, %d : i32) {
-  // Integer equality weak prefix compare+capture → cmpxchg + select(success, E, old) + store
+  // Integer equality weak prefix compare+capture → cmpxchg + store old value
   // CHECK: %[[RES:.*]] = cmpxchg weak ptr %[[X]], i32 %[[E]], i32 %[[D]] monotonic monotonic
   // CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
-  // CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
-  // CHECK: %[[CAPTURED:.*]] = select i1 %[[SUCCESS]], i32 %[[E]], i32 %[[OLD]]
-  // CHECK: store i32 %[[CAPTURED]], ptr %[[V]]
+  // CHECK: store i32 %[[OLD]], ptr %[[V]]
   omp.atomic.capture {
     omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
     omp.atomic.compare %x : !llvm.ptr {

>From 101b9fa02882686a21d346ef12e541c717ca605b Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Tue, 30 Jun 2026 14:00:46 -0500
Subject: [PATCH 07/11] Handling real and logical types.

---
 flang/lib/Lower/OpenMP/Atomic.cpp             | 117 ++++++--
 .../Integration/OpenMP/atomic-compare.f90     |  58 ++++
 flang/test/Lower/OpenMP/atomic-compare.f90    |  83 +++++-
 flang/test/Parser/OpenMP/atomic-unparse.f90   |  12 +
 .../Interfaces/AtomicInterfaces.td            |  13 +
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      | 252 +++++++++++-------
 mlir/test/Dialect/OpenMP/ops.mlir             |  24 ++
 mlir/test/Target/LLVMIR/openmp-llvm.mlir      |  48 ++++
 8 files changed, 470 insertions(+), 137 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/Atomic.cpp b/flang/lib/Lower/OpenMP/Atomic.cpp
index 209871abd52b6..3811ea3d40bbd 100644
--- a/flang/lib/Lower/OpenMP/Atomic.cpp
+++ b/flang/lib/Lower/OpenMP/Atomic.cpp
@@ -375,6 +375,8 @@ genAtomicRead(lower::AbstractConverter &converter,
         fir::getBase(converter.genExprValue(assign.rhs, stmtCtx, &loc));
     converter.resetExprOverrides();
 
+    if (value.getType() != storeType)
+      value = builder.createConvert(loc, storeType, value);
     fir::StoreOp::create(builder, loc, value, storeAddr);
   }
   return op;
@@ -634,6 +636,45 @@ void Fortran::lower::omp::lowerAtomic(
           },
           rel->u);
     }
+
+    // Fortran uses .eqv./.neqv. for logical equality/inequality, which are
+    // LogicalOperation expressions rather than Relational expressions.
+    if (!expectedExprStorage) {
+      if (const auto *someLogical =
+              evaluate::UnwrapExpr<evaluate::Expr<evaluate::SomeLogical>>(
+                  *cond)) {
+        common::visit(
+            [&](const auto &kindLogical) {
+              using LogicalExpr = std::decay_t<decltype(kindLogical)>;
+              constexpr int K = LogicalExpr::Result::kind;
+              if (const auto *logOp =
+                      std::get_if<evaluate::LogicalOperation<K>>(
+                          &kindLogical.u)) {
+                if (logOp->logicalOperator == common::LogicalOperator::Eqv ||
+                    logOp->logicalOperator == common::LogicalOperator::Neqv) {
+                  relOpr =
+                      (logOp->logicalOperator == common::LogicalOperator::Eqv)
+                          ? common::RelationalOperator::EQ
+                          : common::RelationalOperator::NE;
+                  using Operand =
+                      typename evaluate::LogicalOperation<K>::Operand;
+                  auto leftExpr = evaluate::AsGenericExpr(
+                      evaluate::Expr<Operand>{logOp->left()});
+                  auto rightExpr = evaluate::AsGenericExpr(
+                      evaluate::Expr<Operand>{logOp->right()});
+                  if (evaluate::IsSameOrConvertOf(rightExpr, atom)) {
+                    expectedExprStorage = std::move(leftExpr);
+                    relOpr = reverseRelOp(relOpr);
+                  } else {
+                    expectedExprStorage = std::move(rightExpr);
+                  }
+                }
+              }
+            },
+            someLogical->u);
+      }
+    }
+
     if (!expectedExprStorage) {
       mlir::emitError(loc, "internal error: atomic compare condition is not a "
                            "recognized relational expression");
@@ -647,6 +688,20 @@ void Fortran::lower::omp::lowerAtomic(
       expectedVal = builder.createConvert(loc, elemTypeOfX, expectedVal);
     }
 
+    // For logical types, convert address and expected value to integer
+    // type here (above the atomic compare region) so that the region
+    // only contains arith.cmpi eq on integers. If done inside the region,
+    // fir.convert logical<4> -> i32 would lower to `icmp ne %val, 0`
+    // which violates the atomic compare verifier's expectations.
+    if (mlir::isa<fir::LogicalType>(elemTypeOfX)) {
+      unsigned kind = mlir::cast<fir::LogicalType>(elemTypeOfX).getFKind();
+      mlir::Type intTy = builder.getIntegerType(kind * 8);
+      mlir::Type intRefTy = builder.getRefType(intTy);
+      atomAddr = builder.createConvert(loc, intRefTy, atomAddr);
+      expectedVal = builder.createConvert(loc, intTy, expectedVal);
+      elemTypeOfX = intTy;
+    }
+
     // If this is a compare+capture, determine the ordering of ops.
     // Pattern 1 (prefix): v = x; if (x == e) x = d  → read first
     // Pattern 2 (postfix): if (x == e) x = d; v = x → compare first
@@ -694,6 +749,39 @@ void Fortran::lower::omp::lowerAtomic(
         })) {
       weakAttr = builder.getUnitAttr();
     }
+
+    // Extract write assignment (x = d) and generate desired value (d)
+    // before creating the compare region, so that d is defined outside
+    // the region and any intermediate conversions (e.g., logical-to-integer
+    // truthiness normalization) don't appear inside the atomic compare block.
+    [[maybe_unused]] 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 desiredVal before the capture/compare region so any
+    // intermediate ops (loads, conversions) don't pollute the atomic blocks.
+    fir::FirOpBuilder::InsertPoint savedIP = builder.saveInsertionPoint();
+    builder.restoreInsertionPoint(preAt);
+    mlir::Value desiredVal =
+        fir::getBase(converter.genExprValue(writeAssign->rhs, stmtCtx, &loc));
+    if (desiredVal.getType() != elemTypeOfX)
+      desiredVal = builder.createConvert(loc, elemTypeOfX, desiredVal);
+    builder.restoreInsertionPoint(savedIP);
+
     mlir::Operation *atomicOp = mlir::omp::AtomicCompareOp::create(
         builder, loc, atomAddr, weakAttr, hint,
         makeMemOrderAttr(converter, memOrder));
@@ -721,35 +809,6 @@ void Fortran::lower::omp::lowerAtomic(
       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)
-    [[maybe_unused]] 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);
 
diff --git a/flang/test/Integration/OpenMP/atomic-compare.f90 b/flang/test/Integration/OpenMP/atomic-compare.f90
index 4ae1b9a1f1e52..f30349c42e33e 100644
--- a/flang/test/Integration/OpenMP/atomic-compare.f90
+++ b/flang/test/Integration/OpenMP/atomic-compare.f90
@@ -336,3 +336,61 @@ subroutine atomic_compare_capture_fail_only(x, e, d, v)
   !$omp end atomic
 end
 
+! Real equality compare+capture (postfix): if(x==e) x=d; v=x
+! v captures new value of x (d if success, old if fail)
+!CHECK-LABEL: define void @atomic_compare_capture_real_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]], ptr noalias %[[V:.*]])
+!CHECK: %[[EVAL:.*]] = load float, ptr %[[E]]{{.*}}
+!CHECK: %[[DVAL:.*]] = load float, ptr %[[D]]{{.*}}
+!CHECK: %[[EBITS:.*]] = bitcast float %[[EVAL]] to i32
+!CHECK: %[[DBITS:.*]] = bitcast float %[[DVAL]] to i32
+!CHECK: %[[XLOAD:.*]] = load atomic i32, ptr %[[X]] monotonic{{.*}}
+!CHECK: %[[XFP:.*]] = bitcast i32 %[[XLOAD]] to float
+! Part 1: NaN check - if either x or e is NaN, comparison fails
+!CHECK: %[[E_ISNAN:.*]] = fcmp uno float %[[EVAL]], %[[EVAL]]
+!CHECK: %[[X_ISNAN:.*]] = fcmp uno float %[[XFP]], %[[XFP]]
+!CHECK: %[[EITHER_NAN:.*]] = or i1 %[[E_ISNAN]], %[[X_ISNAN]]
+!CHECK: br i1 %[[EITHER_NAN]], label %[[NAN_BB:.*]], label %[[NOTNAN_BB:.*]]
+!CHECK: [[NAN_BB]]:
+!CHECK: br label %[[EXIT_BB:.*]]
+! Part 2: Both-zero check - handles +0.0 vs -0.0 (same value, different bits)
+!CHECK: [[NOTNAN_BB]]:
+!CHECK: %[[XISZERO:.*]] = fcmp oeq float %[[XFP]], 0.000000e+00
+!CHECK: %[[EISZERO:.*]] = fcmp oeq float %[[EVAL]], 0.000000e+00
+!CHECK: %[[BOTHZERO:.*]] = and i1 %[[XISZERO]], %[[EISZERO]]
+!CHECK: br i1 %[[BOTHZERO]], label %[[ZERO_BB:.*]], label %[[NORMAL_BB:.*]]
+!CHECK: [[ZERO_BB]]:
+!CHECK: %[[ZERORES:.*]] = cmpxchg ptr %[[X]], i32 %[[XLOAD]], i32 %[[DBITS]] monotonic monotonic{{.*}}
+!CHECK: br label %[[EXIT_BB]]
+! Part 3: Normal compare - standard cmpxchg with bitcasted expected value
+!CHECK: [[NORMAL_BB]]:
+!CHECK: %[[NORMRES:.*]] = cmpxchg ptr %[[X]], i32 %[[EBITS]], i32 %[[DBITS]] monotonic monotonic{{.*}}
+!CHECK: br label %[[EXIT_BB]]
+! Exit: select v = (success ? d : old_x)
+!CHECK: [[EXIT_BB]]:
+!CHECK: %[[OLD:.*]] = phi i32 {{.*}}
+!CHECK: %[[OK:.*]] = phi i1 {{.*}}
+!CHECK: %[[OLDFP:.*]] = bitcast i32 %[[OLD]] to float
+!CHECK: %[[VVAL:.*]] = select i1 %[[OK]], float %[[DVAL]], float %[[OLDFP]]
+!CHECK: store float %[[VVAL]], ptr %[[V]]{{.*}}
+subroutine atomic_compare_capture_real(x, e, d, v)
+  real :: x, e, d, v
+  !$omp atomic compare capture
+    if (x == e) x = d
+    v = x
+  !$omp end atomic
+end
+
+! Logical .eqv. compare+capture (postfix): if(x.eqv.e) x=d; v=x
+! Logicals are compared as integers after truthiness normalization
+!CHECK-LABEL: define void @atomic_compare_capture_logical_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]], ptr noalias %[[V:.*]])
+!CHECK: cmpxchg ptr %[[X]], i32 %{{.*}}, i32 %{{.*}} monotonic monotonic{{.*}}
+subroutine atomic_compare_capture_logical(x, e, d, v)
+  logical :: x, e, d, v
+  !$omp atomic compare capture
+    if (x .eqv. e) x = d
+    v = x
+  !$omp end atomic
+end
+
diff --git a/flang/test/Lower/OpenMP/atomic-compare.f90 b/flang/test/Lower/OpenMP/atomic-compare.f90
index c770605954539..1219d05296e4d 100644
--- a/flang/test/Lower/OpenMP/atomic-compare.f90
+++ b/flang/test/Lower/OpenMP/atomic-compare.f90
@@ -10,10 +10,10 @@
 ! 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:         %[[DVAL:.*]] = fir.load %[[D_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:         }
@@ -31,10 +31,10 @@ subroutine atomic_compare_int_eq(x, e, 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:         %[[DVAL:.*]] = fir.load %[[D_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:         }
@@ -52,10 +52,10 @@ subroutine atomic_compare_float_eq(x, e, 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:         %[[DVAL:.*]] = fir.load %[[D_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:         }
@@ -71,10 +71,10 @@ subroutine atomic_compare_complex_eq(x, e, 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:         %[[EVAL2:.*]] = 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:         }
@@ -90,10 +90,10 @@ subroutine atomic_compare_int_lt(x, 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:         %[[EVAL2:.*]] = 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:         }
@@ -109,10 +109,10 @@ subroutine atomic_compare_int_gt(x, 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:         %[[EVAL2:.*]] = 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:         }
@@ -128,10 +128,10 @@ subroutine atomic_compare_float_lt(x, 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:         %[[EVAL2:.*]] = 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:         }
@@ -149,10 +149,10 @@ subroutine atomic_compare_float_gt(x, 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:         %[[DVAL:.*]] = fir.load %[[D_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:         } {{.*}}weak{{.*}}
@@ -172,12 +172,12 @@ subroutine atomic_compare_int_eq_weak(x, e, d)
 ! CHECK:         %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
 ! CHECK:         %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
 ! CHECK:         %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK:         %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
 ! CHECK:         omp.atomic.capture memory_order(relaxed) {
 ! CHECK:           omp.atomic.read %[[V_DECL]]#0 = %[[X_DECL]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
 ! CHECK:           omp.atomic.compare %[[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:           }
@@ -200,12 +200,12 @@ subroutine atomic_compare_capture_int_eq(x, e, d, v)
 ! CHECK:         %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
 ! CHECK:         %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
 ! CHECK:         %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK:         %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
 ! CHECK:         omp.atomic.capture memory_order(relaxed) {
 ! CHECK:           omp.atomic.read %[[V_DECL]]#0 = %[[X_DECL]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
 ! CHECK:           omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<i32> {
 ! CHECK:           ^bb0(%[[XVAL:.*]]: i32):
 ! CHECK:             %[[CMP:.*]] = arith.cmpi sgt, %[[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:           } {{.*}}weak{{.*}}
@@ -228,11 +228,11 @@ subroutine atomic_compare_capture_int_gt(x, e, d, v)
 ! CHECK:         %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
 ! CHECK:         %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
 ! CHECK:         %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK:         %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
 ! CHECK:         omp.atomic.capture memory_order(relaxed) {
 ! CHECK:           omp.atomic.compare %[[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:           }
@@ -256,11 +256,11 @@ subroutine atomic_compare_capture_postfix(x, e, d, v)
 ! CHECK:         %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
 ! CHECK:         %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
 ! CHECK:         %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK:         %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
 ! CHECK:         omp.atomic.capture memory_order(relaxed) {
 ! CHECK:           omp.atomic.compare %[[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:           }
@@ -276,3 +276,62 @@ subroutine atomic_compare_capture_fail_only(x, e, d, v)
     end if
   !$omp end atomic
 end
+
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_real(
+! 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-SAME:    %[[V:.*]]: !fir.ref<f32> {fir.bindc_name = "v"})
+! CHECK:         %[[D_DECL:.*]]:2 = hlfir.declare %[[D]] {{.*}}
+! CHECK:         %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK:         %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
+! CHECK:         %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK:         %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<f32>
+! CHECK:         %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<f32>
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<f32> {
+! CHECK:           ^bb0(%[[XVAL:.*]]: f32):
+! CHECK:             %[[CMP:.*]] = arith.cmpf oeq, %[[XVAL]], %[[EVAL]] fastmath<contract> : f32
+! CHECK:             %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : f32
+! CHECK:             omp.yield(%[[SEL]] : f32)
+! CHECK:           }
+! CHECK:           omp.atomic.read %[[V_DECL]]#0 = %[[X_DECL]]#0 : !fir.ref<f32>, !fir.ref<f32>, f32
+! CHECK:         }
+subroutine atomic_compare_capture_real(x, e, d, v)
+  real :: x, e, d, v
+  !$omp atomic compare capture
+    if (x == e) x = d
+    v = x
+  !$omp end atomic
+end
+
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_logical(
+! CHECK-SAME:    %[[X:.*]]: !fir.ref<!fir.logical<4>> {fir.bindc_name = "x"},
+! CHECK-SAME:    %[[E:.*]]: !fir.ref<!fir.logical<4>> {fir.bindc_name = "e"},
+! CHECK-SAME:    %[[D:.*]]: !fir.ref<!fir.logical<4>> {fir.bindc_name = "d"},
+! CHECK-SAME:    %[[V:.*]]: !fir.ref<!fir.logical<4>> {fir.bindc_name = "v"})
+! CHECK:         %[[D_DECL:.*]]:2 = hlfir.declare %[[D]] {{.*}}
+! CHECK:         %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK:         %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
+! CHECK:         %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK:         %[[ELOAD:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<!fir.logical<4>>
+! CHECK:         %[[XCONV:.*]] = fir.convert %[[X_DECL]]#0 : (!fir.ref<!fir.logical<4>>) -> !fir.ref<i32>
+! CHECK:         %[[ECONV:.*]] = fir.convert %[[ELOAD]] : (!fir.logical<4>) -> i32
+! CHECK:         %[[DLOAD:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<!fir.logical<4>>
+! CHECK:         %[[DCONV:.*]] = fir.convert %[[DLOAD]] : (!fir.logical<4>) -> i32
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %[[XCONV]] : !fir.ref<i32> {
+! CHECK:           ^bb0(%[[XVAL:.*]]: i32):
+! CHECK:             %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %[[ECONV]] : i32
+! CHECK:             %[[SEL:.*]] = arith.select %[[CMP]], %[[DCONV]], %[[XVAL]] : i32
+! CHECK:             omp.yield(%[[SEL]] : i32)
+! CHECK:           }
+! CHECK:           omp.atomic.read %{{.*}} = %[[XCONV]] : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:         }
+subroutine atomic_compare_capture_logical(x, e, d, v)
+  logical :: x, e, d, v
+  !$omp atomic compare capture
+    if (x .eqv. e) x = d
+    v = x
+  !$omp end atomic
+end
diff --git a/flang/test/Parser/OpenMP/atomic-unparse.f90 b/flang/test/Parser/OpenMP/atomic-unparse.f90
index 521c1bb2811e2..0d30cd48035ea 100644
--- a/flang/test/Parser/OpenMP/atomic-unparse.f90
+++ b/flang/test/Parser/OpenMP/atomic-unparse.f90
@@ -4,6 +4,7 @@ program main
    implicit none
    integer :: i, j = 10
    integer :: k
+   logical :: l1, l2, l3, l4
 !READ
 !$omp atomic read
    i = j
@@ -225,6 +226,12 @@ program main
    end if
 !$omp end atomic
 
+!COMPARE CAPTURE LOGICAL
+!$omp atomic compare capture
+   if (l1 .eqv. l2) l1 = l3
+   l4 = l1
+!$omp end atomic
+
 !ATOMIC
 !$omp atomic
    i = j
@@ -342,6 +349,11 @@ end program main
 !CHECK: !$OMP ATOMIC COMPARE CAPTURE
 !CHECK: !$OMP END ATOMIC
 
+!COMPARE CAPTURE LOGICAL
+
+!CHECK: !$OMP ATOMIC COMPARE CAPTURE
+!CHECK: !$OMP END ATOMIC
+
 !ATOMIC
 !CHECK: !$OMP ATOMIC
 !CHECK: !$OMP ATOMIC SEQ_CST
diff --git a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
index 5b1527bcb84ec..a61c43e2fd9cd 100644
--- a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
+++ b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
@@ -488,6 +488,19 @@ def AtomicCompareOpInterface : OpInterface<"AtomicCompareOpInterface"> {
               }
             }
             break;
+          } else if (llvm::is_contained(
+                         {"arith.maxsi", "arith.minsi", "arith.maxui",
+                          "arith.minui", "arith.maximumf", "arith.minimumf",
+                          "llvm.intr.smax", "llvm.intr.smin",
+                          "llvm.intr.umax", "llvm.intr.umin",
+                          "llvm.intr.maxnum", "llvm.intr.minnum"},
+                         opName)) {
+            // The canonicalizer may fold select(cmpi slt, e, x) into
+            // maxsi/minsi (or the float equivalents). After fir-to-llvm-ir
+            // these become llvm.intr.smax/smin etc. All are valid
+            // representations of atomic compare min/max operations.
+            foundComparison = true;
+            break;
           }
         }
 
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index e1fb08d780753..8e49571cd2461 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -12,6 +12,7 @@
 //===----------------------------------------------------------------------===//
 #include "mlir/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.h"
 #include "mlir/Analysis/TopologicalSortUtils.h"
+#include "mlir/Dialect/Arith/IR/Arith.h"
 #include "mlir/Dialect/LLVMIR/LLVMDialect.h"
 #include "mlir/Dialect/LLVMIR/LLVMTypes.h"
 #include "mlir/Dialect/OpenMP/OpenMPDialect.h"
@@ -5369,6 +5370,102 @@ convertFCmpPredicateToAtomicCompareOp(LLVM::FCmpPredicate predicate) {
   }
 }
 
+/// Holds the extracted comparison pattern information from an atomic compare
+/// region.
+struct AtomicComparePatternInfo {
+  llvm::omp::OMPAtomicCompareOp compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
+  llvm::Value *eVal = nullptr;
+  llvm::Value *dVal = nullptr;
+  bool isXBinopExpr = false;
+  bool isSigned = false;
+};
+
+/// Extract comparison predicate, expected value (e), desired value (d), and
+/// related flags from an atomic compare region block by scanning for
+/// icmp/fcmp/select/min/max operations.
+static LogicalResult extractAtomicComparePattern(
+    Block &block,
+    llvm::function_ref<llvm::Value *(mlir::Value)> materializeValue,
+    omp::AtomicCompareOp atomicCompareOp, AtomicComparePatternInfo &info) {
+  for (Operation &op : block.getOperations()) {
+    // Pre-filter: skip icmps that don't involve the block argument
+    // (e.g., truthiness extractions from logical-to-integer conversion).
+    if (auto icmpOp = dyn_cast<LLVM::ICmpOp>(op);
+        icmpOp && icmpOp.getOperand(0) != block.getArgument(0) &&
+        icmpOp.getOperand(1) != block.getArgument(0))
+      continue;
+
+    LogicalResult result =
+        llvm::TypeSwitch<Operation *, LogicalResult>(&op)
+            .Case<LLVM::ICmpOp>([&](LLVM::ICmpOp icmpOp) -> LogicalResult {
+              auto maybeOp =
+                  convertICmpPredicateToAtomicCompareOp(icmpOp.getPredicate());
+              if (!maybeOp)
+                return atomicCompareOp.emitError(
+                    "unsupported comparison predicate in atomic compare");
+              info.compareOp = *maybeOp;
+              LLVM::ICmpPredicate pred = icmpOp.getPredicate();
+              info.isSigned = (pred == LLVM::ICmpPredicate::slt ||
+                               pred == LLVM::ICmpPredicate::sgt ||
+                               pred == LLVM::ICmpPredicate::sle ||
+                               pred == LLVM::ICmpPredicate::sge);
+              info.isXBinopExpr =
+                  (icmpOp.getOperand(0) == block.getArgument(0));
+              mlir::Value eOperand = info.isXBinopExpr ? icmpOp.getOperand(1)
+                                                       : icmpOp.getOperand(0);
+              info.eVal = materializeValue(eOperand);
+              return success();
+            })
+            .Case<LLVM::FCmpOp>([&](LLVM::FCmpOp fcmpOp) -> LogicalResult {
+              auto maybeOp =
+                  convertFCmpPredicateToAtomicCompareOp(fcmpOp.getPredicate());
+              if (!maybeOp)
+                return atomicCompareOp.emitError(
+                    "unsupported comparison predicate in atomic compare");
+              info.compareOp = *maybeOp;
+              info.isXBinopExpr =
+                  (fcmpOp.getOperand(0) == block.getArgument(0));
+              mlir::Value eOperand = info.isXBinopExpr ? fcmpOp.getOperand(1)
+                                                       : fcmpOp.getOperand(0);
+              info.eVal = materializeValue(eOperand);
+              return success();
+            })
+            .Case<LLVM::SelectOp>([&](LLVM::SelectOp selectOp) {
+              if (!info.dVal)
+                info.dVal = materializeValue(selectOp.getTrueValue());
+              return success();
+            })
+            .Case<mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
+                  mlir::arith::MaxUIOp, mlir::arith::MinUIOp,
+                  mlir::arith::MaximumFOp, mlir::arith::MinimumFOp,
+                  LLVM::SMaxOp, LLVM::SMinOp, LLVM::UMaxOp, LLVM::UMinOp,
+                  LLVM::MaxNumOp, LLVM::MinNumOp>([&](Operation *) {
+              // Canonicalized min/max ops (arith or LLVM intrinsic form).
+              // max(x,e) came from slt/ult/olt -> OMPAtomicCompareOp::MIN
+              // min(x,e) came from sgt/ugt/ogt -> OMPAtomicCompareOp::MAX
+              // (OMPIRBuilder inverts: MIN->atomicrmw max, MAX->atomicrmw min)
+              bool isMax = isa<mlir::arith::MaxSIOp, mlir::arith::MaxUIOp,
+                               mlir::arith::MaximumFOp, LLVM::SMaxOp,
+                               LLVM::UMaxOp, LLVM::MaxNumOp>(op);
+              info.compareOp = isMax ? llvm::omp::OMPAtomicCompareOp::MIN
+                                     : llvm::omp::OMPAtomicCompareOp::MAX;
+              info.isSigned = isa<mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
+                                  LLVM::SMaxOp, LLVM::SMinOp>(op);
+              info.isXBinopExpr = (op.getOperand(0) == block.getArgument(0));
+              mlir::Value eOperand =
+                  info.isXBinopExpr ? op.getOperand(1) : op.getOperand(0);
+              info.eVal = materializeValue(eOperand);
+              info.dVal = info.eVal;
+              return success();
+            })
+            .Default([](Operation *) { return success(); });
+
+    if (failed(result))
+      return result;
+  }
+  return success();
+}
+
 static LogicalResult
 convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
                         llvm::IRBuilderBase &builder,
@@ -5409,7 +5506,11 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
     // Pre-translate non-pattern operations inside the compare region.
     auto isAtomicComparePatternOp = [](Operation &op) {
       return llvm::isa<LLVM::ICmpOp, LLVM::FCmpOp, LLVM::SelectOp, LLVM::AndOp,
-                       LLVM::OrOp>(op);
+                       LLVM::OrOp, LLVM::SMaxOp, LLVM::SMinOp, LLVM::UMaxOp,
+                       LLVM::UMinOp, LLVM::MaxNumOp, LLVM::MinNumOp,
+                       mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
+                       mlir::arith::MaxUIOp, mlir::arith::MinUIOp,
+                       mlir::arith::MaximumFOp, mlir::arith::MinimumFOp>(op);
     };
     for (Operation &op : block.without_terminator()) {
       if (isAtomicComparePatternOp(op))
@@ -5443,47 +5544,16 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
     };
 
     // Extract comparison predicate, eVal, and dVal from the region.
-    llvm::omp::OMPAtomicCompareOp compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
-    llvm::Value *eVal = nullptr;
-    llvm::Value *dVal = nullptr;
-    bool isXBinopExpr = false;
+    AtomicComparePatternInfo patternInfo;
+    if (failed(extractAtomicComparePattern(block, materializeValue,
+                                           atomicCompareOp, patternInfo)))
+      return failure();
 
-    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);
-
-        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());
-      }
-    }
+    llvm::omp::OMPAtomicCompareOp compareOp = patternInfo.compareOp;
+    llvm::Value *eVal = patternInfo.eVal;
+    llvm::Value *dVal = patternInfo.dVal;
+    bool isXBinopExpr = patternInfo.isXBinopExpr;
+    isSigned = patternInfo.isSigned;
 
     if (!eVal)
       return atomicCompareOp.emitError(
@@ -5529,17 +5599,42 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
     // Postfix: v = select(success, D, old) — captures new value of x.
     if (isPostfixCapture && !isFailOnly) {
       llvm::BasicBlock *curBB = builder.GetInsertBlock();
-      llvm::Instruction *cmpxchgInst = nullptr;
+      llvm::Value *oldVal = nullptr;
+      llvm::Value *successVal = nullptr;
+
+      // Integer path (and non-HandleFPNegZero FP path): a single cmpxchg
+      // lives in the current block.
       for (auto &inst : llvm::reverse(*curBB)) {
         if (isa<llvm::AtomicCmpXchgInst>(&inst)) {
-          cmpxchgInst = &inst;
+          oldVal = builder.CreateExtractValue(&inst, /*Idxs=*/0);
+          successVal = builder.CreateExtractValue(&inst, /*Idxs=*/1);
           break;
         }
       }
-      assert(cmpxchgInst && "expected cmpxchg instruction");
-      llvm::Value *oldVal = builder.CreateExtractValue(cmpxchgInst, /*Idxs=*/0);
-      llvm::Value *successVal =
-          builder.CreateExtractValue(cmpxchgInst, /*Idxs=*/1);
+
+      // FP HandleFPNegZero path: the OMPIRBuilder emits a multi-block
+      // structure (NaN / ±0.0 handling) with cmpxchg in predecessor
+      // blocks. Results are merged via PHI nodes in the current (exit)
+      // block: an i1 PHI for success and a bitcast of an integer PHI
+      // for the old FP value.
+      if (!oldVal) {
+        for (auto &inst : *curBB) {
+          auto *phi = dyn_cast<llvm::PHINode>(&inst);
+          if (!phi)
+            break;
+          if (phi->getType()->isIntegerTy(1))
+            successVal = phi;
+        }
+        for (auto &inst : *curBB) {
+          if (auto *bc = dyn_cast<llvm::BitCastInst>(&inst)) {
+            oldVal = bc;
+            break;
+          }
+        }
+      }
+
+      assert(oldVal && "expected cmpxchg or PHI+bitcast for compare capture");
+      assert(successVal && "expected success flag for compare capture");
       llvm::Value *newVal = builder.CreateSelect(successVal, dVal, oldVal);
       builder.CreateStore(newVal, llvmAtomicV.Var, llvmAtomicV.IsVolatile);
     }
@@ -5686,7 +5781,11 @@ convertOmpAtomicCompare(omp::AtomicCompareOp atomicCompareOp,
 
   auto isAtomicComparePatternOp = [](Operation &op) {
     return llvm::isa<LLVM::ICmpOp, LLVM::FCmpOp, LLVM::SelectOp, LLVM::AndOp,
-                     LLVM::OrOp>(op);
+                     LLVM::OrOp, LLVM::SMaxOp, LLVM::SMinOp, LLVM::UMaxOp,
+                     LLVM::UMinOp, LLVM::MaxNumOp, LLVM::MinNumOp,
+                     mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
+                     mlir::arith::MaxUIOp, mlir::arith::MinUIOp,
+                     mlir::arith::MaximumFOp, mlir::arith::MinimumFOp>(op);
   };
 
   // Pre-translate operations inside the region that compute e and d (e.g.,
@@ -5859,54 +5958,15 @@ convertOmpAtomicCompare(omp::AtomicCompareOp atomicCompareOp,
     }
     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;
-      }
-    }
+    AtomicComparePatternInfo patternInfo;
+    if (failed(extractAtomicComparePattern(block, materializeValue,
+                                           atomicCompareOp, patternInfo)))
+      return failure();
+    compareOp = patternInfo.compareOp;
+    eVal = patternInfo.eVal;
+    dVal = patternInfo.dVal;
+    isXBinopExpr = patternInfo.isXBinopExpr;
+    isSigned = patternInfo.isSigned;
   }
 
   if (!eVal)
diff --git a/mlir/test/Dialect/OpenMP/ops.mlir b/mlir/test/Dialect/OpenMP/ops.mlir
index 6b13be481e0e0..cc40506b909b1 100644
--- a/mlir/test/Dialect/OpenMP/ops.mlir
+++ b/mlir/test/Dialect/OpenMP/ops.mlir
@@ -2161,6 +2161,30 @@ func.func @omp_atomic_compare_capture_fail_only(%v: memref<i32>, %x: memref<i32>
   return
 }
 
+// CHECK-LABEL: omp_atomic_compare_capture_real
+// CHECK-SAME: (%[[V:.*]]: memref<f32>, %[[X:.*]]: memref<f32>, %[[E:.*]]: f32, %[[D:.*]]: f32)
+func.func @omp_atomic_compare_capture_real(%v: memref<f32>, %x: memref<f32>, %e: f32, %d: f32) {
+  // CHECK: omp.atomic.capture {
+  // CHECK-NEXT: omp.atomic.compare %[[X]] : memref<f32> {
+  // CHECK-NEXT: ^bb0(%[[XVAL:.*]]: f32):
+  // CHECK-NEXT:   %[[CMP:.*]] = arith.cmpf oeq, %[[XVAL]], %[[E]] : f32
+  // CHECK-NEXT:   %[[SEL:.*]] = arith.select %[[CMP]], %[[D]], %[[XVAL]] : f32
+  // CHECK-NEXT:   omp.yield(%[[SEL]] : f32)
+  // CHECK-NEXT: }
+  // CHECK-NEXT: omp.atomic.read %[[V]] = %[[X]] : memref<f32>, memref<f32>, f32
+  // CHECK-NEXT: }
+  omp.atomic.capture {
+    omp.atomic.compare %x : memref<f32> {
+    ^bb0(%xval: f32):
+      %cmp = arith.cmpf oeq, %xval, %e : f32
+      %sel = arith.select %cmp, %d, %xval : f32
+      omp.yield(%sel : f32)
+    }
+    omp.atomic.read %v = %x : memref<f32>, memref<f32>, f32
+  }
+  return
+}
+
 // CHECK-LABEL: omp_sectionsop
 func.func @omp_sectionsop(%data_var1 : memref<i32>, %data_var2 : memref<i32>,
                      %data_var3 : memref<i32>, %redn_var : !llvm.ptr) {
diff --git a/mlir/test/Target/LLVMIR/openmp-llvm.mlir b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
index 377bd88894854..b779d20f42d0c 100644
--- a/mlir/test/Target/LLVMIR/openmp-llvm.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
@@ -2882,6 +2882,54 @@ llvm.func @omp_atomic_compare_capture_fail_only(%x : !llvm.ptr, %v : !llvm.ptr,
 }
 // -----
 
+// CHECK-LABEL: @omp_atomic_compare_capture_real
+// CHECK-SAME: (ptr %[[X:.*]], ptr %[[V:.*]], float %[[E:.*]], float %[[D:.*]])
+llvm.func @omp_atomic_compare_capture_real(%x : !llvm.ptr, %v : !llvm.ptr, %e : f32, %d : f32) {
+  // Real compare+capture: uses HandleFPNegZero multi-block path
+  // CHECK: %[[EBITS:.*]] = bitcast float %[[E]] to i32
+  // CHECK: %[[DBITS:.*]] = bitcast float %[[D]] to i32
+  // CHECK: %[[XI:.*]] = load atomic i32, ptr %[[X]] monotonic{{.*}}
+  // CHECK: %[[XFP:.*]] = bitcast i32 %[[XI]] to float
+  // Part 1: NaN check
+  // CHECK: %[[E_NAN:.*]] = fcmp uno float %[[E]], %[[E]]
+  // CHECK: %[[X_NAN:.*]] = fcmp uno float %[[XFP]], %[[XFP]]
+  // CHECK: %[[EITHER_NAN:.*]] = or i1 %[[E_NAN]], %[[X_NAN]]
+  // CHECK: br i1 %[[EITHER_NAN]], label %[[NAN:.*]], label %[[NOTNAN:.*]]
+  // CHECK: [[NAN]]:
+  // CHECK: br label %[[EXIT:.*]]
+  // Part 2: Both-zero check
+  // CHECK: [[NOTNAN]]:
+  // CHECK: %[[XISZERO:.*]] = fcmp oeq float %[[XFP]], 0.000000e+00
+  // CHECK: %[[EISZERO:.*]] = fcmp oeq float %[[E]], 0.000000e+00
+  // CHECK: %[[BOTHZERO:.*]] = and i1 %[[XISZERO]], %[[EISZERO]]
+  // CHECK: br i1 %[[BOTHZERO]], label %[[ZERO:.*]], label %[[NORMAL:.*]]
+  // CHECK: [[ZERO]]:
+  // CHECK: cmpxchg ptr %[[X]], i32 %[[XI]], i32 %[[DBITS]] monotonic monotonic{{.*}}
+  // CHECK: br label %[[EXIT]]
+  // Part 3: Normal cmpxchg
+  // CHECK: [[NORMAL]]:
+  // CHECK: cmpxchg ptr %[[X]], i32 %[[EBITS]], i32 %[[DBITS]] monotonic monotonic{{.*}}
+  // CHECK: br label %[[EXIT]]
+  // Exit: select v = (success ? d : old_x)
+  // CHECK: [[EXIT]]:
+  // CHECK: %[[OLD:.*]] = phi i32 {{.*}}
+  // CHECK: %[[OK:.*]] = phi i1 {{.*}}
+  // CHECK: %[[OLDFP:.*]] = bitcast i32 %[[OLD]] to float
+  // CHECK: %[[VVAL:.*]] = select i1 %[[OK]], float %[[D]], float %[[OLDFP]]
+  // CHECK: store float %[[VVAL]], ptr %[[V]]{{.*}}
+  omp.atomic.capture {
+    omp.atomic.compare %x : !llvm.ptr {
+    ^bb0(%xval : f32):
+      %cmp = llvm.fcmp "oeq" %xval, %e : f32
+      %sel = llvm.select %cmp, %d, %xval : i1, f32
+      omp.yield(%sel : f32)
+    }
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, f32
+  }
+  llvm.return
+}
+// -----
+
 // CHECK-LABEL: @omp_sections_empty
 llvm.func @omp_sections_empty() -> () {
   omp.sections {

>From d42a6412d158c581c76cadf7d4626b4235e60725 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Wed, 1 Jul 2026 07:25:53 -0500
Subject: [PATCH 08/11] Handling a crash in logical "omp atomic
 compare-capture" inside "omp parallel". Adding some extra tests.

---
 flang/lib/Semantics/check-omp-atomic.cpp      |  19 ++-
 .../Integration/OpenMP/atomic-compare.f90     |  27 ++++
 flang/test/Lower/OpenMP/atomic-compare.f90    | 117 ++++++++++++++++++
 .../test/Semantics/OpenMP/atomic-compare.f90  |  12 ++
 4 files changed, 174 insertions(+), 1 deletion(-)

diff --git a/flang/lib/Semantics/check-omp-atomic.cpp b/flang/lib/Semantics/check-omp-atomic.cpp
index ec307be469982..88997df26e4d0 100644
--- a/flang/lib/Semantics/check-omp-atomic.cpp
+++ b/flang/lib/Semantics/check-omp-atomic.cpp
@@ -419,6 +419,22 @@ static bool IsMaybeAtomicWrite(const evaluate::Assignment &assign) {
   return HasStorageOverlap(assign.lhs, assign.rhs) == nullptr;
 }
 
+// Determine whether two designators refer to the same atomic variable.
+// The comparison tolerates OpenMP host-association: a shared variable
+// referenced inside a parallel region may be designated through a
+// host-associated symbol in one statement and through its ultimate symbol
+// in another, even though both denote the same storage. Comparing the
+// (ultimate) symbol vectors together with the source form distinguishes
+// different variables and different constant subscripts (e.g. a(1) vs a(2))
+// while treating host-associated and ultimate references as equal.
+static bool IsSameAtomicVariable(const SomeExpr &x, const SomeExpr &y) {
+  if (x == y) {
+    return true;
+  }
+  return evaluate::GetSymbolVector(x) == evaluate::GetSymbolVector(y) &&
+      x.AsFortran() == y.AsFortran();
+}
+
 static void SetExpr(parser::TypedExpr &expr, MaybeExpr value) {
   if (value) {
     expr.Reset(new evaluate::GenericExprWrapper(std::move(value)),
@@ -822,7 +838,8 @@ void OmpStructureChecker::CheckAtomicCaptureAssignment(
     CheckAtomicVariable(
         atom, rsrc, /*checkTypeOnPointer=*/!IsPointerAssignment(capture));
     // This part should have been checked prior to calling this function.
-    assert(*GetConvertInput(capture.rhs) == atom &&
+    assert(GetConvertInput(capture.rhs) &&
+        IsSameAtomicVariable(*GetConvertInput(capture.rhs), atom) &&
         "This cannot be a capture assignment");
     CheckStorageOverlap(atom, {cap}, source);
   }
diff --git a/flang/test/Integration/OpenMP/atomic-compare.f90 b/flang/test/Integration/OpenMP/atomic-compare.f90
index f30349c42e33e..e98b25e4b4539 100644
--- a/flang/test/Integration/OpenMP/atomic-compare.f90
+++ b/flang/test/Integration/OpenMP/atomic-compare.f90
@@ -394,3 +394,30 @@ subroutine atomic_compare_capture_logical(x, e, d, v)
   !$omp end atomic
 end
 
+! Logical compare+capture inside a parallel region.
+!CHECK-LABEL: define void @atomic_compare_capture_logical_parallel_(
+!CHECK-SAME: ptr noalias %[[X:.*]])
+!CHECK: %[[CGEP:.*]] = getelementptr { ptr }, ptr %structArg, i32 0, i32 0
+!CHECK: store ptr %[[X]], ptr %[[CGEP]]
+!CHECK: call void {{.*}}@__kmpc_fork_call{{.*}}@atomic_compare_capture_logical_parallel_..omp_par
+!CHECK-LABEL: define internal void @atomic_compare_capture_logical_parallel_..omp_par(
+!CHECK-SAME: ptr noalias %{{.*}}, ptr noalias %{{.*}}, ptr %[[STRUCTARG:.*]])
+!CHECK: %[[GEP:.*]] = getelementptr { ptr }, ptr %[[STRUCTARG]], i32 0, i32 0
+!CHECK: %[[SHARED:.*]] = load ptr, ptr %[[GEP]]
+!CHECK: %[[RES:.*]] = cmpxchg ptr %[[SHARED]], i32 %{{.*}}, i32 %{{.*}} monotonic monotonic{{.*}}
+!CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+!CHECK: store i32 %[[OLD]], ptr %{{.*}}
+subroutine atomic_compare_capture_logical_parallel(x)
+  logical :: x, expected, desired, old_value
+  !$omp parallel private(old_value, expected, desired)
+    expected = .true.
+    desired  = .false.
+    !$omp atomic compare capture
+      old_value = x
+      if (x .eqv. expected) then
+        x = desired
+      end if
+    !$omp end atomic
+  !$omp end parallel
+end
+
diff --git a/flang/test/Lower/OpenMP/atomic-compare.f90 b/flang/test/Lower/OpenMP/atomic-compare.f90
index 1219d05296e4d..ccc44a370be79 100644
--- a/flang/test/Lower/OpenMP/atomic-compare.f90
+++ b/flang/test/Lower/OpenMP/atomic-compare.f90
@@ -305,6 +305,83 @@ subroutine atomic_compare_capture_real(x, e, d, v)
   !$omp end atomic
 end
 
+! Compare-capture with an explicit seq_cst memory order (prefix read form).
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_seq_cst(
+! CHECK:         omp.atomic.capture memory_order(seq_cst) {
+! CHECK:           omp.atomic.read %[[V_DECL:.*]]#0 = %[[X_DECL:.*]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:           omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<i32> {
+! CHECK:           ^bb0(%[[XVAL:.*]]: i32):
+! CHECK:             %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %{{.*}} : i32
+! CHECK:             %[[SEL:.*]] = arith.select %[[CMP]], %{{.*}}, %[[XVAL]] : i32
+! CHECK:             omp.yield(%[[SEL]] : i32)
+! CHECK:           }
+! CHECK:         }
+subroutine atomic_compare_capture_seq_cst(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture seq_cst
+    v = x
+    if (x .eq. e) x = d
+  !$omp end atomic
+end
+
+! Compare-capture with an explicit acquire memory order (postfix read form).
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_acquire(
+! CHECK:         omp.atomic.capture memory_order(acquire) {
+! CHECK:           omp.atomic.compare %[[X_DECL:.*]]#0 : !fir.ref<i32> {
+! CHECK:           ^bb0(%[[XVAL:.*]]: i32):
+! CHECK:             %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %{{.*}} : i32
+! CHECK:             %[[SEL:.*]] = arith.select %[[CMP]], %{{.*}}, %[[XVAL]] : i32
+! CHECK:             omp.yield(%[[SEL]] : i32)
+! CHECK:           }
+! CHECK:           omp.atomic.read %{{.*}} = %[[X_DECL]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:         }
+subroutine atomic_compare_capture_acquire(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture acquire
+    if (x .eq. e) x = d
+    v = x
+  !$omp end atomic
+end
+
+! Compare-capture with an explicit release memory order (prefix read form).
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_release(
+! CHECK:         omp.atomic.capture memory_order(release) {
+! CHECK:           omp.atomic.read %[[V_DECL:.*]]#0 = %[[X_DECL:.*]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:           omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<i32> {
+! CHECK:           ^bb0(%[[XVAL:.*]]: i32):
+! CHECK:             %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %{{.*}} : i32
+! CHECK:             %[[SEL:.*]] = arith.select %[[CMP]], %{{.*}}, %[[XVAL]] : i32
+! CHECK:             omp.yield(%[[SEL]] : i32)
+! CHECK:           }
+! CHECK:         }
+subroutine atomic_compare_capture_release(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture release
+    v = x
+    if (x .eq. e) x = d
+  !$omp end atomic
+end
+
+! Compare-capture with an explicit acq_rel memory order (postfix read form).
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_acq_rel(
+! CHECK:         omp.atomic.capture memory_order(acq_rel) {
+! CHECK:           omp.atomic.compare %[[X_DECL:.*]]#0 : !fir.ref<i32> {
+! CHECK:           ^bb0(%[[XVAL:.*]]: i32):
+! CHECK:             %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %{{.*}} : i32
+! CHECK:             %[[SEL:.*]] = arith.select %[[CMP]], %{{.*}}, %[[XVAL]] : i32
+! CHECK:             omp.yield(%[[SEL]] : i32)
+! CHECK:           }
+! CHECK:           omp.atomic.read %{{.*}} = %[[X_DECL]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:         }
+subroutine atomic_compare_capture_acq_rel(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture acq_rel
+    if (x .eq. e) x = d
+    v = x
+  !$omp end atomic
+end
+
+
 ! CHECK-LABEL: func.func @_QPatomic_compare_capture_logical(
 ! CHECK-SAME:    %[[X:.*]]: !fir.ref<!fir.logical<4>> {fir.bindc_name = "x"},
 ! CHECK-SAME:    %[[E:.*]]: !fir.ref<!fir.logical<4>> {fir.bindc_name = "e"},
@@ -335,3 +412,43 @@ subroutine atomic_compare_capture_logical(x, e, d, v)
     v = x
   !$omp end atomic
 end
+
+! Logical compare-capture inside a parallel region. The shared atom 'x' is
+! host-associated, and the capture reads it while the conditional update writes
+! it. Verify the atom is converted to i32 once (above the atomic region) and
+! that the capture reads then compares the same converted reference.
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_logical_parallel(
+! CHECK-SAME:    %[[X:.*]]: !fir.ref<!fir.logical<4>> {fir.bindc_name = "x"})
+! CHECK:         %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}Ex"
+! CHECK:         omp.parallel private(
+! CHECK:           %[[OLD_DECL:.*]]:2 = hlfir.declare %{{.*}}Eold_value"
+! CHECK:           %[[E_DECL:.*]]:2 = hlfir.declare %{{.*}}Eexpected"
+! CHECK:           %[[ELOAD:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<!fir.logical<4>>
+! CHECK:           %[[XCONV:.*]] = fir.convert %[[X_DECL]]#0 : (!fir.ref<!fir.logical<4>>) -> !fir.ref<i32>
+! CHECK:           %[[ECONV:.*]] = fir.convert %[[ELOAD]] : (!fir.logical<4>) -> i32
+! CHECK:           omp.atomic.capture memory_order(relaxed) {
+! CHECK:             omp.atomic.read %[[ATOMVAL:.*]] = %[[XCONV]] : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:             omp.atomic.compare %[[XCONV]] : !fir.ref<i32> {
+! CHECK:             ^bb0(%[[XVAL:.*]]: i32):
+! CHECK:               %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %[[ECONV]] : i32
+! CHECK:               %[[SEL:.*]] = arith.select %[[CMP]], %{{.*}}, %[[XVAL]] : i32
+! CHECK:               omp.yield(%[[SEL]] : i32)
+! CHECK:             }
+! CHECK:           }
+! CHECK:           %[[RES:.*]] = fir.load %[[ATOMVAL]] : !fir.ref<i32>
+! CHECK:           %[[RESCONV:.*]] = fir.convert %[[RES]] : (i32) -> !fir.logical<4>
+! CHECK:           fir.store %[[RESCONV]] to %[[OLD_DECL]]#0 : !fir.ref<!fir.logical<4>>
+! CHECK:           omp.terminator
+subroutine atomic_compare_capture_logical_parallel(x)
+  logical :: x, expected, desired, old_value
+  !$omp parallel private(old_value, expected, desired)
+    expected = .true.
+    desired  = .false.
+    !$omp atomic compare capture
+      old_value = x
+      if (x .eqv. expected) then
+        x = desired
+      end if
+    !$omp end atomic
+  !$omp end parallel
+end
diff --git a/flang/test/Semantics/OpenMP/atomic-compare.f90 b/flang/test/Semantics/OpenMP/atomic-compare.f90
index 0e53729c2f02a..5cee2359f52be 100644
--- a/flang/test/Semantics/OpenMP/atomic-compare.f90
+++ b/flang/test/Semantics/OpenMP/atomic-compare.f90
@@ -9,6 +9,7 @@
 
   real a, b, c
   logical :: r, s
+  logical :: lx, le, ld, lo
   a = 1.0
   b = 2.0
   c = 3.0
@@ -58,6 +59,17 @@
   if (r) b = c
   !$omp end atomic
 
+  ! Compare-capture with a LOGICAL atom using .eqv. inside a parallel region.
+  ! Regression: the host-associated atom (from the update) must be recognized
+  ! as the same variable as the captured one, even though OpenMP resolves them
+  ! through different (but ultimately identical) symbols.
+  !$omp atomic compare capture
+  lo = lx
+  if (lx .eqv. le) then
+    lx = ld
+  end if
+  !$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

>From b6f101ded4b5847a466a27c6a37185a3203b6ebc Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Fri, 10 Jul 2026 00:50:42 -0500
Subject: [PATCH 09/11] Added exhaustive tests covering all atomic compare
 capture combinations Handling changes for atomic compare capture were not
 covered earlier. Completed runs of llvm-test-suite and Fujitsu flang
 test-suite.

---
 flang/lib/Lower/OpenMP/Atomic.cpp             |  14 +
 flang/test/Fir/atomic-compare-capture.fir     | 343 ++++++++++++++
 .../OpenMP/atomic-compare-capture.f90         | 443 ++++++++++++++++++
 .../Todo/atomic-compare-capture-result.f90    |  20 +
 .../Lower/OpenMP/Todo/atomic-compare-fail.f90 |   8 -
 .../Lower/OpenMP/atomic-compare-capture.f90   | 356 ++++++++++++++
 flang/test/Lower/OpenMP/atomic-compare.f90    | 116 +++++
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      | 398 ++++++++++++----
 .../LLVMIR/openmp-atomic-compare-capture.mlir | 407 ++++++++++++++++
 9 files changed, 2007 insertions(+), 98 deletions(-)
 create mode 100644 flang/test/Fir/atomic-compare-capture.fir
 create mode 100644 flang/test/Integration/OpenMP/atomic-compare-capture.f90
 create mode 100644 flang/test/Lower/OpenMP/Todo/atomic-compare-capture-result.f90
 create mode 100644 flang/test/Lower/OpenMP/atomic-compare-capture.f90
 create mode 100644 mlir/test/Target/LLVMIR/openmp-atomic-compare-capture.mlir

diff --git a/flang/lib/Lower/OpenMP/Atomic.cpp b/flang/lib/Lower/OpenMP/Atomic.cpp
index 3811ea3d40bbd..ca37eb4a73375 100644
--- a/flang/lib/Lower/OpenMP/Atomic.cpp
+++ b/flang/lib/Lower/OpenMP/Atomic.cpp
@@ -606,6 +606,20 @@ void Fortran::lower::omp::lowerAtomic(
       TODO(loc, "Compound clauses of OpenMP ATOMIC COMPARE");
     }
 
+    // The comparison-result forms of atomic compare
+    //     (e.g. `r = x == e;
+    //           if (r) x = d`)
+    // are accepted by semantics, but the assignment to `r` is
+    // not captured by the atomic analysis and would be silently dropped.
+    // Detect the extra assignment here and emit an explicit TODO instead of
+    // generating incorrect code.
+    if (!construct.IsCapture()) {
+      const parser::Block &body = std::get<parser::Block>(construct.t);
+      if (body.size() > 1)
+        TODO(loc, "atomic compare capturing the comparison result "
+                  "(e.g. 'r = x == e')");
+    }
+
     common::RelationalOperator relOpr = common::RelationalOperator::EQ;
     std::optional<semantics::SomeExpr> expectedExprStorage;
     bool isUnsigned = false;
diff --git a/flang/test/Fir/atomic-compare-capture.fir b/flang/test/Fir/atomic-compare-capture.fir
new file mode 100644
index 0000000000000..65264fbc688a8
--- /dev/null
+++ b/flang/test/Fir/atomic-compare-capture.fir
@@ -0,0 +1,343 @@
+// RUN: fir-opt --split-input-file --cfg-conversion --fir-to-llvm-ir="target=aarch64-unknown-linux-gnu" %s | FileCheck %s
+
+// Test FIR-to-LLVM-dialect conversion of OpenMP atomic compare-capture: the
+// !fir.ref operands become !llvm.ptr and the compare region's arith ops become
+// their llvm-dialect equivalents, while the omp.atomic.capture / .compare /
+// .read structure is preserved for the later MLIR-to-LLVM-IR translation.
+
+//
+// Integer postfix compare-capture: {compare, read}
+func.func @cc_int_postfix(%x: !fir.ref<i32>, %e: !fir.ref<i32>, %d: !fir.ref<i32>, %v: !fir.ref<i32>) {
+  %eval = fir.load %e : !fir.ref<i32>
+  %dval = fir.load %d : !fir.ref<i32>
+  omp.atomic.capture {
+    omp.atomic.compare %x : !fir.ref<i32> {
+    ^bb0(%xval: i32):
+      %c = arith.cmpi eq, %xval, %eval : i32
+      %s = arith.select %c, %dval, %xval : i32
+      omp.yield(%s : i32)
+    }
+    omp.atomic.read %v = %x : !fir.ref<i32>, !fir.ref<i32>, i32
+  }
+  return
+}
+
+// CHECK-LABEL: llvm.func @cc_int_postfix(
+// CHECK-SAME:  %[[X:.*]]: !llvm.ptr, %[[E:.*]]: !llvm.ptr, %[[D:.*]]: !llvm.ptr, %[[V:.*]]: !llvm.ptr) {
+// CHECK:    %[[EVAL:.*]] = llvm.load %[[E]] : !llvm.ptr -> i32
+// CHECK:    %[[DVAL:.*]] = llvm.load %[[D]] : !llvm.ptr -> i32
+// CHECK:    omp.atomic.capture {
+// CHECK:      omp.atomic.compare %[[X]] : !llvm.ptr {
+// CHECK:      ^bb0(%[[XVAL:.*]]: i32):
+// CHECK:        %[[CMP:.*]] = llvm.icmp "eq" %[[XVAL]], %[[EVAL]] : i32
+// CHECK:        %[[SEL:.*]] = llvm.select %[[CMP]], %[[DVAL]], %[[XVAL]] : i1, i32
+// CHECK:        omp.yield(%[[SEL]] : i32)
+// CHECK:      }
+// CHECK:      omp.atomic.read %[[V]] = %[[X]] : !llvm.ptr, !llvm.ptr, i32
+// CHECK:    }
+
+// -----
+
+// Integer prefix compare-capture: {read, compare}
+func.func @cc_int_prefix(%x: !fir.ref<i32>, %e: !fir.ref<i32>, %d: !fir.ref<i32>, %v: !fir.ref<i32>) {
+  %eval = fir.load %e : !fir.ref<i32>
+  %dval = fir.load %d : !fir.ref<i32>
+  omp.atomic.capture {
+    omp.atomic.read %v = %x : !fir.ref<i32>, !fir.ref<i32>, i32
+    omp.atomic.compare %x : !fir.ref<i32> {
+    ^bb0(%xval: i32):
+      %c = arith.cmpi eq, %xval, %eval : i32
+      %s = arith.select %c, %dval, %xval : i32
+      omp.yield(%s : i32)
+    }
+  }
+  return
+}
+
+// CHECK-LABEL: llvm.func @cc_int_prefix(
+// CHECK:    omp.atomic.capture {
+// CHECK:      omp.atomic.read %{{.*}} = %{{.*}} : !llvm.ptr, !llvm.ptr, i32
+// CHECK:      omp.atomic.compare %{{.*}} : !llvm.ptr {
+// CHECK:        llvm.icmp "eq"
+// CHECK:        llvm.select
+// CHECK:        omp.yield
+// CHECK:      }
+// CHECK:    }
+
+// -----
+
+// Integer fail-only compare-capture: {compare, read} with fail_only attribute.
+func.func @cc_int_failonly(%x: !fir.ref<i32>, %e: !fir.ref<i32>, %d: !fir.ref<i32>, %v: !fir.ref<i32>) {
+  %eval = fir.load %e : !fir.ref<i32>
+  %dval = fir.load %d : !fir.ref<i32>
+  omp.atomic.capture {
+    omp.atomic.compare %x : !fir.ref<i32> {
+    ^bb0(%xval: i32):
+      %c = arith.cmpi eq, %xval, %eval : i32
+      %s = arith.select %c, %dval, %xval : i32
+      omp.yield(%s : i32)
+    }
+    omp.atomic.read %v = %x : !fir.ref<i32>, !fir.ref<i32>, i32
+  } {fail_only}
+  return
+}
+
+// CHECK-LABEL: llvm.func @cc_int_failonly(
+// CHECK:    omp.atomic.capture {
+// CHECK:      omp.atomic.compare %{{.*}} : !llvm.ptr {
+// CHECK:        llvm.icmp "eq"
+// CHECK:      }
+// CHECK:      omp.atomic.read %{{.*}} = %{{.*}} : !llvm.ptr, !llvm.ptr, i32
+// CHECK:    } {fail_only}
+
+// -----
+
+// Real postfix compare-capture: !fir.ref<f32> -> !llvm.ptr, arith.cmpf -> llvm.fcmp.
+func.func @cc_real_postfix(%x: !fir.ref<f32>, %e: !fir.ref<f32>, %d: !fir.ref<f32>, %v: !fir.ref<f32>) {
+  %eval = fir.load %e : !fir.ref<f32>
+  %dval = fir.load %d : !fir.ref<f32>
+  omp.atomic.capture {
+    omp.atomic.compare %x : !fir.ref<f32> {
+    ^bb0(%xval: f32):
+      %c = arith.cmpf oeq, %xval, %eval : f32
+      %s = arith.select %c, %dval, %xval : f32
+      omp.yield(%s : f32)
+    }
+    omp.atomic.read %v = %x : !fir.ref<f32>, !fir.ref<f32>, f32
+  }
+  return
+}
+
+// CHECK-LABEL: llvm.func @cc_real_postfix(
+// CHECK-SAME:  %[[X:.*]]: !llvm.ptr, %[[E:.*]]: !llvm.ptr, %[[D:.*]]: !llvm.ptr, %[[V:.*]]: !llvm.ptr) {
+// CHECK:    %[[EVAL:.*]] = llvm.load %[[E]] : !llvm.ptr -> f32
+// CHECK:    %[[DVAL:.*]] = llvm.load %[[D]] : !llvm.ptr -> f32
+// CHECK:    omp.atomic.capture {
+// CHECK:      omp.atomic.compare %[[X]] : !llvm.ptr {
+// CHECK:      ^bb0(%[[XVAL:.*]]: f32):
+// CHECK:        %[[CMP:.*]] = llvm.fcmp "oeq" %[[XVAL]], %[[EVAL]] : f32
+// CHECK:        %[[SEL:.*]] = llvm.select %[[CMP]], %[[DVAL]], %[[XVAL]] : i1, f32
+// CHECK:        omp.yield(%[[SEL]] : f32)
+// CHECK:      }
+// CHECK:      omp.atomic.read %[[V]] = %[[X]] : !llvm.ptr, !llvm.ptr, f32
+// CHECK:    }
+
+// -----
+
+// Real prefix compare-capture.
+func.func @cc_real_prefix(%x: !fir.ref<f32>, %e: !fir.ref<f32>, %d: !fir.ref<f32>, %v: !fir.ref<f32>) {
+  %eval = fir.load %e : !fir.ref<f32>
+  %dval = fir.load %d : !fir.ref<f32>
+  omp.atomic.capture {
+    omp.atomic.read %v = %x : !fir.ref<f32>, !fir.ref<f32>, f32
+    omp.atomic.compare %x : !fir.ref<f32> {
+    ^bb0(%xval: f32):
+      %c = arith.cmpf oeq, %xval, %eval : f32
+      %s = arith.select %c, %dval, %xval : f32
+      omp.yield(%s : f32)
+    }
+  }
+  return
+}
+
+// CHECK-LABEL: llvm.func @cc_real_prefix(
+// CHECK:    omp.atomic.capture {
+// CHECK:      omp.atomic.read %{{.*}} = %{{.*}} : !llvm.ptr, !llvm.ptr, f32
+// CHECK:      omp.atomic.compare %{{.*}} : !llvm.ptr {
+// CHECK:        llvm.fcmp "oeq"
+// CHECK:      }
+// CHECK:    }
+
+// -----
+
+// Real fail-only compare-capture.
+func.func @cc_real_failonly(%x: !fir.ref<f32>, %e: !fir.ref<f32>, %d: !fir.ref<f32>, %v: !fir.ref<f32>) {
+  %eval = fir.load %e : !fir.ref<f32>
+  %dval = fir.load %d : !fir.ref<f32>
+  omp.atomic.capture {
+    omp.atomic.compare %x : !fir.ref<f32> {
+    ^bb0(%xval: f32):
+      %c = arith.cmpf oeq, %xval, %eval : f32
+      %s = arith.select %c, %dval, %xval : f32
+      omp.yield(%s : f32)
+    }
+    omp.atomic.read %v = %x : !fir.ref<f32>, !fir.ref<f32>, f32
+  } {fail_only}
+  return
+}
+
+// CHECK-LABEL: llvm.func @cc_real_failonly(
+// CHECK:    omp.atomic.capture {
+// CHECK:      omp.atomic.compare %{{.*}} : !llvm.ptr {
+// CHECK:        llvm.fcmp "oeq"
+// CHECK:      }
+// CHECK:      omp.atomic.read %{{.*}} = %{{.*}} : !llvm.ptr, !llvm.ptr, f32
+// CHECK:    } {fail_only}
+
+// -----
+
+// Complex postfix compare-capture: !fir.ref<complex<f32>> -> !llvm.ptr and the
+// fir.cmpc is decomposed into per-field llvm.fcmp + llvm.and over the struct.
+func.func @cc_complex_postfix(%x: !fir.ref<complex<f32>>, %e: !fir.ref<complex<f32>>, %d: !fir.ref<complex<f32>>, %v: !fir.ref<complex<f32>>) {
+  %eval = fir.load %e : !fir.ref<complex<f32>>
+  %dval = fir.load %d : !fir.ref<complex<f32>>
+  omp.atomic.capture {
+    omp.atomic.compare %x : !fir.ref<complex<f32>> {
+    ^bb0(%xval: complex<f32>):
+      %c = fir.cmpc "oeq", %xval, %eval : complex<f32>
+      %s = arith.select %c, %dval, %xval : complex<f32>
+      omp.yield(%s : complex<f32>)
+    }
+    omp.atomic.read %v = %x : !fir.ref<complex<f32>>, !fir.ref<complex<f32>>, complex<f32>
+  }
+  return
+}
+
+// CHECK-LABEL: llvm.func @cc_complex_postfix(
+// CHECK-SAME:  %[[X:.*]]: !llvm.ptr, %[[E:.*]]: !llvm.ptr, %[[D:.*]]: !llvm.ptr, %[[V:.*]]: !llvm.ptr) {
+// CHECK:    %[[EVAL:.*]] = llvm.load %[[E]] : !llvm.ptr -> !llvm.struct<(f32, f32)>
+// CHECK:    %[[DVAL:.*]] = llvm.load %[[D]] : !llvm.ptr -> !llvm.struct<(f32, f32)>
+// CHECK:    omp.atomic.capture {
+// CHECK:      omp.atomic.compare %[[X]] : !llvm.ptr {
+// CHECK:      ^bb0(%[[XVAL:.*]]: !llvm.struct<(f32, f32)>):
+// CHECK:        llvm.extractvalue %[[XVAL]][0]
+// CHECK:        llvm.fcmp "oeq"
+// CHECK:        llvm.extractvalue %[[XVAL]][1]
+// CHECK:        llvm.fcmp "oeq"
+// CHECK:        %[[AND:.*]] = llvm.and
+// CHECK:        %[[SEL:.*]] = llvm.select %[[AND]], %[[DVAL]], %[[XVAL]] : i1, !llvm.struct<(f32, f32)>
+// CHECK:        omp.yield(%[[SEL]] : !llvm.struct<(f32, f32)>)
+// CHECK:      }
+// CHECK:      omp.atomic.read %[[V]] = %[[X]] : !llvm.ptr, !llvm.ptr, !llvm.struct<(f32, f32)>
+// CHECK:    }
+
+// -----
+
+// Complex prefix compare-capture.
+func.func @cc_complex_prefix(%x: !fir.ref<complex<f32>>, %e: !fir.ref<complex<f32>>, %d: !fir.ref<complex<f32>>, %v: !fir.ref<complex<f32>>) {
+  %eval = fir.load %e : !fir.ref<complex<f32>>
+  %dval = fir.load %d : !fir.ref<complex<f32>>
+  omp.atomic.capture {
+    omp.atomic.read %v = %x : !fir.ref<complex<f32>>, !fir.ref<complex<f32>>, complex<f32>
+    omp.atomic.compare %x : !fir.ref<complex<f32>> {
+    ^bb0(%xval: complex<f32>):
+      %c = fir.cmpc "oeq", %xval, %eval : complex<f32>
+      %s = arith.select %c, %dval, %xval : complex<f32>
+      omp.yield(%s : complex<f32>)
+    }
+  }
+  return
+}
+
+// CHECK-LABEL: llvm.func @cc_complex_prefix(
+// CHECK:    omp.atomic.capture {
+// CHECK:      omp.atomic.read %{{.*}} = %{{.*}} : !llvm.ptr, !llvm.ptr, !llvm.struct<(f32, f32)>
+// CHECK:      omp.atomic.compare %{{.*}} : !llvm.ptr {
+// CHECK:        llvm.fcmp "oeq"
+// CHECK:        llvm.fcmp "oeq"
+// CHECK:        llvm.and
+// CHECK:      }
+// CHECK:    }
+
+// -----
+
+// Complex fail-only compare-capture.
+func.func @cc_complex_failonly(%x: !fir.ref<complex<f32>>, %e: !fir.ref<complex<f32>>, %d: !fir.ref<complex<f32>>, %v: !fir.ref<complex<f32>>) {
+  %eval = fir.load %e : !fir.ref<complex<f32>>
+  %dval = fir.load %d : !fir.ref<complex<f32>>
+  omp.atomic.capture {
+    omp.atomic.compare %x : !fir.ref<complex<f32>> {
+    ^bb0(%xval: complex<f32>):
+      %c = fir.cmpc "oeq", %xval, %eval : complex<f32>
+      %s = arith.select %c, %dval, %xval : complex<f32>
+      omp.yield(%s : complex<f32>)
+    }
+    omp.atomic.read %v = %x : !fir.ref<complex<f32>>, !fir.ref<complex<f32>>, complex<f32>
+  } {fail_only}
+  return
+}
+
+// CHECK-LABEL: llvm.func @cc_complex_failonly(
+// CHECK:    omp.atomic.capture {
+// CHECK:      omp.atomic.compare %{{.*}} : !llvm.ptr {
+// CHECK:        llvm.fcmp "oeq"
+// CHECK:        llvm.and
+// CHECK:      }
+// CHECK:      omp.atomic.read %{{.*}} = %{{.*}} : !llvm.ptr, !llvm.ptr, !llvm.struct<(f32, f32)>
+// CHECK:    } {fail_only}
+
+// -----
+
+// Integer min compare-capture (x > e): the compare region uses arith.cmpi sgt.
+func.func @cc_int_min(%x: !fir.ref<i32>, %e: !fir.ref<i32>, %v: !fir.ref<i32>) {
+  %eval = fir.load %e : !fir.ref<i32>
+  omp.atomic.capture {
+    omp.atomic.compare %x : !fir.ref<i32> {
+    ^bb0(%xval: i32):
+      %c = arith.cmpi sgt, %xval, %eval : i32
+      %s = arith.select %c, %eval, %xval : i32
+      omp.yield(%s : i32)
+    }
+    omp.atomic.read %v = %x : !fir.ref<i32>, !fir.ref<i32>, i32
+  }
+  return
+}
+
+// CHECK-LABEL: llvm.func @cc_int_min(
+// CHECK:    omp.atomic.capture {
+// CHECK:      omp.atomic.compare %{{.*}} : !llvm.ptr {
+// CHECK:        llvm.icmp "sgt"
+// CHECK:      }
+// CHECK:      omp.atomic.read %{{.*}} = %{{.*}} : !llvm.ptr, !llvm.ptr, i32
+// CHECK:    }
+
+// -----
+
+// Real min compare-capture (x > e): the compare region uses arith.cmpf ogt.
+func.func @cc_real_min(%x: !fir.ref<f32>, %e: !fir.ref<f32>, %v: !fir.ref<f32>) {
+  %eval = fir.load %e : !fir.ref<f32>
+  omp.atomic.capture {
+    omp.atomic.compare %x : !fir.ref<f32> {
+    ^bb0(%xval: f32):
+      %c = arith.cmpf ogt, %xval, %eval : f32
+      %s = arith.select %c, %eval, %xval : f32
+      omp.yield(%s : f32)
+    }
+    omp.atomic.read %v = %x : !fir.ref<f32>, !fir.ref<f32>, f32
+  }
+  return
+}
+
+// CHECK-LABEL: llvm.func @cc_real_min(
+// CHECK:    omp.atomic.capture {
+// CHECK:      omp.atomic.compare %{{.*}} : !llvm.ptr {
+// CHECK:        llvm.fcmp "ogt"
+// CHECK:      }
+// CHECK:      omp.atomic.read %{{.*}} = %{{.*}} : !llvm.ptr, !llvm.ptr, f32
+// CHECK:    }
+
+// -----
+
+// Weak clause: preserved through FIR-to-LLVM conversion.
+func.func @cc_weak(%x: !fir.ref<i32>, %e: !fir.ref<i32>, %d: !fir.ref<i32>, %v: !fir.ref<i32>) {
+  %eval = fir.load %e : !fir.ref<i32>
+  %dval = fir.load %d : !fir.ref<i32>
+  omp.atomic.capture {
+    omp.atomic.read %v = %x : !fir.ref<i32>, !fir.ref<i32>, i32
+    omp.atomic.compare %x : !fir.ref<i32> {
+    ^bb0(%xval: i32):
+      %c = arith.cmpi eq, %xval, %eval : i32
+      %s = arith.select %c, %dval, %xval : i32
+      omp.yield(%s : i32)
+    } {weak}
+  }
+  return
+}
+
+// CHECK-LABEL: llvm.func @cc_weak(
+// CHECK:    omp.atomic.capture {
+// CHECK:      omp.atomic.read %{{.*}} = %{{.*}} : !llvm.ptr, !llvm.ptr, i32
+// CHECK:      omp.atomic.compare %{{.*}} : !llvm.ptr {
+// CHECK:        llvm.icmp "eq"
+// CHECK:      } {weak}
+// CHECK:    }
diff --git a/flang/test/Integration/OpenMP/atomic-compare-capture.f90 b/flang/test/Integration/OpenMP/atomic-compare-capture.f90
new file mode 100644
index 0000000000000..fe97e7b12558b
--- /dev/null
+++ b/flang/test/Integration/OpenMP/atomic-compare-capture.f90
@@ -0,0 +1,443 @@
+!===----------------------------------------------------------------------===!
+! 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.
+!===----------------------------------------------------------------------===!
+
+!RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=51 %s -o - | FileCheck %s --check-prefix=HLFIR
+!RUN: %flang_fc1 -emit-fir -fopenmp -fopenmp-version=51 %s -o - | FileCheck %s --check-prefix=FIR
+!RUN: %if x86-registered-target %{ %flang_fc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fopenmp -fopenmp-version=51 %s -o - | FileCheck %s --check-prefix=LLVM %}
+!RUN: %if aarch64-registered-target %{ %flang_fc1 -triple aarch64-unknown-linux-gnu -emit-llvm -fopenmp -fopenmp-version=51 %s -o - | FileCheck %s --check-prefix=LLVM %}
+
+! OpenMP atomic compare-capture tracked through every stage of the compiler
+! (HLFIR, FIR/omp dialect, LLVM IR) for every operand type: integer, real,
+! logical and complex under ==, plus integer and real under min/max (<, >).
+! Equality lowers to a cmpxchg; min/max lowers to an atomicrmw.
+
+! ===========================================================================
+! integer ==  (postfix): cmpxchg + select(new value) + store into v
+! HLFIR-LABEL: func.func @_QPcc_integer(
+! HLFIR:         %[[X:.*]]:2 = hlfir.declare %{{.*}}Ex"
+! HLFIR:         omp.atomic.capture memory_order(relaxed) {
+! HLFIR:           omp.atomic.compare %[[X]]#0 : !fir.ref<i32> {
+! HLFIR:             arith.cmpi eq
+! HLFIR:           }
+! HLFIR:           omp.atomic.read %{{.*}} = %[[X]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! HLFIR:         }
+! FIR-LABEL: func.func @_QPcc_integer(
+! FIR:         omp.atomic.capture memory_order(relaxed) {
+! FIR:           omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! FIR:             arith.cmpi eq
+! FIR:           }
+! FIR:           omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! FIR:         }
+! LLVM-LABEL: define void @cc_integer_(
+! LLVM:         %[[RES:.*]] = cmpxchg ptr %{{.*}}, i32 %{{.*}}, i32 %[[D:.*]] monotonic monotonic
+! LLVM:         %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+! LLVM:         %[[OK:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+! LLVM:         %[[NEW:.*]] = select i1 %[[OK]], i32 %[[D]], i32 %[[OLD]]
+! LLVM:         store i32 %[[NEW]], ptr
+subroutine cc_integer(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture
+  if (x == e) x = d
+  v = x
+  !$omp end atomic
+end
+
+! ===========================================================================
+! real ==  (postfix): equality on floats uses the HandleFPNegZero cmpxchg path.
+! HLFIR-LABEL: func.func @_QPcc_real(
+! HLFIR:         omp.atomic.compare %{{.*}}#0 : !fir.ref<f32> {
+! HLFIR:           arith.cmpf oeq
+! HLFIR:         }
+! FIR-LABEL: func.func @_QPcc_real(
+! FIR:         omp.atomic.compare %{{.*}} : !fir.ref<f32> {
+! FIR:           arith.cmpf oeq
+! FIR:         }
+! LLVM-LABEL: define void @cc_real_(
+! LLVM:         cmpxchg ptr
+! LLVM:         store float %{{.*}}, ptr
+subroutine cc_real(x, e, d, v)
+  real :: x, e, d, v
+  !$omp atomic compare capture
+  if (x == e) x = d
+  v = x
+  !$omp end atomic
+end
+
+! ===========================================================================
+! logical ==  (postfix): the logical atom is converted to i32, so the omp op
+! operates on !fir.ref<i32> and lowers to an i32 cmpxchg.
+! HLFIR-LABEL: func.func @_QPcc_logical(
+! HLFIR:         fir.convert %{{.*}} : (!fir.logical<4>) -> i32
+! HLFIR:         omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! HLFIR:           arith.cmpi eq
+! HLFIR:         }
+! FIR-LABEL: func.func @_QPcc_logical(
+! FIR:         fir.convert %{{.*}} : (!fir.logical<4>) -> i32
+! FIR:         omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! LLVM-LABEL: define void @cc_logical_(
+! LLVM:         cmpxchg ptr %{{.*}}, i32
+! LLVM:         store i32 %{{.*}}, ptr
+subroutine cc_logical(x, e, d, v)
+  logical :: x, e, d, v
+  !$omp atomic compare capture
+  if (x .eqv. e) x = d
+  v = x
+  !$omp end atomic
+end
+
+! ===========================================================================
+! complex ==  (postfix): fir.cmpc region; lowers to an i64 (bitcast) cmpxchg
+! and the captured new value is a { float, float } struct.
+! HLFIR-LABEL: func.func @_QPcc_complex(
+! HLFIR:         omp.atomic.compare %{{.*}}#0 : !fir.ref<complex<f32>> {
+! HLFIR:           fir.cmpc "oeq"
+! HLFIR:         }
+! FIR-LABEL: func.func @_QPcc_complex(
+! FIR:         omp.atomic.compare %{{.*}} : !fir.ref<complex<f32>> {
+! FIR:           fir.cmpc "oeq"
+! FIR:         }
+! LLVM-LABEL: define void @cc_complex_(
+! LLVM:         cmpxchg ptr %{{.*}}, i64
+! LLVM:         store { float, float } %{{.*}}, ptr
+subroutine cc_complex(x, e, d, v)
+  complex :: x, e, d, v
+  !$omp atomic compare capture
+  if (x == e) x = d
+  v = x
+  !$omp end atomic
+end
+
+! ===========================================================================
+! integer min (x > e, postfix): atomicrmw min, v gets the new value smin(old,e).
+! HLFIR-LABEL: func.func @_QPcc_integer_min(
+! HLFIR:         omp.atomic.compare %{{.*}}#0 : !fir.ref<i32> {
+! HLFIR:           arith.cmpi sgt
+! HLFIR:         }
+! FIR-LABEL: func.func @_QPcc_integer_min(
+! FIR:         omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! FIR:           arith.cmpi sgt
+! FIR:         }
+! LLVM-LABEL: define void @cc_integer_min_(
+! LLVM:         %[[OLD:.*]] = atomicrmw min ptr %{{.*}}, i32 %[[E:.*]] monotonic
+! LLVM:         %[[NEW:.*]] = call i32 @llvm.smin.i32(i32 %[[OLD]], i32 %[[E]])
+! LLVM:         store i32 %[[NEW]], ptr
+subroutine cc_integer_min(x, e, v)
+  integer :: x, e, v
+  !$omp atomic compare capture
+  if (x > e) x = e
+  v = x
+  !$omp end atomic
+end
+
+! ===========================================================================
+! integer max (x < e, postfix): atomicrmw max, v gets the new value smax(old,e).
+! HLFIR-LABEL: func.func @_QPcc_integer_max(
+! HLFIR:         omp.atomic.compare %{{.*}}#0 : !fir.ref<i32> {
+! HLFIR:           arith.cmpi slt
+! HLFIR:         }
+! FIR-LABEL: func.func @_QPcc_integer_max(
+! FIR:         omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! FIR:           arith.cmpi slt
+! FIR:         }
+! LLVM-LABEL: define void @cc_integer_max_(
+! LLVM:         %[[OLD:.*]] = atomicrmw max ptr %{{.*}}, i32 %[[E:.*]] monotonic
+! LLVM:         %[[NEW:.*]] = call i32 @llvm.smax.i32(i32 %[[OLD]], i32 %[[E]])
+! LLVM:         store i32 %[[NEW]], ptr
+subroutine cc_integer_max(x, e, v)
+  integer :: x, e, v
+  !$omp atomic compare capture
+  if (x < e) x = e
+  v = x
+  !$omp end atomic
+end
+
+! ===========================================================================
+! real min (x > e, postfix): atomicrmw fmin, v gets minnum(old, e).
+! HLFIR-LABEL: func.func @_QPcc_real_min(
+! HLFIR:         omp.atomic.compare %{{.*}}#0 : !fir.ref<f32> {
+! HLFIR:           arith.cmpf ogt
+! HLFIR:         }
+! FIR-LABEL: func.func @_QPcc_real_min(
+! FIR:         omp.atomic.compare %{{.*}} : !fir.ref<f32> {
+! FIR:           arith.cmpf ogt
+! FIR:         }
+! LLVM-LABEL: define void @cc_real_min_(
+! LLVM:         %[[OLD:.*]] = atomicrmw fmin ptr %{{.*}}, float %[[E:.*]] monotonic
+! LLVM:         %[[NEW:.*]] = call float @llvm.minnum.f32(float %[[OLD]], float %[[E]])
+! LLVM:         store float %[[NEW]], ptr
+subroutine cc_real_min(x, e, v)
+  real :: x, e, v
+  !$omp atomic compare capture
+  if (x > e) x = e
+  v = x
+  !$omp end atomic
+end
+
+! ===========================================================================
+! real max (x < e, postfix): atomicrmw fmax, v gets maxnum(old, e).
+! HLFIR-LABEL: func.func @_QPcc_real_max(
+! HLFIR:         omp.atomic.compare %{{.*}}#0 : !fir.ref<f32> {
+! HLFIR:           arith.cmpf olt
+! HLFIR:         }
+! FIR-LABEL: func.func @_QPcc_real_max(
+! FIR:         omp.atomic.compare %{{.*}} : !fir.ref<f32> {
+! FIR:           arith.cmpf olt
+! FIR:         }
+! LLVM-LABEL: define void @cc_real_max_(
+! LLVM:         %[[OLD:.*]] = atomicrmw fmax ptr %{{.*}}, float %[[E:.*]] monotonic
+! LLVM:         %[[NEW:.*]] = call float @llvm.maxnum.f32(float %[[OLD]], float %[[E]])
+! LLVM:         store float %[[NEW]], ptr
+subroutine cc_real_max(x, e, v)
+  real :: x, e, v
+  !$omp atomic compare capture
+  if (x < e) x = e
+  v = x
+  !$omp end atomic
+end
+
+! The remaining cases exercise the different *capture-statement* forms allowed
+! for atomic compare-capture (all with an integer == atom).
+
+! ===========================================================================
+! Prefix form  {v = x; cond-update-stmt}:  the read precedes the update, so v
+! captures the OLD value of x - the read is emitted first and v is a direct
+! store of the extracted old value (no select).
+! HLFIR-LABEL: func.func @_QPcc_prefix(
+! HLFIR:         omp.atomic.capture memory_order(relaxed) {
+! HLFIR:           omp.atomic.read %{{.*}} = %[[X:.*]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! HLFIR:           omp.atomic.compare %[[X]]#0 : !fir.ref<i32> {
+! HLFIR:             arith.cmpi eq
+! HLFIR:           }
+! HLFIR:         }
+! FIR-LABEL: func.func @_QPcc_prefix(
+! FIR:         omp.atomic.capture memory_order(relaxed) {
+! FIR:           omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! FIR:           omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! FIR:         }
+! LLVM-LABEL: define void @cc_prefix_(
+! LLVM:         %[[RES:.*]] = cmpxchg ptr %{{.*}}, i32 %{{.*}}, i32 %{{.*}} monotonic monotonic
+! LLVM:         %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+! LLVM:         store i32 %[[OLD]], ptr
+subroutine cc_prefix(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture
+  v = x
+  if (x == e) x = d
+  !$omp end atomic
+end
+
+! ===========================================================================
+! Fail-only form  {if(x == e){x = d;} else {v = x;}}:  v is captured only when
+! the compare fails; the capture carries the fail_only attribute and lowers to
+! a conditional store of the old value.
+! HLFIR-LABEL: func.func @_QPcc_failonly(
+! HLFIR:         omp.atomic.capture memory_order(relaxed) {
+! HLFIR:           omp.atomic.compare %{{.*}}#0 : !fir.ref<i32> {
+! HLFIR:             arith.cmpi eq
+! HLFIR:           }
+! HLFIR:           omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! HLFIR:         } {fail_only}
+! FIR-LABEL: func.func @_QPcc_failonly(
+! FIR:         omp.atomic.capture memory_order(relaxed) {
+! FIR:         } {fail_only}
+! LLVM-LABEL: define void @cc_failonly_(
+! LLVM:         %[[RES:.*]] = cmpxchg ptr %{{.*}}, i32 %{{.*}}, i32 %{{.*}} monotonic monotonic
+! LLVM:         %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+! LLVM:         %[[OK:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+! LLVM:         br i1 %[[OK]], label %[[EXIT:.*]], label %[[CONT:.*]]
+! LLVM:         [[CONT]]:
+! LLVM:         store i32 %[[OLD]], ptr
+subroutine cc_failonly(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture
+  if (x == e) then
+    x = d
+  else
+    v = x
+  end if
+  !$omp end atomic
+end
+
+! ===========================================================================
+! Reversed comparison operand order  {if(e == x){x = d;} ; v = x}:  the front
+! end normalises 'e == x' to the same compare-capture as 'x == e', so this
+! accepts the alternate spelling and lowers identically (cmpxchg + select).
+! HLFIR-LABEL: func.func @_QPcc_eq_reversed(
+! HLFIR:         omp.atomic.compare %{{.*}}#0 : !fir.ref<i32> {
+! HLFIR:           arith.cmpi eq
+! HLFIR:         }
+! FIR-LABEL: func.func @_QPcc_eq_reversed(
+! LLVM-LABEL: define void @cc_eq_reversed_(
+! LLVM:         %[[RES:.*]] = cmpxchg ptr %{{.*}}, i32 %{{.*}}, i32 %[[D:.*]] monotonic monotonic
+! LLVM:         %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+! LLVM:         %[[OK:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+! LLVM:         %[[NEW:.*]] = select i1 %[[OK]], i32 %[[D]], i32 %[[OLD]]
+! LLVM:         store i32 %[[NEW]], ptr
+subroutine cc_eq_reversed(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture
+  if (e == x) x = d
+  v = x
+  !$omp end atomic
+end
+
+! ===========================================================================
+! The prefix and fail-only forms for the remaining operand types (real,
+! logical, complex), to confirm each type honours every capture form.
+
+! real, prefix: v gets the old value of x (float).
+! HLFIR-LABEL: func.func @_QPcc_real_prefix(
+! HLFIR:         omp.atomic.capture memory_order(relaxed) {
+! HLFIR:           omp.atomic.read %{{.*}} = %[[X:.*]]#0 : !fir.ref<f32>, !fir.ref<f32>, f32
+! HLFIR:           omp.atomic.compare %[[X]]#0 : !fir.ref<f32> {
+! HLFIR:         }
+! FIR-LABEL: func.func @_QPcc_real_prefix(
+! FIR:           omp.atomic.read %{{.*}} : !fir.ref<f32>, !fir.ref<f32>, f32
+! FIR:           omp.atomic.compare %{{.*}} : !fir.ref<f32> {
+! LLVM-LABEL: define void @cc_real_prefix_(
+! LLVM:         cmpxchg ptr
+! LLVM:         store float %{{.*}}, ptr
+subroutine cc_real_prefix(x, e, d, v)
+  real :: x, e, d, v
+  !$omp atomic compare capture
+  v = x
+  if (x == e) x = d
+  !$omp end atomic
+end
+
+! real, fail-only: conditional store of the old value (float).
+! HLFIR-LABEL: func.func @_QPcc_real_failonly(
+! HLFIR:         omp.atomic.capture memory_order(relaxed) {
+! HLFIR:           omp.atomic.compare %{{.*}}#0 : !fir.ref<f32> {
+! HLFIR:         } {fail_only}
+! FIR-LABEL: func.func @_QPcc_real_failonly(
+! FIR:         } {fail_only}
+! LLVM-LABEL: define void @cc_real_failonly_(
+! LLVM:         cmpxchg ptr
+! LLVM:         br i1 %{{.*}}, label %{{.*}}, label %{{.*}}
+! LLVM:         store float %{{.*}}, ptr
+subroutine cc_real_failonly(x, e, d, v)
+  real :: x, e, d, v
+  !$omp atomic compare capture
+  if (x == e) then
+    x = d
+  else
+    v = x
+  end if
+  !$omp end atomic
+end
+
+! logical, prefix: v gets the old value (via the i32 atom).
+! HLFIR-LABEL: func.func @_QPcc_logical_prefix(
+! HLFIR:         omp.atomic.capture memory_order(relaxed) {
+! HLFIR:           omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! HLFIR:           omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! FIR-LABEL: func.func @_QPcc_logical_prefix(
+! FIR:           omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! FIR:           omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! LLVM-LABEL: define void @cc_logical_prefix_(
+! LLVM:         %[[RES:.*]] = cmpxchg ptr %{{.*}}, i32
+! LLVM:         %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+subroutine cc_logical_prefix(x, e, d, v)
+  logical :: x, e, d, v
+  !$omp atomic compare capture
+  v = x
+  if (x .eqv. e) x = d
+  !$omp end atomic
+end
+
+! logical, fail-only: conditional store (via the i32 atom).
+! HLFIR-LABEL: func.func @_QPcc_logical_failonly(
+! HLFIR:         } {fail_only}
+! FIR-LABEL: func.func @_QPcc_logical_failonly(
+! FIR:         } {fail_only}
+! LLVM-LABEL: define void @cc_logical_failonly_(
+! LLVM:         %[[RES:.*]] = cmpxchg ptr %{{.*}}, i32
+! LLVM:         %[[OK:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+! LLVM:         br i1 %[[OK]], label %{{.*}}, label %{{.*}}
+subroutine cc_logical_failonly(x, e, d, v)
+  logical :: x, e, d, v
+  !$omp atomic compare capture
+  if (x .eqv. e) then
+    x = d
+  else
+    v = x
+  end if
+  !$omp end atomic
+end
+
+! complex, prefix: v gets the old complex value (struct), no select.
+! HLFIR-LABEL: func.func @_QPcc_complex_prefix(
+! HLFIR:         omp.atomic.capture memory_order(relaxed) {
+! HLFIR:           omp.atomic.read %{{.*}} = %[[X:.*]]#0 : !fir.ref<complex<f32>>, !fir.ref<complex<f32>>, complex<f32>
+! HLFIR:           omp.atomic.compare %[[X]]#0 : !fir.ref<complex<f32>> {
+! FIR-LABEL: func.func @_QPcc_complex_prefix(
+! FIR:           omp.atomic.read %{{.*}} : !fir.ref<complex<f32>>, !fir.ref<complex<f32>>, complex<f32>
+! LLVM-LABEL: define void @cc_complex_prefix_(
+! LLVM:         %[[RES:.*]] = cmpxchg ptr %{{.*}}, i64
+! LLVM:         store { float, float } %{{.*}}, ptr
+subroutine cc_complex_prefix(x, e, d, v)
+  complex :: x, e, d, v
+  !$omp atomic compare capture
+  v = x
+  if (x == e) x = d
+  !$omp end atomic
+end
+
+! complex, fail-only: conditional store of the old complex value.
+! HLFIR-LABEL: func.func @_QPcc_complex_failonly(
+! HLFIR:         omp.atomic.compare %{{.*}}#0 : !fir.ref<complex<f32>> {
+! HLFIR:         } {fail_only}
+! FIR-LABEL: func.func @_QPcc_complex_failonly(
+! FIR:         } {fail_only}
+! LLVM-LABEL: define void @cc_complex_failonly_(
+! LLVM:         %[[RES:.*]] = cmpxchg ptr %{{.*}}, i64
+! LLVM:         %[[OK:.*]] = extractvalue { i64, i1 } %[[RES]], 1
+! LLVM:         %[[FAILED:.*]] = xor i1 %[[OK]], true
+! LLVM:         br i1 %[[FAILED]], label %{{.*}}, label %{{.*}}
+! LLVM:         store { float, float } %{{.*}}, ptr
+subroutine cc_complex_failonly(x, e, d, v)
+  complex :: x, e, d, v
+  !$omp atomic compare capture
+  if (x == e) then
+    x = d
+  else
+    v = x
+  end if
+  !$omp end atomic
+end
+
+! ===========================================================================
+! weak clause (postfix): cmpxchg gets the 'weak' modifier.
+! HLFIR-LABEL: func.func @_QPcc_weak(
+! HLFIR:         omp.atomic.capture memory_order(relaxed) {
+! HLFIR:           omp.atomic.compare %{{.*}}#0 : !fir.ref<i32> {
+! HLFIR:             arith.cmpi eq
+! HLFIR:           } {weak}
+! HLFIR:           omp.atomic.read %{{.*}} = %{{.*}}#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! HLFIR:         }
+! FIR-LABEL: func.func @_QPcc_weak(
+! FIR:         omp.atomic.capture memory_order(relaxed) {
+! FIR:           omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! FIR:             arith.cmpi eq
+! FIR:           } {weak}
+! FIR:           omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! FIR:         }
+! LLVM-LABEL: define void @cc_weak_(
+! LLVM:         %[[RES:.*]] = cmpxchg weak ptr %{{.*}}, i32 %{{.*}}, i32 %[[D:.*]] monotonic monotonic
+! LLVM:         %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+! LLVM:         %[[OK:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+! LLVM:         %[[NEW:.*]] = select i1 %[[OK]], i32 %[[D]], i32 %[[OLD]]
+! LLVM:         store i32 %[[NEW]], ptr
+subroutine cc_weak(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture weak
+  if (x == e) x = d
+  v = x
+  !$omp end atomic
+end
diff --git a/flang/test/Lower/OpenMP/Todo/atomic-compare-capture-result.f90 b/flang/test/Lower/OpenMP/Todo/atomic-compare-capture-result.f90
new file mode 100644
index 0000000000000..ce6f0a3d6454b
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/atomic-compare-capture-result.f90
@@ -0,0 +1,20 @@
+! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -fopenmp-version=51 -o - %s 2>&1 | FileCheck %s
+
+! The comparison-result form of atomic compare captures the boolean outcome of
+! the comparison into `r`:
+!   r = x == e
+!   if (r) x = d
+! This is accepted by semantics, but the atomic analysis does not record the
+! assignment to `r`, so lowering would silently drop it and never write `r`.
+! Ensure it is diagnosed as "not yet implemented" instead of producing code that
+! leaves `r` unwritten.
+
+! CHECK: not yet implemented: atomic compare capturing the comparison result
+subroutine f(x, e, d, r)
+  integer :: x, e, d
+  logical :: r
+  !$omp atomic compare
+  r = x == e
+  if (r) 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 27c7b1fb1a432..7165bdb2fc122 100644
--- a/flang/test/Lower/OpenMP/Todo/atomic-compare-fail.f90
+++ b/flang/test/Lower/OpenMP/Todo/atomic-compare-fail.f90
@@ -5,20 +5,12 @@ program p
   integer :: x
   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
diff --git a/flang/test/Lower/OpenMP/atomic-compare-capture.f90 b/flang/test/Lower/OpenMP/atomic-compare-capture.f90
new file mode 100644
index 0000000000000..93fbf2d4bd034
--- /dev/null
+++ b/flang/test/Lower/OpenMP/atomic-compare-capture.f90
@@ -0,0 +1,356 @@
+! This test checks lowering of atomic compare capture constructs to HLFIR.
+! 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
+
+! ---------------------------------------------------------------------------
+! integer == postfix: { if (x==e) x=d; v=x }
+! CHECK-LABEL: func.func @_QPcc_int_postfix(
+! 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-SAME:    %[[V:.*]]: !fir.ref<i32> {fir.bindc_name = "v"})
+! CHECK:         %[[D_DECL:.*]]:2 = hlfir.declare %[[D]] {{.*}}
+! CHECK:         %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK:         %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
+! CHECK:         %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK:         %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK:         %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<i32> {
+! CHECK:           ^bb0(%[[XVAL:.*]]: i32):
+! CHECK:             %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %[[EVAL]] : i32
+! CHECK:             %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : i32
+! CHECK:             omp.yield(%[[SEL]] : i32)
+! CHECK:           }
+! CHECK:           omp.atomic.read %[[V_DECL]]#0 = %[[X_DECL]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:         }
+subroutine cc_int_postfix(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture
+  if (x == e) x = d
+  v = x
+  !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! integer == prefix: { v=x; if (x==e) x=d }
+! CHECK-LABEL: func.func @_QPcc_int_prefix(
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.read %{{.*}}#0 = %[[X:.*]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:           omp.atomic.compare %[[X]]#0 : !fir.ref<i32> {
+! CHECK:           ^bb0(%[[XVAL:.*]]: i32):
+! CHECK:             arith.cmpi eq, %[[XVAL]], %{{.*}} : i32
+! CHECK:             omp.yield
+! CHECK:           }
+! CHECK:         }
+subroutine cc_int_prefix(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture
+  v = x
+  if (x == e) x = d
+  !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! integer == fail-only: { if (x==e) x=d; else v=x }
+! CHECK-LABEL: func.func @_QPcc_int_failonly(
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %{{.*}}#0 : !fir.ref<i32> {
+! CHECK:           ^bb0(%[[XVAL:.*]]: i32):
+! CHECK:             arith.cmpi eq, %[[XVAL]], %{{.*}} : i32
+! CHECK:             omp.yield
+! CHECK:           }
+! CHECK:           omp.atomic.read %{{.*}}#0 = %{{.*}}#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:         } {fail_only}
+subroutine cc_int_failonly(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture
+  if (x == e) then
+    x = d
+  else
+    v = x
+  end if
+  !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! integer == reversed (e==x): same lowering as x==e
+! CHECK-LABEL: func.func @_QPcc_int_eq_reversed(
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %{{.*}}#0 : !fir.ref<i32> {
+! CHECK:             arith.cmpi eq
+! CHECK:           }
+! CHECK:           omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:         }
+subroutine cc_int_eq_reversed(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture
+  if (e == x) x = d
+  v = x
+  !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! real == postfix
+! CHECK-LABEL: func.func @_QPcc_real_postfix(
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %{{.*}}#0 : !fir.ref<f32> {
+! CHECK:             arith.cmpf oeq
+! CHECK:           }
+! CHECK:           omp.atomic.read %{{.*}} : !fir.ref<f32>, !fir.ref<f32>, f32
+! CHECK:         }
+subroutine cc_real_postfix(x, e, d, v)
+  real :: x, e, d, v
+  !$omp atomic compare capture
+  if (x == e) x = d
+  v = x
+  !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! real == prefix
+! CHECK-LABEL: func.func @_QPcc_real_prefix(
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.read %{{.*}} = %[[X:.*]]#0 : !fir.ref<f32>, !fir.ref<f32>, f32
+! CHECK:           omp.atomic.compare %[[X]]#0 : !fir.ref<f32> {
+! CHECK:             arith.cmpf oeq
+! CHECK:           }
+! CHECK:         }
+subroutine cc_real_prefix(x, e, d, v)
+  real :: x, e, d, v
+  !$omp atomic compare capture
+  v = x
+  if (x == e) x = d
+  !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! real == fail-only
+! CHECK-LABEL: func.func @_QPcc_real_failonly(
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %{{.*}}#0 : !fir.ref<f32> {
+! CHECK:             arith.cmpf oeq
+! CHECK:           }
+! CHECK:           omp.atomic.read %{{.*}} : !fir.ref<f32>, !fir.ref<f32>, f32
+! CHECK:         } {fail_only}
+subroutine cc_real_failonly(x, e, d, v)
+  real :: x, e, d, v
+  !$omp atomic compare capture
+  if (x == e) then
+    x = d
+  else
+    v = x
+  end if
+  !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! logical .eqv. postfix
+! CHECK-LABEL: func.func @_QPcc_logical_postfix(
+! CHECK:         fir.convert %{{.*}} : (!fir.logical<4>) -> i32
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! CHECK:             arith.cmpi eq
+! CHECK:           }
+! CHECK:           omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:         }
+subroutine cc_logical_postfix(x, e, d, v)
+  logical :: x, e, d, v
+  !$omp atomic compare capture
+  if (x .eqv. e) x = d
+  v = x
+  !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! logical .eqv. prefix
+! CHECK-LABEL: func.func @_QPcc_logical_prefix(
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:           omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! CHECK:             arith.cmpi eq
+! CHECK:           }
+! CHECK:         }
+subroutine cc_logical_prefix(x, e, d, v)
+  logical :: x, e, d, v
+  !$omp atomic compare capture
+  v = x
+  if (x .eqv. e) x = d
+  !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! logical .eqv. fail-only
+! CHECK-LABEL: func.func @_QPcc_logical_failonly(
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! CHECK:             arith.cmpi eq
+! CHECK:           }
+! CHECK:           omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:         } {fail_only}
+subroutine cc_logical_failonly(x, e, d, v)
+  logical :: x, e, d, v
+  !$omp atomic compare capture
+  if (x .eqv. e) then
+    x = d
+  else
+    v = x
+  end if
+  !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! complex == postfix
+! CHECK-LABEL: func.func @_QPcc_complex_postfix(
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %{{.*}}#0 : !fir.ref<complex<f32>> {
+! CHECK:             fir.cmpc "oeq"
+! CHECK:           }
+! CHECK:           omp.atomic.read %{{.*}} : !fir.ref<complex<f32>>, !fir.ref<complex<f32>>, complex<f32>
+! CHECK:         }
+subroutine cc_complex_postfix(x, e, d, v)
+  complex :: x, e, d, v
+  !$omp atomic compare capture
+  if (x == e) x = d
+  v = x
+  !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! complex == prefix
+! CHECK-LABEL: func.func @_QPcc_complex_prefix(
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.read %{{.*}} = %[[X:.*]]#0 : !fir.ref<complex<f32>>, !fir.ref<complex<f32>>, complex<f32>
+! CHECK:           omp.atomic.compare %[[X]]#0 : !fir.ref<complex<f32>> {
+! CHECK:             fir.cmpc "oeq"
+! CHECK:           }
+! CHECK:         }
+subroutine cc_complex_prefix(x, e, d, v)
+  complex :: x, e, d, v
+  !$omp atomic compare capture
+  v = x
+  if (x == e) x = d
+  !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! complex == fail-only
+! CHECK-LABEL: func.func @_QPcc_complex_failonly(
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %{{.*}}#0 : !fir.ref<complex<f32>> {
+! CHECK:             fir.cmpc "oeq"
+! CHECK:           }
+! CHECK:           omp.atomic.read %{{.*}} : !fir.ref<complex<f32>>, !fir.ref<complex<f32>>, complex<f32>
+! CHECK:         } {fail_only}
+subroutine cc_complex_failonly(x, e, d, v)
+  complex :: x, e, d, v
+  !$omp atomic compare capture
+  if (x == e) then
+    x = d
+  else
+    v = x
+  end if
+  !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! integer min (x > e) postfix
+! CHECK-LABEL: func.func @_QPcc_int_min(
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %{{.*}}#0 : !fir.ref<i32> {
+! CHECK:             arith.cmpi sgt
+! CHECK:           }
+! CHECK:           omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:         }
+subroutine cc_int_min(x, e, v)
+  integer :: x, e, v
+  !$omp atomic compare capture
+  if (x > e) x = e
+  v = x
+  !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! integer max (x < e) postfix
+! CHECK-LABEL: func.func @_QPcc_int_max(
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %{{.*}}#0 : !fir.ref<i32> {
+! CHECK:             arith.cmpi slt
+! CHECK:           }
+! CHECK:           omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:         }
+subroutine cc_int_max(x, e, v)
+  integer :: x, e, v
+  !$omp atomic compare capture
+  if (x < e) x = e
+  v = x
+  !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! real min (x > e) postfix
+! CHECK-LABEL: func.func @_QPcc_real_min(
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %{{.*}}#0 : !fir.ref<f32> {
+! CHECK:             arith.cmpf ogt
+! CHECK:           }
+! CHECK:           omp.atomic.read %{{.*}} : !fir.ref<f32>, !fir.ref<f32>, f32
+! CHECK:         }
+subroutine cc_real_min(x, e, v)
+  real :: x, e, v
+  !$omp atomic compare capture
+  if (x > e) x = e
+  v = x
+  !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! real max (x < e) postfix
+! CHECK-LABEL: func.func @_QPcc_real_max(
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %{{.*}}#0 : !fir.ref<f32> {
+! CHECK:             arith.cmpf olt
+! CHECK:           }
+! CHECK:           omp.atomic.read %{{.*}} : !fir.ref<f32>, !fir.ref<f32>, f32
+! CHECK:         }
+subroutine cc_real_max(x, e, v)
+  real :: x, e, v
+  !$omp atomic compare capture
+  if (x < e) x = e
+  v = x
+  !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! weak clause (prefix form)
+! CHECK-LABEL: func.func @_QPcc_weak_prefix(
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.read %{{.*}}#0 = %[[X:.*]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:           omp.atomic.compare %[[X]]#0 : !fir.ref<i32> {
+! CHECK:             arith.cmpi eq
+! CHECK:           } {weak}
+! CHECK:         }
+subroutine cc_weak_prefix(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture weak
+  v = x
+  if (x == e) x = d
+  !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! weak clause (postfix form)
+! CHECK-LABEL: func.func @_QPcc_weak_postfix(
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %{{.*}}#0 : !fir.ref<i32> {
+! CHECK:             arith.cmpi eq
+! CHECK:           } {weak}
+! CHECK:           omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:         }
+subroutine cc_weak_postfix(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture weak
+  if (x == e) x = d
+  v = x
+  !$omp end atomic
+end subroutine
diff --git a/flang/test/Lower/OpenMP/atomic-compare.f90 b/flang/test/Lower/OpenMP/atomic-compare.f90
index ccc44a370be79..83e6f81ca00e3 100644
--- a/flang/test/Lower/OpenMP/atomic-compare.f90
+++ b/flang/test/Lower/OpenMP/atomic-compare.f90
@@ -218,6 +218,101 @@ subroutine atomic_compare_capture_int_gt(x, e, d, v)
   !$omp end atomic
 end
 
+! Min/max (postfix) compare-capture: update first, capture the new value.
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_min_postfix(
+! CHECK-SAME:    %[[X:.*]]: !fir.ref<i32> {fir.bindc_name = "x"},
+! CHECK-SAME:    %[[E:.*]]: !fir.ref<i32> {fir.bindc_name = "e"},
+! CHECK-SAME:    %[[V:.*]]: !fir.ref<i32> {fir.bindc_name = "v"})
+! CHECK:         %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK:         %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
+! CHECK:         %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK:         %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK:         %[[DVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<i32> {
+! CHECK:           ^bb0(%[[XVAL:.*]]: i32):
+! CHECK:             %[[CMP:.*]] = arith.cmpi sgt, %[[XVAL]], %[[EVAL]] : i32
+! CHECK:             %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : i32
+! CHECK:             omp.yield(%[[SEL]] : i32)
+! CHECK:           }
+! CHECK:           omp.atomic.read %[[V_DECL]]#0 = %[[X_DECL]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:         }
+subroutine atomic_compare_capture_min_postfix(x, e, v)
+  integer :: x, e, v
+  !$omp atomic compare capture
+    if (x > e) x = e
+    v = x
+  !$omp end atomic
+end
+
+! Min/max fail-only compare-capture: v is captured in the else branch (when the
+! comparison, and hence the update, does not happen).
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_min_failonly(
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %[[X_DECL:.*]]#0 : !fir.ref<i32> {
+! CHECK:           ^bb0(%[[XVAL:.*]]: i32):
+! CHECK:             %[[CMP:.*]] = arith.cmpi sgt, %[[XVAL]], %{{.*}} : i32
+! CHECK:             %[[SEL:.*]] = arith.select %[[CMP]], %{{.*}}, %[[XVAL]] : i32
+! CHECK:             omp.yield(%[[SEL]] : i32)
+! CHECK:           }
+! CHECK:           omp.atomic.read %{{.*}}#0 = %[[X_DECL]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:         } {fail_only}
+subroutine atomic_compare_capture_min_failonly(x, e, v)
+  integer :: x, e, v
+  !$omp atomic compare capture
+    if (x > e) then
+      x = e
+    else
+      v = x
+    end if
+  !$omp end atomic
+end
+
+! REAL min (postfix): if (x > e) x = e ; v = x -> compare region uses arith.cmpf.
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_real_min(
+! CHECK:         %[[E_DECL:.*]]:2 = hlfir.declare %{{.*}}Ee"
+! CHECK:         %[[V_DECL:.*]]:2 = hlfir.declare %{{.*}}Ev"
+! CHECK:         %[[X_DECL:.*]]:2 = hlfir.declare %{{.*}}Ex"
+! CHECK:         %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<f32>
+! CHECK:         %[[DVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<f32>
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<f32> {
+! CHECK:           ^bb0(%[[XVAL:.*]]: f32):
+! CHECK:             %[[CMP:.*]] = arith.cmpf ogt, %[[XVAL]], %[[EVAL]] fastmath<contract> : f32
+! CHECK:             %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : f32
+! CHECK:             omp.yield(%[[SEL]] : f32)
+! CHECK:           }
+! CHECK:           omp.atomic.read %[[V_DECL]]#0 = %[[X_DECL]]#0 : !fir.ref<f32>, !fir.ref<f32>, f32
+! CHECK:         }
+subroutine atomic_compare_capture_real_min(x, e, v)
+  real :: x, e, v
+  !$omp atomic compare capture
+    if (x > e) x = e
+    v = x
+  !$omp end atomic
+end
+
+! REAL max (postfix): if (x < e) x = e ; v = x.
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_real_max(
+! CHECK:         %[[E_DECL:.*]]:2 = hlfir.declare %{{.*}}Ee"
+! CHECK:         %[[X_DECL:.*]]:2 = hlfir.declare %{{.*}}Ex"
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<f32> {
+! CHECK:           ^bb0(%[[XVAL:.*]]: f32):
+! CHECK:             %[[CMP:.*]] = arith.cmpf olt, %[[XVAL]], %{{.*}} fastmath<contract> : f32
+! CHECK:             %[[SEL:.*]] = arith.select %[[CMP]], %{{.*}}, %[[XVAL]] : f32
+! CHECK:             omp.yield(%[[SEL]] : f32)
+! CHECK:           }
+! CHECK:           omp.atomic.read %{{.*}}#0 = %[[X_DECL]]#0 : !fir.ref<f32>, !fir.ref<f32>, f32
+! CHECK:         }
+subroutine atomic_compare_capture_real_max(x, e, v)
+  real :: x, e, v
+  !$omp atomic compare capture
+    if (x < e) x = e
+    v = x
+  !$omp end atomic
+end
+
 ! CHECK-LABEL: func.func @_QPatomic_compare_capture_postfix(
 ! CHECK-SAME:    %[[X:.*]]: !fir.ref<i32> {fir.bindc_name = "x"},
 ! CHECK-SAME:    %[[E:.*]]: !fir.ref<i32> {fir.bindc_name = "e"},
@@ -305,6 +400,27 @@ subroutine atomic_compare_capture_real(x, e, d, v)
   !$omp end atomic
 end
 
+! Complex compare-capture (postfix form): the compare region decomposes into
+! per-field comparisons combined with fir.cmpc/arith and the desired value is
+! selected as a whole complex value.
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_complex(
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %[[X_DECL:.*]]#0 : !fir.ref<complex<f32>> {
+! CHECK:           ^bb0(%[[XVAL:.*]]: complex<f32>):
+! CHECK:             %[[CMP:.*]] = fir.cmpc "oeq", %[[XVAL]], %{{.*}} : complex<f32>
+! CHECK:             %[[SEL:.*]] = arith.select %[[CMP]], %{{.*}}, %[[XVAL]] : complex<f32>
+! CHECK:             omp.yield(%[[SEL]] : complex<f32>)
+! CHECK:           }
+! CHECK:           omp.atomic.read %{{.*}}#0 = %[[X_DECL]]#0 : !fir.ref<complex<f32>>, !fir.ref<complex<f32>>, complex<f32>
+! CHECK:         }
+subroutine atomic_compare_capture_complex(x, e, d, v)
+  complex :: x, e, d, v
+  !$omp atomic compare capture
+    if (x == e) x = d
+    v = x
+  !$omp end atomic
+end
+
 ! Compare-capture with an explicit seq_cst memory order (prefix read form).
 ! CHECK-LABEL: func.func @_QPatomic_compare_capture_seq_cst(
 ! CHECK:         omp.atomic.capture memory_order(seq_cst) {
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 8e49571cd2461..060e563ecefa8 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -5370,6 +5370,99 @@ convertFCmpPredicateToAtomicCompareOp(LLVM::FCmpPredicate predicate) {
   }
 }
 
+/// Result of matching the decomposed complex equality pattern inside an atomic
+/// compare region.
+struct ComplexComparePattern {
+  bool isComplex = false;
+  bool isNE = false;         // `or` of the field compares => NE (unsupported).
+  mlir::Value eAggregate;    // The complex expected value (`e`).
+  bool isXBinopExpr = false; // True if x is the first fcmp operand.
+};
+
+/// Detect a decomposed complex equality comparison in an atomic compare region:
+///   %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   (llvm.or would be NE)
+/// It is recognised by an and/or whose operands are both fcmps operating on
+/// extractvalues, one chain rooted at the block argument (x) and the other at
+/// the expected complex value (e).
+static ComplexComparePattern detectComplexCompareEq(Block &block) {
+  ComplexComparePattern result;
+  auto traceToAggregate = [](mlir::Value v) -> mlir::Value {
+    if (auto extractOp = v.getDefiningOp<LLVM::ExtractValueOp>())
+      return extractOp.getContainer();
+    return nullptr;
+  };
+  for (Operation &op : block.getOperations()) {
+    if (!isa<LLVM::AndOp, LLVM::OrOp>(op))
+      continue;
+    auto lhsFcmp = op.getOperand(0).getDefiningOp<LLVM::FCmpOp>();
+    auto rhsFcmp = op.getOperand(1).getDefiningOp<LLVM::FCmpOp>();
+    if (!lhsFcmp || !rhsFcmp)
+      continue;
+    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;
+    result.isComplex = true;
+    result.isNE = isa<LLVM::OrOp>(op);
+    result.eAggregate = eAggregate;
+    result.isXBinopExpr = lhsXIsOp0;
+    break;
+  }
+  return result;
+}
+
+/// Emit a bitcast-to-integer `cmpxchg` for a complex (struct-typed) atomic
+/// compare. The expected/desired complex values are spilled to allocas and
+/// reloaded as an integer of the same width, then compared and swapped. Returns
+/// the cmpxchg (result type `{iN, i1}`); `maxAlign` receives the alignment used
+/// so callers can reinterpret the captured old value through memory.
+static llvm::AtomicCmpXchgInst *
+emitComplexAtomicCmpXchg(llvm::IRBuilderBase &builder, llvm::Value *llvmX,
+                         llvm::Type *complexTy, llvm::Value *eVal,
+                         llvm::Value *dVal, llvm::AtomicOrdering atomicOrdering,
+                         bool isWeak, llvm::Align &maxAlign) {
+  const llvm::DataLayout &DL =
+      builder.GetInsertBlock()->getModule()->getDataLayout();
+  unsigned totalBits = DL.getTypeStoreSizeInBits(complexTy).getFixedValue();
+  llvm::IntegerType *intTy =
+      llvm::IntegerType::get(builder.getContext(), totalBits);
+  llvm::Align complexAlign = DL.getABITypeAlign(complexTy);
+  llvm::Align intAlign = DL.getABITypeAlign(intTy);
+  maxAlign = std::max(complexAlign, intAlign);
+
+  llvm::AllocaInst *eAlloca =
+      builder.CreateAlloca(complexTy, nullptr, "cmplx.e");
+  eAlloca->setAlignment(maxAlign);
+  llvm::AllocaInst *dAlloca =
+      builder.CreateAlloca(complexTy, 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);
+  auto *cmpXchg = builder.CreateAtomicCmpXchg(llvmX, eInt, dInt, maxAlign,
+                                              atomicOrdering, failOrdering);
+  cmpXchg->setWeak(isWeak);
+  return cmpXchg;
+}
+
 /// Holds the extracted comparison pattern information from an atomic compare
 /// region.
 struct AtomicComparePatternInfo {
@@ -5379,7 +5472,6 @@ struct AtomicComparePatternInfo {
   bool isXBinopExpr = false;
   bool isSigned = false;
 };
-
 /// Extract comparison predicate, expected value (e), desired value (d), and
 /// related flags from an atomic compare region block by scanning for
 /// icmp/fcmp/select/min/max operations.
@@ -5387,6 +5479,27 @@ static LogicalResult extractAtomicComparePattern(
     Block &block,
     llvm::function_ref<llvm::Value *(mlir::Value)> materializeValue,
     omp::AtomicCompareOp atomicCompareOp, AtomicComparePatternInfo &info) {
+  // Complex equality is a decomposed per-field pattern (extractvalue + fcmp +
+  // and) rather than a single scalar compare. Detect it first so the scalar
+  // icmp/fcmp handling below does not mistake a real/imaginary field for the
+  // whole expected value.
+  if (ComplexComparePattern cplx = detectComplexCompareEq(block);
+      cplx.isComplex) {
+    if (cplx.isNE)
+      return atomicCompareOp.emitError(
+          "unsupported comparison predicate (NE) for complex atomic compare");
+    info.compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
+    info.isXBinopExpr = cplx.isXBinopExpr;
+    info.eVal = materializeValue(cplx.eAggregate);
+    for (Operation &op : block.getOperations()) {
+      if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
+        info.dVal = materializeValue(selectOp.getTrueValue());
+        break;
+      }
+    }
+    return success();
+  }
+
   for (Operation &op : block.getOperations()) {
     // Pre-filter: skip icmps that don't involve the block argument
     // (e.g., truthiness extractions from logical-to-integer conversion).
@@ -5573,22 +5686,101 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
     bool isPostfixCapture = !isReadFirst;
     bool isFailOnly = atomicCaptureOp.getFailOnly();
 
+    // Complex equality capture: x is struct-typed, which the OMPIRBuilder
+    // cannot handle, so emit a bitcast-to-integer cmpxchg (as in the
+    // non-capture complex path) and reconstruct the captured value from its
+    // result through memory. Complex only supports the == comparison.
+    if (llvmXElementType->isStructTy()) {
+      llvm::Align maxAlign;
+      llvm::AtomicCmpXchgInst *cmpXchg = emitComplexAtomicCmpXchg(
+          builder, llvmX, llvmXElementType, eVal, dVal, atomicOrdering,
+          atomicCompareOp.getWeak(), maxAlign);
+
+      llvm::Value *oldInt = builder.CreateExtractValue(cmpXchg, 0);
+      llvm::Value *cmpOk = builder.CreateExtractValue(cmpXchg, 1);
+
+      // Reinterpret the old integer value as the complex struct via memory.
+      llvm::AllocaInst *oldAlloca =
+          builder.CreateAlloca(llvmXElementType, nullptr, "cmplx.old");
+      oldAlloca->setAlignment(maxAlign);
+      builder.CreateAlignedStore(oldInt, oldAlloca, maxAlign);
+      llvm::Value *oldComplex = builder.CreateAlignedLoad(
+          llvmXElementType, oldAlloca, maxAlign, "cmplx.old.val");
+
+      if (isFailOnly) {
+        // v is written only when the compare fails (cmpOk == false).
+        llvm::Value *cmpFailed = builder.CreateNot(cmpOk);
+        llvm::BasicBlock *curBB = builder.GetInsertBlock();
+        llvm::Function *fn = curBB->getParent();
+        llvm::BasicBlock *contBB = llvm::BasicBlock::Create(
+            builder.getContext(), "omp.atomic.cont", fn);
+        llvm::BasicBlock *exitBB = llvm::BasicBlock::Create(
+            builder.getContext(), "omp.atomic.exit", fn);
+        builder.CreateCondBr(cmpFailed, contBB, exitBB);
+        builder.SetInsertPoint(contBB);
+        builder.CreateStore(oldComplex, llvmAtomicV.Var,
+                            llvmAtomicV.IsVolatile);
+        builder.CreateBr(exitBB);
+        builder.SetInsertPoint(exitBB);
+      } else if (isPostfixCapture) {
+        // v gets the new value of x: d on success, old x otherwise.
+        llvm::Value *newComplex = builder.CreateSelect(cmpOk, dVal, oldComplex);
+        builder.CreateStore(newComplex, llvmAtomicV.Var,
+                            llvmAtomicV.IsVolatile);
+      } else {
+        // Prefix: v gets the old value of x.
+        builder.CreateStore(oldComplex, llvmAtomicV.Var,
+                            llvmAtomicV.IsVolatile);
+      }
+
+      // Emit flush after atomic compare if needed (release/acq_rel/seq_cst).
+      if (atomicOrdering == llvm::AtomicOrdering::Release ||
+          atomicOrdering == llvm::AtomicOrdering::AcquireRelease ||
+          atomicOrdering == llvm::AtomicOrdering::SequentiallyConsistent) {
+        llvm::OpenMPIRBuilder::LocationDescription flushLoc(builder);
+        ompBuilder->createFlush(flushLoc);
+      }
+      return success();
+    }
+
+    // Min/max (<, >) comparisons lower to an atomicrmw. The OMPIRBuilder has no
+    // notion of a failed compare for an atomicrmw, so the fail-only capture
+    // form (v written only when the compare fails) has no valid mapping and is
+    // Min/max (<, >) comparisons lower to an atomicrmw. The OMPIRBuilder has no
+    // notion of a failed compare for an atomicrmw (it asserts on IsFailOnly),
+    // so for min/max the fail-only capture is reconstructed manually below.
+    bool isMinMax = compareOp != llvm::omp::OMPAtomicCompareOp::EQ;
+
     llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicVForCall = llvmAtomicV;
-    // Postfix: bypass V in OMPIRBuilder, handled manually below.
-    if (isPostfixCapture && !isFailOnly)
+    // Capture into V is reconstructed manually below for:
+    //   * postfix capture (v gets the new value of x): equality from the
+    //     cmpxchg result, min/max from the atomicrmw result;
+    //   * min/max fail-only capture (the OMPIRBuilder cannot express it).
+    // Bypass V in the OMPIRBuilder for those cases so it does not also emit its
+    // own (for min/max, incorrect or unsupported) capture store.
+    bool minMaxManualCapture = isMinMax && (isPostfixCapture || isFailOnly);
+    bool eqPostfixManualCapture = !isMinMax && isPostfixCapture && !isFailOnly;
+    if (minMaxManualCapture || eqPostfixManualCapture)
       llvmAtomicVForCall = {nullptr, nullptr, false, false};
 
-    // Prefix: IsPostfixUpdate=true → direct store of old value.
-    // Fail-only: IsPostfixUpdate=false → conditional store on failure.
-    bool isPostfixUpdate = !isFailOnly;
+    // The OMPIRBuilder only understands IsFailOnly for the equality (cmpxchg)
+    // path; for min/max it would assert. Min/max fail-only is handled here.
+    bool builderFailOnly = isFailOnly && !isMinMax;
+
+    // IsPostfixUpdate selects which value the OMPIRBuilder captures into V:
+    //   * min/max prefix and equality prefix: a direct store of the old value
+    //     (IsPostfixUpdate=true).
+    //   * equality fail-only: a conditional store (IsPostfixUpdate=false).
+    // Manually-reconstructed captures bypass V above.
+    bool isPostfixUpdate = !builderFailOnly;
 
     bool isWeak = atomicCompareOp.getWeak();
     bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
     llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
-        ompBuilder->createAtomicCompare(ompLoc, llvmAtomicX, llvmAtomicVForCall,
-                                        llvmAtomicR, eVal, dVal, atomicOrdering,
-                                        compareOp, isXBinopExpr,
-                                        isPostfixUpdate, isFailOnly, isWeak);
+        ompBuilder->createAtomicCompare(
+            ompLoc, llvmAtomicX, llvmAtomicVForCall, llvmAtomicR, eVal, dVal,
+            atomicOrdering, compareOp, isXBinopExpr, isPostfixUpdate,
+            builderFailOnly, isWeak);
     ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
 
     if (failed(handleError(afterIP, *atomicCaptureOp)))
@@ -5596,8 +5788,99 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
 
     builder.restoreIP(*afterIP);
 
-    // Postfix: v = select(success, D, old) — captures new value of x.
-    if (isPostfixCapture && !isFailOnly) {
+    // Min/max capture is reconstructed from the atomicrmw the OMPIRBuilder
+    // emits (its result is the old value of x). V was bypassed above.
+    //   * postfix: v gets the new value min/max(old, e);
+    //   * fail-only: v gets the old value, but only when the compare failed
+    //     (i.e. the atomicrmw did not change x).
+    // (Prefix min/max captures the old value directly through V, so nothing
+    // extra is needed there.)
+    if (isMinMax && (isPostfixCapture || isFailOnly)) {
+      llvm::BasicBlock *curBB = builder.GetInsertBlock();
+      llvm::AtomicRMWInst *rmw = nullptr;
+      for (auto &inst : llvm::reverse(*curBB)) {
+        if (auto *r = dyn_cast<llvm::AtomicRMWInst>(&inst)) {
+          rmw = r;
+          break;
+        }
+      }
+      assert(rmw && "expected atomicrmw for min/max compare capture");
+      llvm::Value *oldVal = rmw;
+      llvm::Value *rhs = rmw->getValOperand();
+
+      if (isFailOnly) {
+        // The compare "failed" (the else branch runs) exactly when the
+        // atomicrmw did not change x. Recompute the original update condition
+        // on the old value and negate it. v is stored only in that case.
+        llvm::CmpInst::Predicate updatePred;
+        switch (rmw->getOperation()) {
+        case llvm::AtomicRMWInst::Min:
+          updatePred = llvm::CmpInst::ICMP_SGT;
+          break;
+        case llvm::AtomicRMWInst::Max:
+          updatePred = llvm::CmpInst::ICMP_SLT;
+          break;
+        case llvm::AtomicRMWInst::UMin:
+          updatePred = llvm::CmpInst::ICMP_UGT;
+          break;
+        case llvm::AtomicRMWInst::UMax:
+          updatePred = llvm::CmpInst::ICMP_ULT;
+          break;
+        case llvm::AtomicRMWInst::FMin:
+          updatePred = llvm::CmpInst::FCMP_OGT;
+          break;
+        case llvm::AtomicRMWInst::FMax:
+          updatePred = llvm::CmpInst::FCMP_OLT;
+          break;
+        default:
+          llvm_unreachable(
+              "unexpected atomicrmw op for min/max compare capture");
+        }
+        llvm::Value *updated = builder.CreateCmp(updatePred, oldVal, rhs);
+        llvm::Value *failed = builder.CreateNot(updated);
+        llvm::Function *fn = curBB->getParent();
+        llvm::BasicBlock *contBB = llvm::BasicBlock::Create(
+            builder.getContext(), "omp.atomic.cont", fn);
+        llvm::BasicBlock *exitBB = llvm::BasicBlock::Create(
+            builder.getContext(), "omp.atomic.exit", fn);
+        builder.CreateCondBr(failed, contBB, exitBB);
+        builder.SetInsertPoint(contBB);
+        builder.CreateStore(oldVal, llvmAtomicV.Var, llvmAtomicV.IsVolatile);
+        builder.CreateBr(exitBB);
+        builder.SetInsertPoint(exitBB);
+      } else {
+        llvm::Intrinsic::ID id;
+        switch (rmw->getOperation()) {
+        case llvm::AtomicRMWInst::Min:
+          id = llvm::Intrinsic::smin;
+          break;
+        case llvm::AtomicRMWInst::Max:
+          id = llvm::Intrinsic::smax;
+          break;
+        case llvm::AtomicRMWInst::UMin:
+          id = llvm::Intrinsic::umin;
+          break;
+        case llvm::AtomicRMWInst::UMax:
+          id = llvm::Intrinsic::umax;
+          break;
+        case llvm::AtomicRMWInst::FMin:
+          id = llvm::Intrinsic::minnum;
+          break;
+        case llvm::AtomicRMWInst::FMax:
+          id = llvm::Intrinsic::maxnum;
+          break;
+        default:
+          llvm_unreachable(
+              "unexpected atomicrmw op for min/max compare capture");
+        }
+        llvm::Value *newVal = builder.CreateBinaryIntrinsic(id, oldVal, rhs);
+        builder.CreateStore(newVal, llvmAtomicV.Var, llvmAtomicV.IsVolatile);
+      }
+    }
+
+    // Equality postfix: v = select(success, D, old) — reconstructs the new
+    // value of x from the cmpxchg result.
+    if (!isMinMax && isPostfixCapture && !isFailOnly) {
       llvm::BasicBlock *curBB = builder.GetInsertBlock();
       llvm::Value *oldVal = nullptr;
       llvm::Value *successVal = nullptr;
@@ -5848,56 +6131,18 @@ convertOmpAtomicCompare(omp::AtomicCompareOp atomicCompareOp,
   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
+  // Check for a decomposed complex comparison pattern (extractvalue + fcmp +
+  // and/or of the real/imaginary fields).
+  ComplexComparePattern cplx = detectComplexCompareEq(block);
+  bool isComplexPattern = cplx.isComplex;
+  if (isComplexPattern) {
+    if (cplx.isNE)
       // 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;
+    compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
+    isXBinopExpr = cplx.isXBinopExpr;
+    eVal = materializeValue(cplx.eAggregate);
   }
 
   if (isComplexPattern) {
@@ -5916,37 +6161,10 @@ convertOmpAtomicCompare(omp::AtomicCompareOp atomicCompareOp,
       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);
-    auto *cmpXchg = builder.CreateAtomicCmpXchg(llvmX, eInt, dInt, maxAlign,
-                                                atomicOrdering, failOrdering);
-    cmpXchg->setWeak(atomicCompareOp.getWeak());
+    llvm::Align maxAlign;
+    emitComplexAtomicCmpXchg(builder, llvmX, llvmXElementType, eVal, dVal,
+                             atomicOrdering, atomicCompareOp.getWeak(),
+                             maxAlign);
 
     // Emit flush after atomic compare if needed (for release, acq_rel,
     // seq_cst orderings).
diff --git a/mlir/test/Target/LLVMIR/openmp-atomic-compare-capture.mlir b/mlir/test/Target/LLVMIR/openmp-atomic-compare-capture.mlir
new file mode 100644
index 0000000000000..2a3cb6dc99133
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/openmp-atomic-compare-capture.mlir
@@ -0,0 +1,407 @@
+// RUN: mlir-translate -mlir-to-llvmir %s | FileCheck %s
+
+// Prefix compare+capture: {read, compare}
+// CHECK-LABEL: define void @compare_capture_prefix(
+// CHECK-SAME:    ptr %[[X:.*]], ptr %[[E:.*]], ptr %[[D:.*]], ptr %[[V:.*]])
+// CHECK:         %[[EVAL:.*]] = load i32, ptr %[[E]], align 4
+// CHECK:         %[[DVAL:.*]] = load i32, ptr %[[D]], align 4
+// CHECK:         %[[RESULT:.*]] = cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
+// CHECK:         %[[OLD:.*]] = extractvalue { i32, i1 } %[[RESULT]], 0
+// CHECK:         store i32 %[[OLD]], ptr %[[V]], align 4
+llvm.func @compare_capture_prefix(%x : !llvm.ptr, %e : !llvm.ptr, %d : !llvm.ptr, %v : !llvm.ptr) {
+  %eval = llvm.load %e : !llvm.ptr -> i32
+  %dval = llvm.load %d : !llvm.ptr -> i32
+  omp.atomic.capture memory_order(relaxed) {
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+    omp.atomic.compare %x : !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
+}
+
+// Postfix compare+capture: {compare, read}
+// CHECK-LABEL: define void @compare_capture_postfix(
+// CHECK-SAME:    ptr %[[X:.*]], ptr %[[E:.*]], ptr %[[D:.*]], ptr %[[V:.*]])
+// CHECK:         %[[EVAL:.*]] = load i32, ptr %[[E]], align 4
+// CHECK:         %[[DVAL:.*]] = load i32, ptr %[[D]], align 4
+// CHECK:         %[[RESULT:.*]] = cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
+// CHECK:         %[[OLD:.*]] = extractvalue { i32, i1 } %[[RESULT]], 0
+// CHECK:         %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RESULT]], 1
+// CHECK:         %[[NEWVAL:.*]] = select i1 %[[SUCCESS]], i32 %[[DVAL]], i32 %[[OLD]]
+// CHECK:         store i32 %[[NEWVAL]], ptr %[[V]], align 4
+llvm.func @compare_capture_postfix(%x : !llvm.ptr, %e : !llvm.ptr, %d : !llvm.ptr, %v : !llvm.ptr) {
+  %eval = llvm.load %e : !llvm.ptr -> i32
+  %dval = llvm.load %d : !llvm.ptr -> i32
+  omp.atomic.capture memory_order(relaxed) {
+    omp.atomic.compare %x : !llvm.ptr {
+    ^bb0(%xval: i32):
+      %cmp = llvm.icmp "eq" %xval, %eval : i32
+      %sel = llvm.select %cmp, %dval, %xval : i1, i32
+      omp.yield(%sel : i32)
+    }
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+  }
+  llvm.return
+}
+
+// Fail-only compare+capture: {compare, read} with fail_only
+// CHECK-LABEL: define void @compare_capture_failonly(
+// CHECK-SAME:    ptr %[[X:.*]], ptr %[[E:.*]], ptr %[[D:.*]], ptr %[[V:.*]])
+// CHECK:         %[[EVAL:.*]] = load i32, ptr %[[E]], align 4
+// CHECK:         %[[DVAL:.*]] = load i32, ptr %[[D]], align 4
+// CHECK:         %[[RESULT:.*]] = cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
+// CHECK:         %[[OLD:.*]] = extractvalue { i32, i1 } %[[RESULT]], 0
+// CHECK:         %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RESULT]], 1
+// CHECK:         br i1 %[[SUCCESS]], label %{{.*}}.atomic.exit, label %{{.*}}.atomic.cont
+// CHECK:       {{.*}}.atomic.cont:
+// CHECK:         store i32 %[[OLD]], ptr %[[V]]
+// CHECK:         br label %{{.*}}.atomic.exit
+// CHECK:       {{.*}}.atomic.exit:
+llvm.func @compare_capture_failonly(%x : !llvm.ptr, %e : !llvm.ptr, %d : !llvm.ptr, %v : !llvm.ptr) {
+  %eval = llvm.load %e : !llvm.ptr -> i32
+  %dval = llvm.load %d : !llvm.ptr -> i32
+  omp.atomic.capture memory_order(relaxed) {
+    omp.atomic.compare %x : !llvm.ptr {
+    ^bb0(%xval: i32):
+      %cmp = llvm.icmp "eq" %xval, %eval : i32
+      %sel = llvm.select %cmp, %dval, %xval : i1, i32
+      omp.yield(%sel : i32)
+    }
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+  } {fail_only}
+  llvm.return
+}
+
+// Weak compare+capture: {read, compare} with weak
+// CHECK-LABEL: define void @compare_capture_weak(
+// CHECK-SAME:    ptr %[[X:.*]], ptr %[[E:.*]], ptr %[[D:.*]], ptr %[[V:.*]])
+// CHECK:         %[[EVAL:.*]] = load i32, ptr %[[E]], align 4
+// CHECK:         %[[DVAL:.*]] = load i32, ptr %[[D]], align 4
+// CHECK:         %[[RESULT:.*]] = cmpxchg weak ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
+// CHECK:         %[[OLD:.*]] = extractvalue { i32, i1 } %[[RESULT]], 0
+// CHECK:         store i32 %[[OLD]], ptr %[[V]], align 4
+llvm.func @compare_capture_weak(%x : !llvm.ptr, %e : !llvm.ptr, %d : !llvm.ptr, %v : !llvm.ptr) {
+  %eval = llvm.load %e : !llvm.ptr -> i32
+  %dval = llvm.load %d : !llvm.ptr -> i32
+  omp.atomic.capture memory_order(relaxed) {
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+    omp.atomic.compare %x : !llvm.ptr {
+    ^bb0(%xval: i32):
+      %cmp = llvm.icmp "eq" %xval, %eval : i32
+      %sel = llvm.select %cmp, %dval, %xval : i1, i32
+      omp.yield(%sel : i32)
+    } {weak}
+  }
+  llvm.return
+}
+
+// ===== Float (real) tests =====
+// Float uses the HandleFPNegZero path with NaN/zero checks and bitcast.
+
+// Float prefix compare+capture: v gets old value
+// CHECK-LABEL: define void @compare_capture_float_prefix(
+// CHECK:         cmpxchg ptr
+// CHECK:       {{.*}}.atomic.exit:
+// CHECK:         store float %{{.*}}, ptr %[[V:.*]], align 4
+llvm.func @compare_capture_float_prefix(%x : !llvm.ptr, %e : !llvm.ptr, %d : !llvm.ptr, %v : !llvm.ptr) {
+  %eval = llvm.load %e : !llvm.ptr -> f32
+  %dval = llvm.load %d : !llvm.ptr -> f32
+  omp.atomic.capture memory_order(relaxed) {
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, f32
+    omp.atomic.compare %x : !llvm.ptr {
+    ^bb0(%xval: f32):
+      %cmp = llvm.fcmp "oeq" %xval, %eval : f32
+      %sel = llvm.select %cmp, %dval, %xval : i1, f32
+      omp.yield(%sel : f32)
+    }
+  }
+  llvm.return
+}
+
+// Float postfix compare+capture: v gets select(success, d, old)
+// CHECK-LABEL: define void @compare_capture_float_postfix(
+// CHECK:         cmpxchg ptr
+// CHECK:       {{.*}}.atomic.exit:
+// CHECK:         %[[OK:.*]] = phi i1
+// CHECK:         %[[OLD_FP:.*]] = bitcast i32 %{{.*}} to float
+// CHECK:         %[[NEW:.*]] = select i1 %[[OK]], float %{{.*}}, float %[[OLD_FP]]
+// CHECK:         store float %[[NEW]], ptr %{{.*}}, align 4
+llvm.func @compare_capture_float_postfix(%x : !llvm.ptr, %e : !llvm.ptr, %d : !llvm.ptr, %v : !llvm.ptr) {
+  %eval = llvm.load %e : !llvm.ptr -> f32
+  %dval = llvm.load %d : !llvm.ptr -> f32
+  omp.atomic.capture memory_order(relaxed) {
+    omp.atomic.compare %x : !llvm.ptr {
+    ^bb0(%xval: f32):
+      %cmp = llvm.fcmp "oeq" %xval, %eval : f32
+      %sel = llvm.select %cmp, %dval, %xval : i1, f32
+      omp.yield(%sel : f32)
+    }
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, f32
+  }
+  llvm.return
+}
+
+// Float fail-only compare+capture: conditional store on failure
+// CHECK-LABEL: define void @compare_capture_float_failonly(
+// CHECK:         cmpxchg ptr
+// CHECK:       {{.*}}.atomic.exit:
+// CHECK:         %[[OK:.*]] = phi i1
+// CHECK:         %[[OLD_FP:.*]] = bitcast i32 %{{.*}} to float
+// CHECK:         br i1 %[[OK]], label %{{.*}}.atomic.exit{{.*}}, label %{{.*}}.atomic.cont
+// CHECK:       {{.*}}.atomic.cont:
+// CHECK:         store float %[[OLD_FP]], ptr %{{.*}}
+llvm.func @compare_capture_float_failonly(%x : !llvm.ptr, %e : !llvm.ptr, %d : !llvm.ptr, %v : !llvm.ptr) {
+  %eval = llvm.load %e : !llvm.ptr -> f32
+  %dval = llvm.load %d : !llvm.ptr -> f32
+  omp.atomic.capture memory_order(relaxed) {
+    omp.atomic.compare %x : !llvm.ptr {
+    ^bb0(%xval: f32):
+      %cmp = llvm.fcmp "oeq" %xval, %eval : f32
+      %sel = llvm.select %cmp, %dval, %xval : i1, f32
+      omp.yield(%sel : f32)
+    }
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, f32
+  } {fail_only}
+  llvm.return
+}
+
+// ===== Complex (struct<(f32, f32)>) tests =====
+// Complex uses bitcast to i64 for cmpxchg, with FP neg-zero handling.
+
+// Complex prefix compare+capture: v gets old value
+// CHECK-LABEL: define void @compare_capture_complex_prefix(
+// CHECK:         cmpxchg ptr %{{.*}}, i64 %{{.*}}, i64 %{{.*}} monotonic monotonic
+// CHECK:         store { float, float } %{{.*}}, ptr %{{.*}}
+llvm.func @compare_capture_complex_prefix(%x : !llvm.ptr, %e : !llvm.ptr, %d : !llvm.ptr, %v : !llvm.ptr) {
+  %eval = llvm.load %e : !llvm.ptr -> !llvm.struct<(f32, f32)>
+  %dval = llvm.load %d : !llvm.ptr -> !llvm.struct<(f32, f32)>
+  omp.atomic.capture memory_order(relaxed) {
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, !llvm.struct<(f32, f32)>
+    omp.atomic.compare %x : !llvm.ptr {
+    ^bb0(%xval: !llvm.struct<(f32, f32)>):
+      %xr = llvm.extractvalue %xval[0] : !llvm.struct<(f32, f32)>
+      %er = llvm.extractvalue %eval[0] : !llvm.struct<(f32, f32)>
+      %cmpr = llvm.fcmp "oeq" %xr, %er : f32
+      %xi = llvm.extractvalue %xval[1] : !llvm.struct<(f32, f32)>
+      %ei = llvm.extractvalue %eval[1] : !llvm.struct<(f32, f32)>
+      %cmpi = llvm.fcmp "oeq" %xi, %ei : f32
+      %cmp = llvm.and %cmpr, %cmpi : i1
+      %sel = llvm.select %cmp, %dval, %xval : i1, !llvm.struct<(f32, f32)>
+      omp.yield(%sel : !llvm.struct<(f32, f32)>)
+    }
+  }
+  llvm.return
+}
+
+// Complex postfix compare+capture: v gets select(success, d, old)
+// CHECK-LABEL: define void @compare_capture_complex_postfix(
+// CHECK:         cmpxchg ptr %{{.*}}, i64 %{{.*}}, i64 %{{.*}} monotonic monotonic
+// CHECK:         select i1 %{{.*}}, { float, float } %{{.*}}, { float, float } %{{.*}}
+// CHECK:         store { float, float } %{{.*}}, ptr %{{.*}}
+llvm.func @compare_capture_complex_postfix(%x : !llvm.ptr, %e : !llvm.ptr, %d : !llvm.ptr, %v : !llvm.ptr) {
+  %eval = llvm.load %e : !llvm.ptr -> !llvm.struct<(f32, f32)>
+  %dval = llvm.load %d : !llvm.ptr -> !llvm.struct<(f32, f32)>
+  omp.atomic.capture memory_order(relaxed) {
+    omp.atomic.compare %x : !llvm.ptr {
+    ^bb0(%xval: !llvm.struct<(f32, f32)>):
+      %xr = llvm.extractvalue %xval[0] : !llvm.struct<(f32, f32)>
+      %er = llvm.extractvalue %eval[0] : !llvm.struct<(f32, f32)>
+      %cmpr = llvm.fcmp "oeq" %xr, %er : f32
+      %xi = llvm.extractvalue %xval[1] : !llvm.struct<(f32, f32)>
+      %ei = llvm.extractvalue %eval[1] : !llvm.struct<(f32, f32)>
+      %cmpi = llvm.fcmp "oeq" %xi, %ei : f32
+      %cmp = llvm.and %cmpr, %cmpi : i1
+      %sel = llvm.select %cmp, %dval, %xval : i1, !llvm.struct<(f32, f32)>
+      omp.yield(%sel : !llvm.struct<(f32, f32)>)
+    }
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, !llvm.struct<(f32, f32)>
+  }
+  llvm.return
+}
+
+// Complex fail-only compare+capture: conditional store on failure
+// CHECK-LABEL: define void @compare_capture_complex_failonly(
+// CHECK:         cmpxchg ptr %{{.*}}, i64 %{{.*}}, i64 %{{.*}} monotonic monotonic
+// CHECK:         br i1 %{{.*}}, label %{{.*}}, label %{{.*}}
+// CHECK:         store { float, float } %{{.*}}, ptr %{{.*}}
+llvm.func @compare_capture_complex_failonly(%x : !llvm.ptr, %e : !llvm.ptr, %d : !llvm.ptr, %v : !llvm.ptr) {
+  %eval = llvm.load %e : !llvm.ptr -> !llvm.struct<(f32, f32)>
+  %dval = llvm.load %d : !llvm.ptr -> !llvm.struct<(f32, f32)>
+  omp.atomic.capture memory_order(relaxed) {
+    omp.atomic.compare %x : !llvm.ptr {
+    ^bb0(%xval: !llvm.struct<(f32, f32)>):
+      %xr = llvm.extractvalue %xval[0] : !llvm.struct<(f32, f32)>
+      %er = llvm.extractvalue %eval[0] : !llvm.struct<(f32, f32)>
+      %cmpr = llvm.fcmp "oeq" %xr, %er : f32
+      %xi = llvm.extractvalue %xval[1] : !llvm.struct<(f32, f32)>
+      %ei = llvm.extractvalue %eval[1] : !llvm.struct<(f32, f32)>
+      %cmpi = llvm.fcmp "oeq" %xi, %ei : f32
+      %cmp = llvm.and %cmpr, %cmpi : i1
+      %sel = llvm.select %cmp, %dval, %xval : i1, !llvm.struct<(f32, f32)>
+      omp.yield(%sel : !llvm.struct<(f32, f32)>)
+    }
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, !llvm.struct<(f32, f32)>
+  } {fail_only}
+  llvm.return
+}
+
+// ===== Weak clause with float =====
+
+// Float weak prefix: cmpxchg weak with FP neg-zero handling
+// CHECK-LABEL: define void @compare_capture_float_weak(
+// CHECK:         cmpxchg weak ptr %{{.*}}, i32 %{{.*}}, i32 %{{.*}} monotonic monotonic
+// CHECK:         store float %{{.*}}, ptr %{{.*}}
+llvm.func @compare_capture_float_weak(%x : !llvm.ptr, %e : !llvm.ptr, %d : !llvm.ptr, %v : !llvm.ptr) {
+  %eval = llvm.load %e : !llvm.ptr -> f32
+  %dval = llvm.load %d : !llvm.ptr -> f32
+  omp.atomic.capture memory_order(relaxed) {
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, f32
+    omp.atomic.compare %x : !llvm.ptr {
+    ^bb0(%xval: f32):
+      %cmp = llvm.fcmp "oeq" %xval, %eval : f32
+      %sel = llvm.select %cmp, %dval, %xval : i1, f32
+      omp.yield(%sel : f32)
+    } {weak}
+  }
+  llvm.return
+}
+
+// ===== Min/Max compare+capture =====
+
+// Integer min, postfix: atomicrmw min, v gets min(old, e)
+// CHECK-LABEL: define void @compare_capture_min_postfix(
+// CHECK-SAME:    ptr %[[X:.*]], ptr %[[V:.*]], i32 %[[E:.*]])
+// CHECK:         %[[OLD:.*]] = atomicrmw min ptr %[[X]], i32 %[[E]] monotonic
+// CHECK:         %[[NEW:.*]] = call i32 @llvm.smin.i32(i32 %[[OLD]], i32 %[[E]])
+// CHECK:         store i32 %[[NEW]], ptr %[[V]]
+llvm.func @compare_capture_min_postfix(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32) {
+  omp.atomic.capture {
+    omp.atomic.compare %x : !llvm.ptr {
+    ^bb0(%xval : i32):
+      %cmp = llvm.icmp "sgt" %xval, %e : i32
+      %sel = llvm.select %cmp, %e, %xval : i1, i32
+      omp.yield(%sel : i32)
+    }
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+  }
+  llvm.return
+}
+
+// Integer max, postfix: atomicrmw max, v gets max(old, e)
+// CHECK-LABEL: define void @compare_capture_max_postfix(
+// CHECK:         %[[OLD:.*]] = atomicrmw max ptr %{{.*}}, i32 %[[E:.*]] monotonic
+// CHECK:         %[[NEW:.*]] = call i32 @llvm.smax.i32(i32 %[[OLD]], i32 %[[E]])
+// CHECK:         store i32 %[[NEW]], ptr %{{.*}}
+llvm.func @compare_capture_max_postfix(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32) {
+  omp.atomic.capture {
+    omp.atomic.compare %x : !llvm.ptr {
+    ^bb0(%xval : i32):
+      %cmp = llvm.icmp "slt" %xval, %e : i32
+      %sel = llvm.select %cmp, %e, %xval : i1, i32
+      omp.yield(%sel : i32)
+    }
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+  }
+  llvm.return
+}
+
+// Integer min, prefix: v gets old value
+// CHECK-LABEL: define void @compare_capture_min_prefix(
+// CHECK:         %[[OLD:.*]] = atomicrmw min ptr %{{.*}}, i32 %{{.*}} monotonic
+// CHECK:         store i32 %[[OLD]], ptr %{{.*}}
+llvm.func @compare_capture_min_prefix(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32) {
+  omp.atomic.capture {
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+    omp.atomic.compare %x : !llvm.ptr {
+    ^bb0(%xval : i32):
+      %cmp = llvm.icmp "sgt" %xval, %e : i32
+      %sel = llvm.select %cmp, %e, %xval : i1, i32
+      omp.yield(%sel : i32)
+    }
+  }
+  llvm.return
+}
+
+// Float min, postfix: atomicrmw fmin, v gets minnum(old, e)
+// CHECK-LABEL: define void @compare_capture_fmin_postfix(
+// CHECK:         %[[OLD:.*]] = atomicrmw fmin ptr %{{.*}}, float %[[E:.*]] monotonic
+// CHECK:         %[[NEW:.*]] = call float @llvm.minnum.f32(float %[[OLD]], float %[[E]])
+// CHECK:         store float %[[NEW]], ptr %{{.*}}
+llvm.func @compare_capture_fmin_postfix(%x : !llvm.ptr, %v : !llvm.ptr, %e : f32) {
+  omp.atomic.capture {
+    omp.atomic.compare %x : !llvm.ptr {
+    ^bb0(%xval : f32):
+      %cmp = llvm.fcmp "ogt" %xval, %e : f32
+      %sel = llvm.select %cmp, %e, %xval : i1, f32
+      omp.yield(%sel : f32)
+    }
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, f32
+  }
+  llvm.return
+}
+
+// Float max, postfix: atomicrmw fmax, v gets maxnum(old, e)
+// CHECK-LABEL: define void @compare_capture_fmax_postfix(
+// CHECK:         %[[OLD:.*]] = atomicrmw fmax ptr %{{.*}}, float %[[E:.*]] monotonic
+// CHECK:         %[[NEW:.*]] = call float @llvm.maxnum.f32(float %[[OLD]], float %[[E]])
+// CHECK:         store float %[[NEW]], ptr %{{.*}}
+llvm.func @compare_capture_fmax_postfix(%x : !llvm.ptr, %v : !llvm.ptr, %e : f32) {
+  omp.atomic.capture {
+    omp.atomic.compare %x : !llvm.ptr {
+    ^bb0(%xval : f32):
+      %cmp = llvm.fcmp "olt" %xval, %e : f32
+      %sel = llvm.select %cmp, %e, %xval : i1, f32
+      omp.yield(%sel : f32)
+    }
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, f32
+  }
+  llvm.return
+}
+
+// ===== Min/Max fail-only =====
+
+// Integer min, fail-only: conditional store on failure
+// CHECK-LABEL: define void @compare_capture_min_fail_only(
+// CHECK:         %[[OLD:.*]] = atomicrmw min ptr %{{.*}}, i32 %[[E:.*]] monotonic
+// CHECK:         %[[UPD:.*]] = icmp sgt i32 %[[OLD]], %[[E]]
+// CHECK:         %[[FAILED:.*]] = xor i1 %[[UPD]], true
+// CHECK:         br i1 %[[FAILED]], label %[[CONT:.*]], label %[[EXIT:.*]]
+// CHECK:       [[CONT]]:
+// CHECK:         store i32 %[[OLD]], ptr %{{.*}}
+llvm.func @compare_capture_min_fail_only(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32) {
+  omp.atomic.capture {
+    omp.atomic.compare %x : !llvm.ptr {
+    ^bb0(%xval : i32):
+      %cmp = llvm.icmp "sgt" %xval, %e : i32
+      %sel = llvm.select %cmp, %e, %xval : i1, i32
+      omp.yield(%sel : i32)
+    }
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+  } {fail_only}
+  llvm.return
+}
+
+// Integer max, fail-only
+// CHECK-LABEL: define void @compare_capture_max_fail_only(
+// CHECK:         %[[OLD:.*]] = atomicrmw max ptr %{{.*}}, i32 %[[E:.*]] monotonic
+// CHECK:         %[[UPD:.*]] = icmp slt i32 %[[OLD]], %[[E]]
+// CHECK:         %[[FAILED:.*]] = xor i1 %[[UPD]], true
+// CHECK:         br i1 %[[FAILED]], label %[[CONT:.*]], label %[[EXIT:.*]]
+// CHECK:       [[CONT]]:
+// CHECK:         store i32 %[[OLD]], ptr %{{.*}}
+llvm.func @compare_capture_max_fail_only(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32) {
+  omp.atomic.capture {
+    omp.atomic.compare %x : !llvm.ptr {
+    ^bb0(%xval : i32):
+      %cmp = llvm.icmp "slt" %xval, %e : i32
+      %sel = llvm.select %cmp, %e, %xval : i1, i32
+      omp.yield(%sel : i32)
+    }
+    omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+  } {fail_only}
+  llvm.return
+}

>From ea6c6151e5dce95ca2b26cf7f1d0f7a2cb70c421 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Tue, 21 Jul 2026 01:34:26 -0500
Subject: [PATCH 10/11] Using load, fcmp oeq & cmpxchg for complex numbers.
 Avoiding CAS loops similar to atomic compare.

---
 .../OpenMP/atomic-compare-capture.f90         |  10 +-
 .../Integration/OpenMP/atomic-compare.f90     |  41 ++++--
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      | 124 ++++++++++++------
 mlir/test/Target/LLVMIR/openmp-llvm.mlir      |  17 ++-
 4 files changed, 129 insertions(+), 63 deletions(-)

diff --git a/flang/test/Integration/OpenMP/atomic-compare-capture.f90 b/flang/test/Integration/OpenMP/atomic-compare-capture.f90
index fe97e7b12558b..4f56720b75a0c 100644
--- a/flang/test/Integration/OpenMP/atomic-compare-capture.f90
+++ b/flang/test/Integration/OpenMP/atomic-compare-capture.f90
@@ -102,6 +102,8 @@ subroutine cc_logical(x, e, d, v)
 ! FIR:           fir.cmpc "oeq"
 ! FIR:         }
 ! LLVM-LABEL: define void @cc_complex_(
+! LLVM:         %[[REEQ:.*]] = fcmp oeq float %[[REX:.*]], %[[REE:.*]]
+! LLVM:         %[[IMEQ:.*]] = fcmp oeq float %[[IMX:.*]], %[[IME:.*]]
 ! LLVM:         cmpxchg ptr %{{.*}}, i64
 ! LLVM:         store { float, float } %{{.*}}, ptr
 subroutine cc_complex(x, e, d, v)
@@ -379,6 +381,8 @@ subroutine cc_logical_failonly(x, e, d, v)
 ! FIR-LABEL: func.func @_QPcc_complex_prefix(
 ! FIR:           omp.atomic.read %{{.*}} : !fir.ref<complex<f32>>, !fir.ref<complex<f32>>, complex<f32>
 ! LLVM-LABEL: define void @cc_complex_prefix_(
+! LLVM:         %[[REEQ:.*]] = fcmp oeq float %[[REX:.*]], %[[REE:.*]]
+! LLVM:         %[[IMEQ:.*]] = fcmp oeq float %[[IMX:.*]], %[[IME:.*]]
 ! LLVM:         %[[RES:.*]] = cmpxchg ptr %{{.*}}, i64
 ! LLVM:         store { float, float } %{{.*}}, ptr
 subroutine cc_complex_prefix(x, e, d, v)
@@ -396,8 +400,10 @@ subroutine cc_complex_prefix(x, e, d, v)
 ! FIR-LABEL: func.func @_QPcc_complex_failonly(
 ! FIR:         } {fail_only}
 ! LLVM-LABEL: define void @cc_complex_failonly_(
-! LLVM:         %[[RES:.*]] = cmpxchg ptr %{{.*}}, i64
-! LLVM:         %[[OK:.*]] = extractvalue { i64, i1 } %[[RES]], 1
+! LLVM:         %[[REEQ:.*]] = fcmp oeq float %[[REX:.*]], %[[REE:.*]]
+! LLVM:         %[[IMEQ:.*]] = fcmp oeq float %[[IMX:.*]], %[[IME:.*]]
+! LLVM:         cmpxchg ptr %{{.*}}, i64
+! LLVM:         %[[OK:.*]] = phi i1
 ! LLVM:         %[[FAILED:.*]] = xor i1 %[[OK]], true
 ! LLVM:         br i1 %[[FAILED]], label %{{.*}}, label %{{.*}}
 ! LLVM:         store { float, float } %{{.*}}, ptr
diff --git a/flang/test/Integration/OpenMP/atomic-compare.f90 b/flang/test/Integration/OpenMP/atomic-compare.f90
index e98b25e4b4539..88b4bca9e9968 100644
--- a/flang/test/Integration/OpenMP/atomic-compare.f90
+++ b/flang/test/Integration/OpenMP/atomic-compare.f90
@@ -150,32 +150,45 @@ subroutine atomic_compare_real(x, e, d)
   if (x == e) x = d
 end
 
-! Complex(4) equality → type-punned i64 cmpxchg with consistent alignment
+! Complex(4) equality -> component-wise IEEE-754 fcmp, then a cmpxchg that swaps
+! using X's loaded bit-pattern as the comparand (so -0.0/+0.0 and NaN follow the
+! scalar float semantics rather than a raw bitwise compare).
 !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: %[[EVAL:.*]] = load { float, float }, ptr %[[E]], align 4
+!CHECK: %[[DVAL:.*]] = load { float, float }, ptr %[[D]], align 4
+!CHECK: %[[DALLOCA:.*]] = alloca { float, float }, align [[ALIGN:[0-9]+]]
+!CHECK: store { float, float } %[[DVAL]], ptr %[[DALLOCA]], align [[ALIGN]]
 !CHECK: %[[DINT:.*]] = load i64, ptr %[[DALLOCA]], align [[ALIGN]]
-!CHECK: cmpxchg ptr %[[X]], i64 %[[EINT]], i64 %[[DINT]] monotonic monotonic, align [[ALIGN]]
+!CHECK: %[[XLOAD:.*]] = load atomic i64, ptr %[[X]] monotonic, align [[ALIGN]]
+!CHECK: %[[REEQ:.*]] = fcmp oeq float %[[REX:.*]], %[[REE:.*]]
+!CHECK: %[[IMEQ:.*]] = fcmp oeq float %[[IMX:.*]], %[[IME:.*]]
+!CHECK: %[[EQ:.*]] = and i1 %[[REEQ]], %[[IMEQ]]
+!CHECK: br i1 %[[EQ]], label %[[SWAP:[^,]+]], label %{{.*}}
+!CHECK: [[SWAP]]:
+!CHECK: cmpxchg ptr %[[X]], i64 %[[XLOAD]], 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
+! Complex(8) equality -> component-wise IEEE-754 fcmp, then an i128 cmpxchg using
+! X's loaded bit-pattern as the comparand.
 !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: %[[EVAL:.*]] = load { double, double }, ptr %[[E]], align 8
+!CHECK: %[[DVAL:.*]] = load { double, double }, ptr %[[D]], align 8
+!CHECK: %[[DALLOCA:.*]] = alloca { double, double }, align [[ALIGN:[0-9]+]]
+!CHECK: store { double, double } %[[DVAL]], ptr %[[DALLOCA]], align [[ALIGN]]
 !CHECK: %[[DINT:.*]] = load i128, ptr %[[DALLOCA]], align [[ALIGN]]
-!CHECK: cmpxchg ptr %[[X]], i128 %[[EINT]], i128 %[[DINT]] monotonic monotonic, align [[ALIGN]]
+!CHECK: %[[XLOAD:.*]] = load atomic i128, ptr %[[X]] monotonic, align [[ALIGN]]
+!CHECK: %[[REEQ:.*]] = fcmp oeq double %[[REX:.*]], %[[REE:.*]]
+!CHECK: %[[IMEQ:.*]] = fcmp oeq double %[[IMX:.*]], %[[IME:.*]]
+!CHECK: %[[EQ:.*]] = and i1 %[[REEQ]], %[[IMEQ]]
+!CHECK: br i1 %[[EQ]], label %[[SWAP:[^,]+]], label %{{.*}}
+!CHECK: [[SWAP]]:
+!CHECK: cmpxchg ptr %[[X]], i128 %[[XLOAD]], i128 %[[DINT]] monotonic monotonic, align [[ALIGN]]
 subroutine atomic_compare_complex8(x, e, d)
   complex(8) :: x, e, d
   !$omp atomic compare
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 060e563ecefa8..fdc8f09fe5a2d 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -5422,16 +5422,15 @@ static ComplexComparePattern detectComplexCompareEq(Block &block) {
   return result;
 }
 
-/// Emit a bitcast-to-integer `cmpxchg` for a complex (struct-typed) atomic
-/// compare. The expected/desired complex values are spilled to allocas and
-/// reloaded as an integer of the same width, then compared and swapped. Returns
-/// the cmpxchg (result type `{iN, i1}`); `maxAlign` receives the alignment used
-/// so callers can reinterpret the captured old value through memory.
-static llvm::AtomicCmpXchgInst *
-emitComplexAtomicCmpXchg(llvm::IRBuilderBase &builder, llvm::Value *llvmX,
-                         llvm::Type *complexTy, llvm::Value *eVal,
-                         llvm::Value *dVal, llvm::AtomicOrdering atomicOrdering,
-                         bool isWeak, llvm::Align &maxAlign) {
+/// Emit an IEEE-754-correct `cmpxchg` for a complex (struct-typed) atomic
+/// compare with `fcmp oeq`. The old value of X is returned (as the complex struct
+/// type) in \p oldComplex and the success flag (i1) in \p cmpOk.
+static void emitComplexAtomicCmpXchg(llvm::IRBuilderBase &builder,
+                                     llvm::Value *llvmX, llvm::Type *complexTy,
+                                     llvm::Value *eVal, llvm::Value *dVal,
+                                     llvm::AtomicOrdering atomicOrdering,
+                                     bool isWeak, llvm::Value *&oldComplex,
+                                     llvm::Value *&cmpOk) {
   const llvm::DataLayout &DL =
       builder.GetInsertBlock()->getModule()->getDataLayout();
   unsigned totalBits = DL.getTypeStoreSizeInBits(complexTy).getFixedValue();
@@ -5439,28 +5438,76 @@ emitComplexAtomicCmpXchg(llvm::IRBuilderBase &builder, llvm::Value *llvmX,
       llvm::IntegerType::get(builder.getContext(), totalBits);
   llvm::Align complexAlign = DL.getABITypeAlign(complexTy);
   llvm::Align intAlign = DL.getABITypeAlign(intTy);
-  maxAlign = std::max(complexAlign, intAlign);
+  llvm::Align maxAlign = std::max(complexAlign, intAlign);
 
-  llvm::AllocaInst *eAlloca =
-      builder.CreateAlloca(complexTy, nullptr, "cmplx.e");
-  eAlloca->setAlignment(maxAlign);
+  // Spill D to obtain its integer bit pattern for the swap value.
   llvm::AllocaInst *dAlloca =
       builder.CreateAlloca(complexTy, 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");
 
+  // Load the current value of X atomically and reinterpret it as complex.
+  llvm::LoadInst *xCurr =
+      builder.CreateAlignedLoad(intTy, llvmX, maxAlign, "cmplx.x.load");
+  xCurr->setAtomic(llvm::AtomicOrdering::Monotonic);
+  llvm::AllocaInst *xAlloca =
+      builder.CreateAlloca(complexTy, nullptr, "cmplx.x");
+  xAlloca->setAlignment(maxAlign);
+  builder.CreateAlignedStore(xCurr, xAlloca, maxAlign);
+  llvm::Value *xStruct =
+      builder.CreateAlignedLoad(complexTy, xAlloca, maxAlign, "cmplx.x.val");
+
+  // Component-wise IEEE-754 equality: `fcmp oeq` yields false for NaN (so a
+  // NaN component correctly makes the compare fail) and true for +0.0 vs -0.0
+  // (so a zero-sign difference does not spuriously fail the compare).
+  llvm::Value *reX = builder.CreateExtractValue(xStruct, 0);
+  llvm::Value *imX = builder.CreateExtractValue(xStruct, 1);
+  llvm::Value *reE = builder.CreateExtractValue(eVal, 0);
+  llvm::Value *imE = builder.CreateExtractValue(eVal, 1);
+  llvm::Value *reEq = builder.CreateFCmpOEQ(reX, reE, "cmplx.re.eq");
+  llvm::Value *imEq = builder.CreateFCmpOEQ(imX, imE, "cmplx.im.eq");
+  llvm::Value *fpEqual = builder.CreateAnd(reEq, imEq, "cmplx.eq");
+
+  // When the components compare equal, attempt the swap using X's own loaded
+  // bit pattern as the comparand; otherwise the compare fails and X is left
+  // unchanged (the captured old value is the value just loaded).
+  llvm::BasicBlock *curBB = builder.GetInsertBlock();
+  llvm::Function *fn = curBB->getParent();
+  llvm::BasicBlock *swapBB =
+      llvm::BasicBlock::Create(builder.getContext(), "cmplx.atomic.swap", fn);
+  llvm::BasicBlock *exitBB =
+      llvm::BasicBlock::Create(builder.getContext(), "cmplx.atomic.exit", fn);
+  builder.CreateCondBr(fpEqual, swapBB, exitBB);
+
+  builder.SetInsertPoint(swapBB);
   llvm::AtomicOrdering failOrdering =
       llvm::AtomicCmpXchgInst::getStrongestFailureOrdering(atomicOrdering);
-  auto *cmpXchg = builder.CreateAtomicCmpXchg(llvmX, eInt, dInt, maxAlign,
-                                              atomicOrdering, failOrdering);
+  llvm::AtomicCmpXchgInst *cmpXchg = builder.CreateAtomicCmpXchg(
+      llvmX, xCurr, dInt, maxAlign, atomicOrdering, failOrdering);
   cmpXchg->setWeak(isWeak);
-  return cmpXchg;
+  llvm::Value *oldSwap = builder.CreateExtractValue(cmpXchg, 0);
+  llvm::Value *okSwap = builder.CreateExtractValue(cmpXchg, 1);
+  builder.CreateBr(exitBB);
+
+  // Merge the swap and no-swap paths.
+  builder.SetInsertPoint(exitBB);
+  llvm::PHINode *oldIntPHI = builder.CreatePHI(intTy, 2, "cmplx.old.int");
+  oldIntPHI->addIncoming(oldSwap, swapBB);
+  oldIntPHI->addIncoming(xCurr, curBB);
+  llvm::PHINode *okPHI = builder.CreatePHI(builder.getInt1Ty(), 2, "cmplx.ok");
+  okPHI->addIncoming(okSwap, swapBB);
+  okPHI->addIncoming(builder.getFalse(), curBB);
+
+  // Reinterpret the old integer value as the complex struct via memory.
+  llvm::AllocaInst *oldAlloca =
+      builder.CreateAlloca(complexTy, nullptr, "cmplx.old");
+  oldAlloca->setAlignment(maxAlign);
+  builder.CreateAlignedStore(oldIntPHI, oldAlloca, maxAlign);
+  oldComplex = builder.CreateAlignedLoad(complexTy, oldAlloca, maxAlign,
+                                         "cmplx.old.val");
+  cmpOk = okPHI;
 }
 
 /// Holds the extracted comparison pattern information from an atomic compare
@@ -5687,25 +5734,17 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
     bool isFailOnly = atomicCaptureOp.getFailOnly();
 
     // Complex equality capture: x is struct-typed, which the OMPIRBuilder
-    // cannot handle, so emit a bitcast-to-integer cmpxchg (as in the
-    // non-capture complex path) and reconstruct the captured value from its
-    // result through memory. Complex only supports the == comparison.
+    // cannot handle, so emit an IEEE-754-correct cmpxchg (as in the non-capture
+    // complex path) and reconstruct the captured value from its result. The
+    // helper compares components with `fcmp oeq` so `-0.0 == +0.0` and `NaN`
+    // are handled as in the scalar float path. Complex only supports the ==
+    // comparison.
     if (llvmXElementType->isStructTy()) {
-      llvm::Align maxAlign;
-      llvm::AtomicCmpXchgInst *cmpXchg = emitComplexAtomicCmpXchg(
-          builder, llvmX, llvmXElementType, eVal, dVal, atomicOrdering,
-          atomicCompareOp.getWeak(), maxAlign);
-
-      llvm::Value *oldInt = builder.CreateExtractValue(cmpXchg, 0);
-      llvm::Value *cmpOk = builder.CreateExtractValue(cmpXchg, 1);
-
-      // Reinterpret the old integer value as the complex struct via memory.
-      llvm::AllocaInst *oldAlloca =
-          builder.CreateAlloca(llvmXElementType, nullptr, "cmplx.old");
-      oldAlloca->setAlignment(maxAlign);
-      builder.CreateAlignedStore(oldInt, oldAlloca, maxAlign);
-      llvm::Value *oldComplex = builder.CreateAlignedLoad(
-          llvmXElementType, oldAlloca, maxAlign, "cmplx.old.val");
+      llvm::Value *oldComplex = nullptr;
+      llvm::Value *cmpOk = nullptr;
+      emitComplexAtomicCmpXchg(builder, llvmX, llvmXElementType, eVal, dVal,
+                               atomicOrdering, atomicCompareOp.getWeak(),
+                               oldComplex, cmpOk);
 
       if (isFailOnly) {
         // v is written only when the compare fails (cmpOk == false).
@@ -6161,10 +6200,13 @@ convertOmpAtomicCompare(omp::AtomicCompareOp atomicCompareOp,
       dVal = materializeValue(yieldOp.getResults()[0]);
     }
 
-    llvm::Align maxAlign;
+    llvm::Value *oldComplex = nullptr;
+    llvm::Value *cmpOk = nullptr;
     emitComplexAtomicCmpXchg(builder, llvmX, llvmXElementType, eVal, dVal,
                              atomicOrdering, atomicCompareOp.getWeak(),
-                             maxAlign);
+                             oldComplex, cmpOk);
+    (void)oldComplex;
+    (void)cmpOk;
 
     // Emit flush after atomic compare if needed (for release, acq_rel,
     // seq_cst orderings).
diff --git a/mlir/test/Target/LLVMIR/openmp-llvm.mlir b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
index b779d20f42d0c..63054056adadc 100644
--- a/mlir/test/Target/LLVMIR/openmp-llvm.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
@@ -2619,14 +2619,19 @@ llvm.func @omp_atomic_compare(
     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]]
+  // Complex equality  →  component-wise IEEE-754 fcmp, then a cmpxchg that
+  // swaps using x's loaded bit-pattern as the comparand (so -0.0/+0.0 and NaN
+  // follow the scalar float semantics rather than a raw bitwise compare).
+  // CHECK: %[[DALLOCA:.*]] = alloca { float, float }, align [[ALIGN:[0-9]+]]
   // 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]]
+  // CHECK: %[[XLOAD:.*]] = load atomic i64, ptr %[[XC]] monotonic, align [[ALIGN]]
+  // CHECK: %[[REEQ:.*]] = fcmp oeq float %[[REX:.*]], %[[REE:.*]]
+  // CHECK: %[[IMEQ:.*]] = fcmp oeq float %[[IMX:.*]], %[[IME:.*]]
+  // CHECK: %[[CEQ:.*]] = and i1 %[[REEQ]], %[[IMEQ]]
+  // CHECK: br i1 %[[CEQ]], label %[[CSWAP:[^,]+]], label %{{.*}}
+  // CHECK: [[CSWAP]]:
+  // CHECK: cmpxchg ptr %[[XC]], i64 %[[XLOAD]], 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)>

>From a446fe321fd0023edecfbc3734cc761a61ff7fc0 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Tue, 21 Jul 2026 02:05:43 -0500
Subject: [PATCH 11/11] Correcting a code formatting error.

---
 .../LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp       | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index fdc8f09fe5a2d..69b722582d785 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -5423,8 +5423,8 @@ static ComplexComparePattern detectComplexCompareEq(Block &block) {
 }
 
 /// Emit an IEEE-754-correct `cmpxchg` for a complex (struct-typed) atomic
-/// compare with `fcmp oeq`. The old value of X is returned (as the complex struct
-/// type) in \p oldComplex and the success flag (i1) in \p cmpOk.
+/// compare with `fcmp oeq`. The old value of X is returned (as the complex
+/// struct type) in \p oldComplex and the success flag (i1) in \p cmpOk.
 static void emitComplexAtomicCmpXchg(llvm::IRBuilderBase &builder,
                                      llvm::Value *llvmX, llvm::Type *complexTy,
                                      llvm::Value *eVal, llvm::Value *dVal,



More information about the flang-commits mailing list