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

via flang-commits flang-commits at lists.llvm.org
Thu Aug 13 11:46:05 PDT 2026


Author: SunilKuravinakop
Date: 2026-08-14T00:15:59+05:30
New Revision: e8ff1d1d93728ba488c8a09023c4554030463ced

URL: https://github.com/llvm/llvm-project/commit/e8ff1d1d93728ba488c8a09023c4554030463ced
DIFF: https://github.com/llvm/llvm-project/commit/e8ff1d1d93728ba488c8a09023c4554030463ced.diff

LOG: [flang][OpenMP] Support for "atomic compare capture" (#202315)

Adding support for "!$omp atomic compare capture".

This also Fixes [#202311](https://github.com/llvm/llvm-project/issues/202311)

Co-authored-by: Sunil Kuravinakop <koops at hpe.com>

Added: 
    flang/test/Fir/atomic-compare-capture.fir
    flang/test/Integration/OpenMP/atomic-compare-capture.f90
    flang/test/Lower/OpenMP/Todo/atomic-compare-capture-result.f90
    flang/test/Lower/OpenMP/atomic-compare-capture.f90
    flang/test/Semantics/OpenMP/atomic-compare-capture-fail.f90
    mlir/test/Target/LLVMIR/openmp-atomic-compare-capture.mlir

Modified: 
    flang/lib/Lower/OpenMP/Atomic.cpp
    flang/lib/Semantics/check-omp-atomic.cpp
    flang/test/Integration/OpenMP/atomic-compare.f90
    flang/test/Lower/OpenMP/atomic-compare-fail.f90
    flang/test/Lower/OpenMP/atomic-compare.f90
    flang/test/Parser/OpenMP/atomic-unparse.f90
    flang/test/Semantics/OpenMP/atomic-compare.f90
    mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
    mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
    mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
    mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
    mlir/test/Dialect/OpenMP/ops.mlir
    mlir/test/Target/LLVMIR/openmp-llvm.mlir

Removed: 
    flang/test/Lower/OpenMP/Todo/atomic-compare-fail.f90


################################################################################
diff  --git a/flang/lib/Lower/OpenMP/Atomic.cpp b/flang/lib/Lower/OpenMP/Atomic.cpp
index faf170c49149e..37c856795defe 100644
--- a/flang/lib/Lower/OpenMP/Atomic.cpp
+++ b/flang/lib/Lower/OpenMP/Atomic.cpp
@@ -392,6 +392,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;
@@ -577,16 +579,54 @@ 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 &&
+           "Expecting two actions");
+    (void)action0;
+    (void)action1;
+    captureOp = mlir::omp::AtomicCaptureOp::create(
+        builder, loc, hint, makeMemOrderAttr(converter, memOrder),
+        /*fail_only=*/nullptr);
+    // 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
 
-    // Atomic compare with capture is not yet supported.
-    if (llvm::any_of(clauses, [](const omp::Clause &clause) {
-          return clause.id == llvm::omp::Clause::OMPC_capture;
-        })) {
-      TODO(loc, "Compound clauses of OpenMP ATOMIC COMPARE");
+    // Restore insertion point so pre-processing code (e.g. computing
+    // expectedVal) is emitted before the capture op, not after the terminator.
+    builder.restoreInsertionPoint(preAt);
+
+    // 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')");
     }
 
     // The `fail` clause sets the memory ordering for a failed compare;
@@ -629,6 +669,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");
@@ -642,12 +721,100 @@ 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
+    // 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()) {
+      // 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;
     if (llvm::any_of(clauses, [](const omp::Clause &clause) {
           return clause.id == llvm::omp::Clause::OMPC_weak;
         })) {
       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),
@@ -676,35 +843,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);
 
@@ -712,36 +850,23 @@ void Fortran::lower::omp::lowerAtomic(
     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 {
-    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");
+             "Expecting single action");
       assert(!(analysis.op0.what & analysis.Condition));
       postAt = atomicAt = preAt;
     }
@@ -761,16 +886,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/flang/lib/Semantics/check-omp-atomic.cpp b/flang/lib/Semantics/check-omp-atomic.cpp
index 810d91a54d710..7db4bdefa65b0 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
+// 
diff erent variables and 
diff erent 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/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..4f56720b75a0c
--- /dev/null
+++ b/flang/test/Integration/OpenMP/atomic-compare-capture.f90
@@ -0,0 +1,449 @@
+!===----------------------------------------------------------------------===!
+! 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:         %[[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)
+  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 
diff erent *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:         %[[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)
+  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:         %[[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
+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/Integration/OpenMP/atomic-compare.f90 b/flang/test/Integration/OpenMP/atomic-compare.f90
index 249fb0dd8fa64..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
@@ -260,3 +273,164 @@ subroutine atomic_compare_weak(x, e, d)
   if (x == e) x = d
 end 
 
+! 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]]
+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 (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]]
+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
+
+! 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
+
+! 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, 
diff erent 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
+
+! 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/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
deleted file mode 100644
index 27c7b1fb1a432..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/atomic-compare-fail.f90
+++ /dev/null
@@ -1,28 +0,0 @@
-! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -fopenmp-version=51 -o - %s 2>&1 | FileCheck %s
-
-! CHECK: not yet implemented: Compound clauses of OpenMP ATOMIC COMPARE
-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
-  end if
-  !$omp end atomic
-
-end program p

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..b678c074e7ace
--- /dev/null
+++ b/flang/test/Lower/OpenMP/atomic-compare-capture.f90
@@ -0,0 +1,386 @@
+! 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:         %[[E_DECL:.*]]:2 = hlfir.declare %arg1 {{.*}}Ee"
+! CHECK:         %[[E_VAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! 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]], %[[E_VAL]] : 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:         %[[E_DECL:.*]]:2 = hlfir.declare %arg1 {{.*}}Ee"
+! CHECK:         %[[E_VAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK:         omp.atomic.capture memory_order(relaxed) {
+! CHECK:           omp.atomic.compare %{{.*}}#0 : !fir.ref<i32> {
+! CHECK:           ^bb0(%[[XVAL:.*]]: i32):
+! CHECK:             arith.cmpi eq, %[[XVAL]], %[[E_VAL]] : 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 == fail-only with `fail` clause: v is captured on the failure path,
+! and the fail clause sets that path's ordering independently of the success
+! ordering (seq_cst success, acquire failure).
+! CHECK-LABEL: func.func @_QPcc_int_failonly_fail(
+! CHECK:         %[[E_DECL:.*]]:2 = hlfir.declare %arg1 {{.*}}Ee"
+! CHECK:         %[[E_VAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK:         omp.atomic.capture memory_order(seq_cst) {
+! CHECK:           omp.atomic.compare %{{.*}}#0 : !fir.ref<i32> {
+! CHECK:           ^bb0(%[[XVAL:.*]]: i32):
+! CHECK:             arith.cmpi eq, %[[XVAL]], %[[E_VAL]] : i32
+! CHECK:             omp.yield
+! CHECK:           } {fail_memory_order = #omp<memoryorderkind acquire>}
+! CHECK:           omp.atomic.read %{{.*}}#0 = %{{.*}}#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK:         } {fail_only}
+subroutine cc_int_failonly_fail(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic compare capture seq_cst fail(acquire)
+  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-fail.f90 b/flang/test/Lower/OpenMP/atomic-compare-fail.f90
index 5f63ffa4890fd..3d603f1b2d11e 100644
--- a/flang/test/Lower/OpenMP/atomic-compare-fail.f90
+++ b/flang/test/Lower/OpenMP/atomic-compare-fail.f90
@@ -11,9 +11,11 @@ subroutine fail_acquire(x, e, d)
   !$omp atomic compare fail(acquire)
   if (x == e) x = d
 end
+! CHECK:   %[[E_DECL:.*]]:2 = hlfir.declare %arg1 {{.*}}Ee"
+! CHECK:   %[[E_VAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
 ! CHECK: omp.atomic.compare memory_order(relaxed) %{{.*}} : !fir.ref<i32> {
 ! CHECK: ^bb0(%[[XVAL:.*]]: i32):
-! CHECK:   arith.cmpi eq, %[[XVAL]], %{{.*}} : i32
+! CHECK:   arith.cmpi eq, %[[XVAL]], %[[E_VAL]] : i32
 ! CHECK:   omp.yield
 ! CHECK: } {fail_memory_order = #omp<memoryorderkind acquire>}
 

diff  --git a/flang/test/Lower/OpenMP/atomic-compare.f90 b/flang/test/Lower/OpenMP/atomic-compare.f90
index ac70edbed4e60..076aa08e1f3b2 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{{.*}}
@@ -161,3 +161,421 @@ 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:         %[[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:             %[[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:         %[[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:             %[[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
+
+! 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:         %[[E_DECL:.*]]:2 = hlfir.declare %arg1 {{.*}}Ee"
+! CHECK:         %[[E_VAL:.*]] = 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]], %[[E_VAL]] : 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:         %[[E_VAL:.*]] = 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 olt, %[[XVAL]], %[[E_VAL]] 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"},
+! 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 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:         %[[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:         } {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
+
+! 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
+
+! 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:         %[[E_DECL:.*]]:2 = hlfir.declare %arg1 {{.*}}Ee"
+! CHECK:         %[[E_VAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! 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]], %[[E_VAL]] : 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:         %[[E_DECL:.*]]:2 = hlfir.declare %arg1 {{.*}}Ee"
+! CHECK:         %[[E_VAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! 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]], %[[E_VAL]] : 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:         %[[E_DECL:.*]]:2 = hlfir.declare %arg1 {{.*}}Ee"
+! CHECK:         %[[E_VAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! 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]], %[[E_VAL]] : 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:         %[[E_DECL:.*]]:2 = hlfir.declare %arg1 {{.*}}Ee"
+! CHECK:         %[[E_VAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! 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]], %[[E_VAL]] : 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"},
+! 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
+
+! 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/Parser/OpenMP/atomic-unparse.f90 b/flang/test/Parser/OpenMP/atomic-unparse.f90
index 4f3cf0eac0338..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
@@ -192,6 +193,45 @@ 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
+!$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
+
+!COMPARE CAPTURE LOGICAL
+!$omp atomic compare capture
+   if (l1 .eqv. l2) l1 = l3
+   l4 = l1
+!$omp end atomic
+
 !ATOMIC
 !$omp atomic
    i = j
@@ -296,6 +336,24 @@ 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
+!CHECK: !$OMP ATOMIC COMPARE CAPTURE
+!CHECK: !$OMP END ATOMIC
+!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/flang/test/Semantics/OpenMP/atomic-compare-capture-fail.f90 b/flang/test/Semantics/OpenMP/atomic-compare-capture-fail.f90
new file mode 100644
index 0000000000000..5bc16bd125196
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/atomic-compare-capture-fail.f90
@@ -0,0 +1,148 @@
+! RUN: %python %S/../test_errors.py %s %flang_fc1 -fopenmp -fopenmp-version=52
+
+! Atomic compare-capture with each of the 5 effective memory orders combined
+! with each of the 3 valid FAIL arguments (SEQ_CST, ACQUIRE, RELAXED): all 15
+! combinations are valid, so no diagnostics are expected.
+
+!===----------------------------------------------------------------------===!
+! Success order: SEQ_CST
+!===----------------------------------------------------------------------===!
+
+subroutine seq_cst_fail_seq_cst(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic update compare capture seq_cst fail(seq_cst)
+  v = x
+  if (x == e) x = d
+  !$omp end atomic
+end subroutine
+
+subroutine seq_cst_fail_acquire(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic update compare capture seq_cst fail(acquire)
+  if (x == e) then
+    x = d
+  else
+    v = x
+  end if
+  !$omp end atomic
+end subroutine
+
+subroutine seq_cst_fail_relaxed(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic update compare capture seq_cst fail(relaxed)
+  v = x
+  if (x == e) x = d
+  !$omp end atomic
+end subroutine
+
+!===----------------------------------------------------------------------===!
+! Success order: ACQ_REL
+!===----------------------------------------------------------------------===!
+
+subroutine acq_rel_fail_seq_cst(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic update compare capture acq_rel fail(seq_cst)
+  v = x
+  if (x == e) x = d
+  !$omp end atomic
+end subroutine
+
+subroutine acq_rel_fail_acquire(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic update compare capture acq_rel fail(acquire)
+  v = x
+  if (x == e) x = d
+  !$omp end atomic
+end subroutine
+
+subroutine acq_rel_fail_relaxed(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic update compare capture acq_rel fail(relaxed)
+  v = x
+  if (x == e) x = d
+  !$omp end atomic
+end subroutine
+
+!===----------------------------------------------------------------------===!
+! Success order: RELEASE
+!===----------------------------------------------------------------------===!
+
+subroutine release_fail_seq_cst(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic update compare capture release fail(seq_cst)
+  v = x
+  if (x == e) x = d
+  !$omp end atomic
+end subroutine
+
+subroutine release_fail_acquire(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic update compare capture release fail(acquire)
+  v = x
+  if (x == e) x = d
+  !$omp end atomic
+end subroutine
+
+subroutine release_fail_relaxed(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic update compare capture release fail(relaxed)
+  v = x
+  if (x == e) x = d
+  !$omp end atomic
+end subroutine
+
+!===----------------------------------------------------------------------===!
+! Success order: ACQUIRE
+!===----------------------------------------------------------------------===!
+
+subroutine acquire_fail_seq_cst(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic update compare capture acquire fail(seq_cst)
+  v = x
+  if (x == e) x = d
+  !$omp end atomic
+end subroutine
+
+subroutine acquire_fail_acquire(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic update compare capture acquire fail(acquire)
+  v = x
+  if (x == e) x = d
+  !$omp end atomic
+end subroutine
+
+subroutine acquire_fail_relaxed(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic update compare capture acquire fail(relaxed)
+  v = x
+  if (x == e) x = d
+  !$omp end atomic
+end subroutine
+
+!===----------------------------------------------------------------------===!
+! Success order: RELAXED
+!===----------------------------------------------------------------------===!
+
+subroutine relaxed_fail_seq_cst(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic update compare capture relaxed fail(seq_cst)
+  v = x
+  if (x == e) x = d
+  !$omp end atomic
+end subroutine
+
+subroutine relaxed_fail_acquire(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic update compare capture relaxed fail(acquire)
+  v = x
+  if (x == e) x = d
+  !$omp end atomic
+end subroutine
+
+subroutine relaxed_fail_relaxed(x, e, d, v)
+  integer :: x, e, d, v
+  !$omp atomic update compare capture relaxed fail(relaxed)
+  v = x
+  if (x == e) x = d
+  !$omp end atomic
+end subroutine

diff  --git a/flang/test/Semantics/OpenMP/atomic-compare.f90 b/flang/test/Semantics/OpenMP/atomic-compare.f90
index 64b2c2b3a9225..f1f541a6cdc2a 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 
diff erent (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 ATOMIC directive
   !$omp atomic seq_cst seq_cst compare

diff  --git a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
index abb21705b3c1c..a61c43e2fd9cd 100644
--- a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
+++ b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
@@ -286,13 +286,17 @@ 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);
+        auto secondCompareStmt = dyn_cast<AtomicCompareOpInterface>(secondOp);
 
         if (!((firstUpdateStmt && secondReadStmt) ||
               (firstReadStmt && secondUpdateStmt) ||
-              (firstReadStmt && secondWriteStmt)))
+              (firstReadStmt && secondWriteStmt) ||
+              (firstReadStmt && secondCompareStmt) ||
+              (firstCompareStmt && secondReadStmt)))
           return ops.front().emitError()
                 << "invalid sequence of operations in the capture region";
         if (firstUpdateStmt && secondReadStmt &&
@@ -310,6 +314,16 @@ 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";
+        if (firstCompareStmt && secondReadStmt &&
+            secondReadStmt.getX() != firstCompareStmt.getX())
+          return secondReadStmt.emitError()
+                << "captured variable in atomic.read must be updated in "
+                    "first operation";
 
         return mlir::success();
       }]
@@ -474,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/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index 59cfbd620721e..b5ecf88596f8c 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -2028,9 +2028,17 @@ def AtomicCaptureOp : OpenMP_Op<"atomic.capture", traits = [
         omp.atomic.write ...
         omp.terminator
       }
+
+      omp.atomic.capture {
+        omp.atomic.read ...
+        omp.atomic.compare ...
+        omp.terminator
+      }
     ```
   }] # clausesDescription;
 
+  let arguments = !con(clausesArgs, (ins UnitAttr:$fail_only));
+
   // Override region definition.
   let regions = (region SizedRegion<1>:$region);
 
@@ -2046,6 +2054,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 0b7edec98625c..6aabe6e918049 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -5050,6 +5050,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 4864aa2a005b2..1a861767b5512 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"
@@ -5628,6 +5629,18 @@ convertAtomicOrdering(std::optional<omp::ClauseMemoryOrderKind> ao) {
   llvm_unreachable("Unknown ClauseMemoryOrderKind kind");
 }
 
+/// Compute the cmpxchg failure ordering for an atomic compare op: use the
+/// `fail` clause ordering when present (the verifier guarantees it is a valid
+/// cmpxchg failure ordering), otherwise the strongest failure ordering derived
+/// from the success ordering (which matches the OpenMPIRBuilder default).
+static llvm::AtomicOrdering
+getAtomicCompareFailureOrdering(omp::AtomicCompareOp atomicCompareOp,
+                                llvm::AtomicOrdering atomicOrdering) {
+  if (atomicCompareOp.getFailMemoryOrder())
+    return convertAtomicOrdering(atomicCompareOp.getFailMemoryOrder());
+  return llvm::AtomicCmpXchgInst::getStrongestFailureOrdering(atomicOrdering);
+}
+
 /// Convert omp.atomic.read operation to LLVM IR.
 static LogicalResult
 convertOmpAtomicRead(Operation &opInst, llvm::IRBuilderBase &builder,
@@ -5797,6 +5810,302 @@ 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;
+  }
+}
+
+/// 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 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.
+/// \p failOrdering is the memory ordering used when the compare-exchange does
+/// not store; it must be a valid cmpxchg failure ordering.
+static void emitComplexAtomicCmpXchg(llvm::IRBuilderBase &builder,
+                                     llvm::Value *llvmX, llvm::Type *complexTy,
+                                     llvm::Value *eVal, llvm::Value *dVal,
+                                     llvm::AtomicOrdering atomicOrdering,
+                                     llvm::AtomicOrdering failOrdering,
+                                     bool isWeak, llvm::Value *&oldComplex,
+                                     llvm::Value *&cmpOk) {
+  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);
+  llvm::Align maxAlign = std::max(complexAlign, intAlign);
+
+  // 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(dVal, dAlloca, maxAlign);
+  llvm::Value *dInt =
+      builder.CreateAlignedLoad(intTy, dAlloca, maxAlign, "cmplx.d.int");
+
+  // Load X atomically and reinterpret as complex. Use the failure ordering: on
+  // a failed component comparison we branch around the cmpxchg, so this load is
+  // the only memory op on that path.
+  llvm::LoadInst *xCurr =
+      builder.CreateAlignedLoad(intTy, llvmX, maxAlign, "cmplx.x.load");
+  xCurr->setAtomic(failOrdering);
+  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 
diff erence 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::AtomicCmpXchgInst *cmpXchg = builder.CreateAtomicCmpXchg(
+      llvmX, xCurr, dInt, maxAlign, atomicOrdering, failOrdering);
+  cmpXchg->setWeak(isWeak);
+  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
+/// 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) {
+  // 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).
+    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,
@@ -5805,13 +6114,344 @@ 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, 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))
+        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.
+    AtomicComparePatternInfo patternInfo;
+    if (failed(extractAtomicComparePattern(block, materializeValue,
+                                           atomicCompareOp, patternInfo)))
+      return failure();
+
+    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(
+          "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 isReadFirst = isa<omp::AtomicReadOp>(atomicCaptureOp.getFirstOp());
+    bool isPostfixCapture = !isReadFirst;
+    bool isFailOnly = atomicCaptureOp.getFailOnly();
+
+    // Complex equality capture: x is struct-typed, which the OMPIRBuilder
+    // 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::Value *oldComplex = nullptr;
+      llvm::Value *cmpOk = nullptr;
+      llvm::AtomicOrdering failOrdering =
+          getAtomicCompareFailureOrdering(atomicCompareOp, atomicOrdering);
+      emitComplexAtomicCmpXchg(builder, llvmX, llvmXElementType, eVal, dVal,
+                               atomicOrdering, failOrdering,
+                               atomicCompareOp.getWeak(), oldComplex, cmpOk);
+
+      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;
+    // 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};
+
+    // 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::AtomicOrdering failureOrdering =
+        getAtomicCompareFailureOrdering(atomicCompareOp, atomicOrdering);
+    llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
+        ompBuilder->createAtomicCompare(
+            ompLoc, llvmAtomicX, llvmAtomicVForCall, llvmAtomicR, eVal, dVal,
+            atomicOrdering, compareOp, isXBinopExpr, isPostfixUpdate,
+            builderFailOnly, failureOrdering, isWeak);
+    ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
+
+    if (failed(handleError(afterIP, *atomicCaptureOp)))
+      return failure();
+
+    builder.restoreIP(*afterIP);
+
+    // 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;
+
+      // 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)) {
+          oldVal = builder.CreateExtractValue(&inst, /*Idxs=*/0);
+          successVal = builder.CreateExtractValue(&inst, /*Idxs=*/1);
+          break;
+        }
+      }
+
+      // 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);
+    }
+
+    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");
 
@@ -5898,43 +6538,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
@@ -5984,7 +6587,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.,
@@ -6047,56 +6654,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) {
@@ -6115,43 +6684,15 @@ 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");
-
-    // Honor the `fail` clause ordering when present (the verifier guarantees
-    // it is a valid cmpxchg failure ordering); otherwise derive it from the
-    // success ordering.
+    llvm::Value *oldComplex = nullptr;
+    llvm::Value *cmpOk = nullptr;
     llvm::AtomicOrdering failOrdering =
-        atomicCompareOp.getFailMemoryOrder()
-            ? convertAtomicOrdering(atomicCompareOp.getFailMemoryOrder())
-            : llvm::AtomicCmpXchgInst::getStrongestFailureOrdering(
-                  atomicOrdering);
-    auto *cmpXchg = builder.CreateAtomicCmpXchg(llvmX, eInt, dInt, maxAlign,
-                                                atomicOrdering, failOrdering);
-    cmpXchg->setWeak(atomicCompareOp.getWeak());
+        getAtomicCompareFailureOrdering(atomicCompareOp, atomicOrdering);
+    emitComplexAtomicCmpXchg(builder, llvmX, llvmXElementType, eVal, dVal,
+                             atomicOrdering, failOrdering,
+                             atomicCompareOp.getWeak(), oldComplex, cmpOk);
+    (void)oldComplex;
+    (void)cmpOk;
 
     // Emit flush after atomic compare if needed (for release, acq_rel,
     // seq_cst orderings).
@@ -6163,54 +6704,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)
@@ -6236,19 +6738,13 @@ convertOmpAtomicCompare(omp::AtomicCompareOp atomicCompareOp,
   bool isWeak = atomicCompareOp.getWeak();
 
   bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
-  llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP = [&]() {
-    if (auto failOrder = atomicCompareOp.getFailMemoryOrder()) {
-      llvm::AtomicOrdering failureOrdering = convertAtomicOrdering(*failOrder);
-      return ompBuilder->createAtomicCompare(
+  llvm::AtomicOrdering failureOrdering =
+      getAtomicCompareFailureOrdering(atomicCompareOp, atomicOrdering);
+  llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
+      ompBuilder->createAtomicCompare(
           ompLoc, llvmAtomicX, vOpVal, rOpVal, eVal, dVal, atomicOrdering,
           compareOp, isXBinopExpr, /*IsPostfixUpdate=*/false,
           /*IsFailOnly=*/false, failureOrdering, isWeak);
-    }
-    return ompBuilder->createAtomicCompare(
-        ompLoc, llvmAtomicX, vOpVal, rOpVal, eVal, dVal, atomicOrdering,
-        compareOp, isXBinopExpr, /*IsPostfixUpdate=*/false,
-        /*IsFailOnly=*/false, isWeak);
-  }();
   ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
 
   if (failed(handleError(afterIP, *atomicCompareOp)))

diff  --git a/mlir/test/Dialect/OpenMP/ops.mlir b/mlir/test/Dialect/OpenMP/ops.mlir
index a035205a4d2b4..27ffee581ce6f 100644
--- a/mlir/test/Dialect/OpenMP/ops.mlir
+++ b/mlir/test/Dialect/OpenMP/ops.mlir
@@ -2152,6 +2152,150 @@ func.func @omp_atomic_compare_fail(%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_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_atomic_compare_capture_fail_only_fail
+// CHECK-SAME: (%[[V:.*]]: memref<i32>, %[[X:.*]]: memref<i32>, %[[E:.*]]: i32, %[[D:.*]]: i32)
+func.func @omp_atomic_compare_capture_fail_only_fail(%v: memref<i32>, %x: memref<i32>, %e: i32, %d: i32) {
+  // CHECK: omp.atomic.capture memory_order(seq_cst) {
+  // 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: } {fail_memory_order = #omp<memoryorderkind acquire>}
+  // CHECK-NEXT: omp.atomic.read %[[V]] = %[[X]] : memref<i32>, memref<i32>, i32
+  // CHECK-NEXT: } {fail_only}
+  omp.atomic.capture memory_order(seq_cst) {
+    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)
+    } {fail_memory_order = #omp<memoryorderkind acquire>}
+    omp.atomic.read %v = %x : memref<i32>, memref<i32>, i32
+  } {fail_only}
+  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-atomic-compare-capture.mlir b/mlir/test/Target/LLVMIR/openmp-atomic-compare-capture.mlir
new file mode 100644
index 0000000000000..7b1f177d4c070
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/openmp-atomic-compare-capture.mlir
@@ -0,0 +1,492 @@
+// 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
+}
+
+// Fail-only compare+capture with an explicit `fail` clause: V is captured on
+// the failure path, and the fail clause sets that path's ordering. Success
+// order seq_cst, fail order acquire => `cmpxchg ... seq_cst acquire`.
+// CHECK-LABEL: define void @compare_capture_failonly_fail(
+// 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]] seq_cst acquire
+// 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_fail(%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(seq_cst) {
+    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)
+    } {fail_memory_order = #omp<memoryorderkind acquire>}
+    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
+}
+
+// Complex acquire compare+capture: when the component comparison fails we
+// branch around the cmpxchg, so the initial atomic load (the only memory
+// operation on the failure path) must carry the acquire failure ordering.
+// CHECK-LABEL: define void @compare_capture_complex_acquire(
+// CHECK:         load atomic i64, ptr %{{.*}} acquire
+// CHECK:         cmpxchg ptr %{{.*}}, i64 %{{.*}}, i64 %{{.*}} acquire acquire
+// CHECK:         store { float, float } %{{.*}}, ptr %{{.*}}
+llvm.func @compare_capture_complex_acquire(%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(acquire) {
+    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 seq_cst compare+capture: the failure path load must carry the
+// seq_cst failure ordering.
+// CHECK-LABEL: define void @compare_capture_complex_seqcst(
+// CHECK:         load atomic i64, ptr %{{.*}} seq_cst
+// CHECK:         cmpxchg ptr %{{.*}}, i64 %{{.*}}, i64 %{{.*}} seq_cst seq_cst
+// CHECK:         store { float, float } %{{.*}}, ptr %{{.*}}
+llvm.func @compare_capture_complex_seqcst(%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(seq_cst) {
+    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
+}
+
+// ===== 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
+}

diff  --git a/mlir/test/Target/LLVMIR/openmp-llvm.mlir b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
index 1a9e6ac0a2bdd..5fdb7aa72b833 100644
--- a/mlir/test/Target/LLVMIR/openmp-llvm.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
@@ -2683,14 +2683,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)>
@@ -2932,6 +2937,141 @@ 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 + store old value
+  // 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 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: 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_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_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 {


        


More information about the flang-commits mailing list